Pro Yearly is on sale from $80 to $50! »

The Little Server That Could - RubyConf AU

The Little Server That Could - RubyConf AU

9c3bc4fea06c0e0bafab417be0bbdb74?s=128

stellacotton

February 10, 2017
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 1. Visiting web page
 2. Telnet


    3. CURL
 4. HTTP Client Libraries
 (e.g. Net::HTTP) Clients:
  15. Stella Cotton | @practice_cactus Just a Ruby Program

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

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

  18. Stella Cotton | @practice_cactus 1. Interacts with the OS to

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

    Pages http://www.worldwidewebsize.com/
  20. 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%
  21. Stella Cotton | @practice_cactus How is this possible?

  22. Stella Cotton | @practice_cactus W3C Worldwide Web Consortium

  23. Stella Cotton | @practice_cactus HTTP as an API

  24. Stella Cotton | @practice_cactus RFC 2616

  25. Stella Cotton | @practice_cactus

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

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

  28. 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

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

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

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

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

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

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

  35. Stella Cotton | @practice_cactus

  36. Stella Cotton | @practice_cactus 
 
 Ruby Standard Library

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

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

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

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

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

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

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

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

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

  46. Stella Cotton | @practice_cactus 
 
 Stream sockets 


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

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

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

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


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

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


    end
 
 def create_server_socket
 Socket.new(:INET, :STREAM, 0) #create socket
 end
  53. 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
  54. 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
  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
 socket.bind(sockaddr) #bind socket to that address
 socket.listen(5) #listen on the socket
 socket #return the socket
 end
  56. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  57. 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 #do something end
 end
  58. Stella Cotton | @practice_cactus 
 
 $ curl localhost:2222

  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 # returns new socket end
 end
  60. 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
  61. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  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)
 response = run_application_code
 end
 end

  63. 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
  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 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
  65. Stella Cotton | @practice_cactus Open a socket Listen for requests

    Handle those requests Close the socket
  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 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
  67. 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
  68. Stella Cotton | @practice_cactus 1. The Outside World 2. The

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

  70. 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
  71. Stella Cotton | @practice_cactus

  72. 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
  73. Stella Cotton | @practice_cactus 
 
 Ragel Parser (.rl)

  74. 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!
  75. Stella Cotton | @practice_cactus Rack Interface

  76. 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:
  77. 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)
 Ruby object Responds to .call
 with one arg Returns:
 status
 header
 body
  78. 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!
  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
 status, headers, body = app.call({})
 
 server_response = "HTTP/1.1 #{status}\r\n" +
 "#{headers}" +
 "\r\n" +
 "#{body}"
 end
 Using the Rack interface!
  80. 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)
 Ruby object Responds to .call
 with one arg Returns:
 status
 header
 body
  81. Stella Cotton | @practice_cactus Server Clients

  82. 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
  83. Stella Cotton | @practice_cactus

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

    Clients Slowly 
 downloading 
 cat gifs
  85. Stella Cotton | @practice_cactus Server Clients Client 2 waits almost

    10 seconds Slowly 
 downloading 
 cat gifs Client 1 waits 5 seconds
  86. Stella Cotton | @practice_cactus Forking

  87. 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
  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 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 Extract out
 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 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 Now we can fork 
 the code that 
 handles the request
  90. Stella Cotton | @practice_cactus We’re
 still slowly 
 downloading 


    cat gifs Server Clients But Client 2 only waits 5 seconds
  91. 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!
  92. Stella Cotton | @practice_cactus 
 Killing Zombies: sudo lsof -i

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

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

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

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

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

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

  98. Stella Cotton | @practice_cactus Garbage Collection

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

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

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

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

    Child Process Child Process
  103. 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
  104. 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
  105. Stella Cotton | @practice_cactus Threading

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

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

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

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


    buy_cat_toy
 else
 cat_toy_unavailable
 end
 end

  110. 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
  111. Stella Cotton | @practice_cactus GIL
 Global Interpreter Lock

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

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

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

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

  116. Stella Cotton | @practice_cactus 
 
 kill -l

  117. Stella Cotton | @practice_cactus 
 
 ctrl + c

  118. 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
  119. Stella Cotton | @practice_cactus 
 
 SIGKILL

  120. Stella Cotton | @practice_cactus Our Little Server 
 ❤

  121. Stella Cotton | @practice_cactus Thank you!