The Little Server That Could

9c3bc4fea06c0e0bafab417be0bbdb74?s=47 stellacotton
November 11, 2016

The Little Server That Could

Have you ever wondered what dark magic happens when you start up your Ruby server? Let’s explore the mysteries of the web universe by writing a tiny web server in Ruby! Writing a web server lets you dig deeper into the Ruby Standard Library and the Rack interface. You’ll get friendlier with I/O, signal trapping, file handles, and threading. You’ll also explore dangers first hand that can be lurking inside your production code- like blocking web requests and shared state with concurrency.

9c3bc4fea06c0e0bafab417be0bbdb74?s=128

stellacotton

November 11, 2016
Tweet

Transcript

  1. T h e L i t t l e S

    e r v e r T h a t C o u l d T h e L i t t l e S e r v e r T h a t C o u l d
  2. Stella Cotton | @practice_cactus

  3. Stella Cotton | @practice_cactus The Little Server That Could

  4. Stella Cotton | @practice_cactus The Little Server That Could Can’t

  5. Stella Cotton | @practice_cactus What can it do?

  6. Stella Cotton | @practice_cactus Abstractions are Powerful

  7. Stella Cotton | @practice_cactus Abstractions are 
 not Magic

  8. Stella Cotton | @practice_cactus Servers are an Abstraction

  9. Stella Cotton | @practice_cactus Magical

  10. Stella Cotton | @practice_cactus Servers are 
 not Magic

  11. Stella Cotton | @practice_cactus What else can it do?

  12. Stella Cotton | @practice_cactus What’s a server?

  13. Stella Cotton | @practice_cactus Production Servers: • Unicorn • Puma

    • Phusion Passenger • Thin Dev Server: • WEBrick

  14. Stella Cotton | @practice_cactus Just a Ruby Program

  15. Stella Cotton | @practice_cactus $ ruby server.rb

  16. Stella Cotton | @practice_cactus How is it different?

  17. Stella Cotton | @practice_cactus 
 1. Interacts with the OS

    to talk to the outside world
 2. Conforms to a specific API
  18. Stella Cotton | @practice_cactus 
 ~4.68 Billion 
 Indexed Web

    Pages http://www.worldwidewebsize.com/
  19. Stella Cotton | @practice_cactus https://en.wikipedia.org/wiki/Usage_share_of_web_browsers Browser Usage Share Chrome 49.82%

    Safari 13.61% Firefox 7.78% UC Browser 6.67% Opera 5.71% Other 2.43%
  20. Stella Cotton | @practice_cactus How is this possible?

  21. Stella Cotton | @practice_cactus W3C Worldwide Web Consortium

  22. Stella Cotton | @practice_cactus HTTP as an API

  23. Stella Cotton | @practice_cactus RFC 2616

  24. Stella Cotton | @practice_cactus

  25. Stella Cotton | @practice_cactus 1. Initial line 2. Zero or

    more header lines 3. Blank line 4. Message body (optional) https://www.jmarshall.com/easy/http/ GET /path/to/index.html HTTP/1.0 Host: www.example.com:80 Hello, this is a message body
  26. Stella Cotton | @practice_cactus 1. Visiting web page
 2. Telnet


    3. CURL
 4. HTTP Client Libraries
 (e.g. Net::HTTP) Clients:
  27. Stella Cotton | @practice_cactus Instructions to Build 
 Your Own

    API
  28. Stella Cotton | @practice_cactus 
 Literal “Spec” & Figurative “Spec”

  29. Stella Cotton | @practice_cactus 
 
 GET /path/to/index.html HTTP/1.0

  30. Stella Cotton | @practice_cactus 
 
 http://abc.com:80/~smith/home.html http://ABC.com/%7Esmith/home.html http://ABC.com:/%7esmith/home.html https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html

  31. Stella Cotton | @practice_cactus Seth Godin: http://sethgodin.typepad.com/

  32. Stella Cotton | @practice_cactus 
 
 $ curl localhost:2222

  33. Stella Cotton | @practice_cactus How does it communicate?

  34. Stella Cotton | @practice_cactus 1. The Outside World 2. The

    Application 3. You, the Developer
  35. Stella Cotton | @practice_cactus 
 
 Process

  36. Stella Cotton | @practice_cactus $ ps aux | grep server.rb

  37. Stella Cotton | @practice_cactus

  38. Stella Cotton | @practice_cactus 
 
 Ruby Standard Library

  39. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  40. Stella Cotton | @practice_cactus 
 
 $ netstat -a

  41. Stella Cotton | @practice_cactus 
 
 “File Descriptor”/“File Handle”

  42. Stella Cotton | @practice_cactus 
 Defining our Socket:
 1. Addressing

    Format
 2. Kind of socket
  43. Stella Cotton | @practice_cactus 
 UNIX 
 vs 
 INET

  44. Stella Cotton | @practice_cactus 
 UNIX: /tmp/my_socket.sock

  45. Stella Cotton | @practice_cactus 
 INET: 192.0.1.1:2222

  46. Stella Cotton | @practice_cactus 
 Defining our Socket:
 1. Addressing

    Format
 2. Kind of socket
  47. Stella Cotton | @practice_cactus 
 Stream 
 vs 
 Datagram

  48. Stella Cotton | @practice_cactus 
 
 Stream sockets 


  49. Stella Cotton | @practice_cactus 
 TCP 
 Three-Way Handshake

  50. Stella Cotton | @practice_cactus created by unlimicon from the Noun

    Project Client Server Syn Ack Syn, Ack
  51. Stella Cotton | @practice_cactus created by unlimicon from the Noun

    Project Client Server Ack Data Pkt 1 Ack Data Pkt 2
  52. Stella Cotton | @practice_cactus 
 
 Datagram sockets 


  53. Stella Cotton | @practice_cactus created by unlimicon from the Noun

    Project Client Server Data Pkt 10 Data Pkt 4 Data Pkt 4
  54. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 create_server_socket


    end
 
 def create_server_socket
 Socket.new(:INET, :STREAM, 0) #create socket
 end
  55. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 create_server_socket


    end
 
 def create_server_socket
 socket = Socket.new(:INET, :STREAM, 0) #create socket
 sockaddr = Addrinfo.tcp("127.0.0.1", 2222) #create an address
 end
  56. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 create_server_socket


    end
 
 def create_server_socket
 socket = Socket.new(:INET, :STREAM, 0) #create socket
 sockaddr = Addrinfo.tcp("127.0.0.1", 2222) #create an address
 socket.bind(sockaddr) #bind socket to that address
 end
  57. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 create_server_socket


    end
 
 def create_server_socket
 socket = Socket.new(:INET, :STREAM, 0) #create socket
 sockaddr = Addrinfo.tcp("127.0.0.1", 2222) #create an address
 socket.bind(sockaddr) #bind socket to that address
 socket.listen(5) #listen on the socket
 socket #return the socket
 end
  58. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  59. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do client_socket, client_addrinfo = server.accept end
 end
  60. Stella Cotton | @practice_cactus 
 
 $ curl localhost:2222

  61. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do client_socket, client_addrinfo = server.accept # returns new sockets end
 end
  62. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do client_socket, client_addrinfo = server.accept request = client_socket.recv(1056) end
 end
  63. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  64. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end
 def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code
 end
 end

  65. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end
  66. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end
  67. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  68. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end
  69. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end
  70. Stella Cotton | @practice_cactus 1. The Outside World 2. The

    Application 3. You, the Developer
  71. Stella Cotton | @practice_cactus Parser

  72. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end we never 
 actually use this
 request
  73. Stella Cotton | @practice_cactus 
 Parser extracts… Header fields and

    values Content-Length Request method Response status code Transfer-Encoding HTTP version Request URL Message body
  74. Stella Cotton | @practice_cactus 
 
 Ragel Parser (.rl)

  75. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end let’s modify this!
  76. Stella Cotton | @practice_cactus Rack Interface

  77. Stella Cotton | @practice_cactus 1. Ruby Object
 2. Responds to

    “.call”
 3. Takes 1 argument (env hash)
 4. Returns a response with 
 status, header, body Rack Interface:
  78. Stella Cotton | @practice_cactus class RackApp
 def call(environment)
 [
 '200',


    {'Content-Type' => 'text/html'},
 ["Hello world for reals"]
 ]
 end
 end
 
 Rack::Handler.register('server', 'Rack::Handler::Server')
 Rack::Handler::Server.run(RackApp.new)

  79. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end
 def run_application_code
 app_response = "Hello World!\n"
 server_response = "HTTP/1.1 200 OK\r\n" +
 "Content-Type: text/plain\r\n" +
 "Content-Length: #{app_response.bytesize}\r\n” +
 "Connection: close\r\n" +
 "\r\n" +
 app_response
 end let’s modify this!
  80. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end def run_application_code
 status, headers, body = app.call({})
 
 server_response = "HTTP/1.1 #{status}\r\n" +
 "#{headers}" +
 "\r\n" +
 "#{body}"
 end
 Rack interface!
  81. Stella Cotton | @practice_cactus class RackApp
 def call(environment)
 [
 '200',


    {'Content-Type' => 'text/html'},
 ["Hello world for reals"]
 ]
 end
 end
 
 Rack::Handler.register('server', 'Rack::Handler::Server')
 Rack::Handler::Server.run(RackApp.new)

  82. Stella Cotton | @practice_cactus

  83. Stella Cotton | @practice_cactus class RackApp
 def call(environment)
 maru_gif =

    get_maru_gif_from_giphy_api
 [
 '200',
 {'Content-Type' => 'text/html'},
 [ maru_gif ]
 ]
 end
 end
 
 Rack::Handler.register('server', 'Rack::Handler::Server')
 Rack::Handler::Server.run(RackApp.new) External API call
  84. Stella Cotton | @practice_cactus

  85. Stella Cotton | @practice_cactus Client 1 waits 5 seconds Server

    Two Clients Slowly 
 downloading 
 cat gifs
  86. Stella Cotton | @practice_cactus Server Two Clients Client 2 waits

    almost 10 seconds Slowly 
 downloading 
 cat gifs
  87. Stella Cotton | @practice_cactus Forking

  88. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept request = client_socket.recv(1056)
 response = run_application_code client_socket.print response client_socket.close
 end
 end
 code that 
 handles request
  89. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept handle_request(client_socket) client_socket.close
 end
 end
 def handle_request
 request = client_socket.recv(1056)
 response = run_application_code
 client_socket.print response
 end code that 
 handles request
  90. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept fork do handle_request(client_socket) end client_socket.close
 end
 end
 def handle_request
 request = client_socket.recv(1056)
 response = run_application_code
 client_socket.print response
 end code that 
 handles request
  91. Stella Cotton | @practice_cactus Still slowly 
 downloading 
 cat

    gifs Server Two Clients Client 2 only waits 5 seconds
  92. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept fork do handle_request(client_socket) end client_socket.close
 end
 end
 def handle_request
 request = client_socket.recv(1056)
 response = run_application_code
 client_socket.print response
 end be sure to close this!
  93. Stella Cotton | @practice_cactus 
 Killing Zombies: sudo lsof -i

    -n -P | grep 2222 ps aux | grep [process id] kill [process id]

  94. Stella Cotton | @practice_cactus Parent Process fork do Child Process

    fork do Child Process fork do Child Process
  95. Stella Cotton | @practice_cactus 
 
 Wait…won’t fork double my

    memory that I’m consuming?
  96. Stella Cotton | @practice_cactus 
 
 “Copy on Write”

  97. Stella Cotton | @practice_cactus Parent Process fork do Child Process

    fork do Child Process fork do Child Process
  98. Stella Cotton | @practice_cactus Pre- Ruby 2.0
 :(

  99. Stella Cotton | @practice_cactus Garbage Collection

  100. Stella Cotton | @practice_cactus 
 
 cat_gifs = “awesome”

  101. Stella Cotton | @practice_cactus Pre- Ruby 2.0
 :( *technically a

    patched version of Ruby 1.8 existed, but wasn’t in the core
  102. Stella Cotton | @practice_cactus Parent Process fork do Child Process

    fork do Child Process fork do Child Process
  103. Stella Cotton | @practice_cactus Parent Process fork do fork do

    Child Process Child Process
  104. Stella Cotton | @practice_cactus 
 
 Ruby 2.0
 :) http://patshaughnessy.net/2012/3/23/


    why-you-should-be-excited-about-garbage-collection-in-ruby-2-0
  105. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 socket

    = create_server_socket
 loop_and_listen_for_client_requests(socket)
 end def loop_and_listen_for_client_requests(server)
 loop do
 client_socket, client_addrinfo = server.accept fork do handle_request(client_socket) end client_socket.close
 end
 end
 def handle_request
 request = client_socket.recv(1056)
 response = run_application_code
 client_socket.print response
 end
  106. Stella Cotton | @practice_cactus Threading

  107. Stella Cotton | @practice_cactus Parent Process Thread.new Child Thread Child

    Thread Child Thread Thread.new Thread.new Shared Memory
  108. Stella Cotton | @practice_cactus Benefits of Threading:
 - Uses less

    memory
 -Threads die with process
 - Much faster to communicate
  109. Stella Cotton | @practice_cactus GIL
 Global Interpreter Lock

  110. Stella Cotton | @practice_cactus def purchase_cat_toys
 if @available_cat_toys > 0


    buy_cat_toy
 else
 cat_toy_unavailable
 end
 end

  111. Stella Cotton | @practice_cactus def purchase_cat_toys
 if @available_cat_toys > 0

    
 #context switch 
 #buy a cat toy that #doesn’t exist :( buy_cat_toy
 else
 cat_toy_unavailable
 end
 end def purchase_cat_toys 
 if @available_cat_toys > 0
 #buy last cat toy
 buy_cat_toy 
 else
 cat_toy_unavailable
 end
 end #context switch Thread 1 Thread 2
  112. Stella Cotton | @practice_cactus GIL
 Global Interpreter Lock

  113. Stella Cotton | @practice_cactus Why use a multi-threaded 
 server

    with MRI?
  114. Stella Cotton | @practice_cactus Downsides of Threading:
 - Your code

    has to be thread safe
 - Your gems have to be thread safe
  115. Stella Cotton | @practice_cactus 1. The Outside World 2. The

    Application 3. You, the Developer
  116. Stella Cotton | @practice_cactus 
 
 Signals

  117. Stella Cotton | @practice_cactus 
 
 kill -l

  118. Stella Cotton | @practice_cactus 
 
 ctrl + c

  119. Stella Cotton | @practice_cactus require 'socket'
 
 def server
 trap_interrupt


    socket = create_server_socket
 loop_and_listen_for_client_requests(socket)
 socket.close
 end
 
 def trap_interrupt
 Signal.trap("INT") do
 puts "\nTerminating..."
 exit 130
 end
 end
  120. Stella Cotton | @practice_cactus 
 
 SIGKILL

  121. Stella Cotton | @practice_cactus Our Little Server 
 ❤

  122. Stella Cotton | @practice_cactus 11am - 2pm Saturday