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

From Rails to the Webserver to the Browser

From Rails to the Webserver to the Browser

Most of us know how to build beautiful web applications with Rails. With the help of templating tools like ERB and HAML our web apps create HTML documents, but, do you know exactly how those HTML documents end up in a browser?

During this talk I will show you the bits that make it all happen. We will dissect the relevant code within Rails, Rack and the thin web server to discover exactly how the web server starts and listens to a TCP port, communicates with Rails and returns the HTML document that your browser parses.

Why? Because we're curious about it, that's why.

David Padilla

April 30, 2013
Tweet

More Decks by David Padilla

Other Decks in Programming

Transcript

  1. require 'test/unit' require 'turn' require 'net/http' class TestServer < Test::Unit::TestCase

    def test_the_request uri = URI('http://127.0.0.1:9292') assert "Hello World", Net::HTTP.get(uri) end end test/test.rb
  2. class App def call(env) content = "Hello World" status_code =

    200 headers = { "Content-Length" => content.length.to_s } [status_code, headers, [content]] end end rack/app.rb
  3. module Thin # The uterly famous Thin HTTP server. #

    It listen for incoming request through # a given +backend+ # and forward all request to +app+. # # == TCP server # Create a new TCP server on bound # to <tt>host:port</tt> by specifiying +host+ # and +port+ as the first 2 arguments. # # Thin::Server.start('0.0.0.0', 3000, app) lib/thin/server.rb
  4. # Start the server and listen for connections. def start

    raise ArgumentError, 'app required' unless @app log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::C debug ">> Debugging ON" trace ">> Tracing ON" log ">> Maximum connections set to #{@backend.maximum_connections}" log ">> Listening on #{@backend}, CTRL+C to stop" @backend.start end lib/thin/server.rb
  5. # Start the server and listen for connections. def start

    raise ArgumentError, 'app required' unless @app log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::C debug ">> Debugging ON" trace ">> Tracing ON" log ">> Maximum connections set to #{@backend.maximum_connections}" log ">> Listening on #{@backend}, CTRL+C to stop" @backend.start end lib/thin/server.rb
  6. def select_backend(host, port, options) case when options.has_key?(:backend) raise ArgumentError, ":backend

    must be options[:backend].new(host, port, optio when options.has_key?(:swiftiply) Backends::SwiftiplyClient.new(host, por when host.include?('/') Backends::UnixServer.new(host) else Backends::TcpServer.new(host, port) end end lib/thin/server.rb
  7. def select_backend(host, port, options) case when options.has_key?(:backend) raise ArgumentError, ":backend

    must be options[:backend].new(host, port, optio when options.has_key?(:swiftiply) Backends::SwiftiplyClient.new(host, por when host.include?('/') Backends::UnixServer.new(host) else Backends::TcpServer.new(host, port) end end lib/thin/server.rb
  8. def select_backend(host, port, options) case when options.has_key?(:backend) raise ArgumentError, ":backend

    must be options[:backend].new(host, port, optio when options.has_key?(:swiftiply) Backends::SwiftiplyClient.new(host, por when host.include?('/') Backends::UnixServer.new(host) else Backends::TcpServer.new(host, port) end end lib/thin/server.rb
  9. module Thin module Backends # Backend to act as a

    TCP socket server. class TcpServer < Base # Address and port on which the server is listening for connections. attr_accessor :host, :port def initialize(host, port) @host = host @port = port super() end # Connect the server def connect @signature = EventMachine.start_server(@host, @port, Connection, &method(:initializ end # Stops the server def disconnect EventMachine.stop_server(@signature) end def to_s "#{@host}:#{@port}" end end end end lib/thin/backends/tcp_server.rb
  10. module Connection def post_init # A client connected end def

    receive_data data # Data received end def unbind # Client disconnected end end EventMachine.start_server("0.0.0.0", 8081, Connection) EventMachine
  11. # Called when data is received from the client. def

    receive_data(data) @idle = false trace { data } process if @request.parse(data) rescue InvalidRequest => e log "!! Invalid request" log_error e post_process Response::BAD_REQUEST end lib/thin/connection.rb
  12. # Called when data is received from the client. def

    receive_data(data) @idle = false trace { data } process if @request.parse(data) rescue InvalidRequest => e log "!! Invalid request" log_error e post_process Response::BAD_REQUEST end lib/thin/connection.rb
  13. # Parse a chunk of data into the request environment

    # Raises a +InvalidRequest+ if invalid. # Returns +true+ if the parsing is complete. def parse(data) if @parser.finished? # Header finished, can only be some more body @body << data else # Parse more header using the super parser @data << data raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER @nparsed = @parser.execute(@env, @data, @nparsed) # Transfert to a tempfile if body is very big move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY end if finished? # Check if header and body are complete @data = nil @body.rewind true # Request is fully parsed else false # Not finished, need more data end end lib/thin/request.rb
  14. /** * Copyright (c) 2005 Zed A. Shaw * You

    can redistribute it and/or modify * it under the same terms as Ruby. */ #ifndef http11_parser_h #define http11_parser_h #include <sys/types.h> #if defined(_WIN32) #include <stddef.h> #endif
  15. # Called when data is received from the client def

    receive_data(data) @idle = false trace { data } process if @request.parse(data) rescue InvalidRequest => e log "!! Invalid request" log_error e post_process Response::BAD_REQUEST end lib/thin/connection.rb
  16. # Called when data is received from the client def

    receive_data(data) @idle = false trace { data } process if @request.parse(data) rescue InvalidRequest => e log "!! Invalid request" log_error e post_process Response::BAD_REQUEST end lib/thin/connection.rb
  17. # Called when all data was received and # the

    request is ready to be processed. def process if threaded? @request.threaded = true EventMachine.defer(method(:pre_process), else @request.threaded = false post_process(pre_process) end end lib/thin/connection.rb
  18. # Called when all data was received and # the

    request is ready to be processed. def process if threaded? @request.threaded = true EventMachine.defer(method(:pre_process), else @request.threaded = false post_process(pre_process) end end lib/thin/connection.rb
  19. def pre_process # Add client info to the request env

    @request.remote_address = remote_address # Connection may be closed unless the App# # It should be noted that connection objec # callback is no longer referenced, so be @request.async_callback = method(:post_pro if @backend.ssl? @request.env["rack.url_scheme"] = "https if cert = get_peer_cert @request.env['rack.peer_cert'] = cert end end
  20. end # When we're under a non-async framework l #

    off async responses using the callback i # in removing this. response = AsyncResponse catch(:async) do # Process the request calling the Rack a response = @app.call(@request.env) end response rescue Exception handle_error # Pass through error response can_persist? && @request.persistent? ? Res end
  21. end # When we're under a non-async framework l #

    off async responses using the callback i # in removing this. response = AsyncResponse catch(:async) do # Process the request calling the Rack a response = @app.call(@request.env) end response rescue Exception handle_error # Pass through error response can_persist? && @request.persistent? ? Res end lib/thin/connection.rb
  22. # Called when all data was received and # the

    request is ready to be processed. def process if threaded? @request.threaded = true EventMachine.defer(method(:pre_process), else @request.threaded = false post_process(pre_process) end end lib/thin/connection.rb
  23. # Called when all data was received and # the

    request is ready to be processed. def process if threaded? @request.threaded = true EventMachine.defer(method(:pre_process), else @request.threaded = false post_process(pre_process) end end