Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Little Server That Could - RubyConf AU

stellacotton
February 10, 2017

The Little Server That Could - RubyConf AU

stellacotton

February 10, 2017
Tweet

More Decks by stellacotton

Other Decks in Technology

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 Production Servers: • Unicorn • Puma

    • Phusion Passenger • Thin Dev Server: • WEBrick

  3. Stella Cotton | @practice_cactus 1. Visiting web page
 2. Telnet


    3. CURL
 4. HTTP Client Libraries
 (e.g. Net::HTTP) Clients:
  4. Stella Cotton | @practice_cactus 1. Interacts with the OS to

    talk to the outside world 2. Conforms to a specific API
  5. Stella Cotton | @practice_cactus created by unlimicon from the Noun

    Project Client Server Ack Data Pkt 1 Ack Data Pkt 2
  6. Stella Cotton | @practice_cactus created by unlimicon from the Noun

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


    end
 
 def create_server_socket
 Socket.new(:INET, :STREAM, 0) #create socket
 end
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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

  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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!
  22. 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:
  23. 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
  24. 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!
  25. 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!
  26. 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
  27. 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
  28. Stella Cotton | @practice_cactus Client 1 waits 5 seconds Server

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

    10 seconds Slowly 
 downloading 
 cat gifs Client 1 waits 5 seconds
  30. 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
  31. 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
  32. 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
  33. Stella Cotton | @practice_cactus We’re
 still slowly 
 downloading 


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

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

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

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

    fork do Child Process fork do Child Process
  38. Stella Cotton | @practice_cactus Pre- Ruby 2.0
 :( *technically a

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

    fork do Child Process fork do Child Process
  40. 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
  41. 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
  42. Stella Cotton | @practice_cactus Parent Process Thread.new Child Thread Child

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

    memory
 -Threads die with process
 - Much faster to communicate
  44. Stella Cotton | @practice_cactus def purchase_cat_toys
 if @available_cat_toys > 0


    buy_cat_toy
 else
 cat_toy_unavailable
 end
 end

  45. 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
  46. Stella Cotton | @practice_cactus Downsides of Threading:
 - Your code

    has to be thread safe
 - Your gems have to be thread safe
  47. 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