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

The Lifecycle of a Rails Request

The Lifecycle of a Rails Request

This breathtaking documentary series combines rare action, unimaginable scale, impossible locations and intimate moments captured from the depths of Rails' deepest internals. Together we will follow the lives of Rails' best loved, wildest and most elusive components. From the towering peaks of Rack to the lush green of Action Dispatch and the dry-sculpted crescents of Action Controller, our world is truly spectacular. Join the Skylight team on this incredible Journey to unearth the lifecycle of a Rails request.

Godfrey Chan

April 30, 2019
Tweet

More Decks by Godfrey Chan

Other Decks in Programming

Transcript

  1. app/controllers/hello_controller.rb
    Hello World
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    class HelloController < ActionController::Base
    def index
    # ...do stuff here...
    render plain: 'Hello World'
    end
    end
    app/controllers/hello_controller.rb

    View Slide

  2. Meet at Pastini?
    where dat
    911 SW Taylor St, Portland, OR 97205

    View Slide

  3. app/controllers/hello_controller.rb
    What’s the address for skylight.io?
    34.194.84.73
    http://skylight.io/hello

    View Slide

  4. app/controllers/hello_controller.rb
    http://skylight.io/hello
    DNS LOOKUP
    Domain Name System

    View Slide

  5. app/controllers/hello_controller.rb
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    $ dig skylight.io


    ; <<>> DiG 9.10.6 <<>> skylight.io
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32689
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 512
    ;; QUESTION SECTION:
    ;skylight.io. IN A
    ;; ANSWER SECTION:
    skylight.io. 59 IN A 34.194.84.73
    ;; Query time: 34 msec
    ;; SERVER: 8.8.8.8#53(8.8.8.8)
    ;; WHEN: Mon Apr 29 14:50:34 PDT 2019
    ;; MSG SIZE rcvd: 56
    Terminal

    View Slide

  6. app/controllers/hello_controller.rb
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    $ dig skylight.io


    ; <<>> DiG 9.10.6 <<>> skylight.io
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32689
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 512
    ;; QUESTION SECTION:
    ;skylight.io. IN A
    ;; ANSWER SECTION:
    skylight.io. 59 IN A 34.194.84.73
    ;; Query time: 34 msec
    ;; SERVER: 8.8.8.8#53(8.8.8.8)
    ;; WHEN: Mon Apr 29 14:50:34 PDT 2019
    ;; MSG SIZE rcvd: 56
    Terminal

    View Slide

  7. app/controllers/hello_controller.rb
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    Terminal
    $

    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80



    < Connection closed by foreign host.

    Hi???

    View Slide

  8. app/controllers/hello_controller.rb
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    Terminal
    $

    Trying 34.194.84.73...
    Connected to ec2-34-194-84-73.compute-1.amazonaws.com.
    Escape character is ‘^]’.
    >
    telnet 34.194.84.73 80



    < HTTP/1.1 200 OK
    < Content-Type: text/plain
    < Content-Length: 11
    < Date: Thu, 25 Apr 2019 18:52:54 GMT
    <
    < Hello World

    GET /hello HTTP/1.1
    > Host: skylight.io
    >
    Hello World

    View Slide

  9. View Slide

  10. hey server, can you do me a favor?
    What’s up?
    If someone ask for /assets/*, can you give it
    to them from /var/myapp/public/assets?
    Sure thing.
    With compression, 404, etc. The usual.

    View Slide

  11. app/controllers/hello_controller.rb
    http://skylight.io/assets/kitten.jpg
    app/controllers/hello_controller.rb
    location /assets {
    alias /var/myapp/public/assets;
    gzip_static on;
    gzip on;
    expires max;
    add_header Cache-Control public;
    }
    nginx.conf
    © Kenny Louie, CC BY 2.0

    View Slide

  12. app/controllers/hello_controller.rb
    http://skylight.io/blog
    !
    location /blog {
    }
    nginx.conf
    !

    View Slide

  13. app/controllers/hello_controller.rb
    server.on_request do |request, response|
    request.path # => '/hello'
    request.headers # => { host: ... }
    response.status = 200
    response.body = 'Hello World'
    end
    app/controllers/hello_controller.rb
    MyApp.handle_request(
    http_method, # => 'GET'
    http_path, # => '/hello'
    http_headers # => #
    )
    # => #
    app/controllers/hello_controller.rb
    ENV['REQUEST_METHOD'] # => 'GET'
    ENV['PATH_INFO'] # => '/hello'
    ENV['HTTP_HOST'] # => 'skylight.io'
    puts 'Status: 200 OK'
    puts
    puts 'Hello World'

    View Slide

  14. Hey, I’ve a request for you to handle.
    Sounds good.
    It’s a GET request for /hello, Host header is skylight.io
    200 OK. Content type is text/plain, body is “Hello World”.

    View Slide

  15. app/controllers/hello_controller.rb
    Hello World
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    env = {
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => '/hello',
    'HTTP_HOST' => 'skylight.io',
    # ...
    }
    status, headers, body = app.call(env)
    status # => 200
    headers # => { 'Content-Type' => 'text/plain' }
    body # => ['Hello World']
    server.rb

    View Slide

  16. app/controllers/hello_controller.rb
    Terminal
    $
    app/controllers/hello_controller.rb
    class HelloWorld
    def call(env)
    if env['PATH_INFO'] == '/hello'
    [200, {'Content-Type' => 'text/plain'}, ['Hello World']]
    else
    [404, {'Content-Type' => 'text/plain'}, ['Not Found']]
    end
    end
    end
    app.rb
    app/controllers/hello_controller.rb
    require_relative 'app'
    run HelloWorld.new
    config.ru
    rackup
    Thin web server (v1.7.2 codename Bachmanity)
    Maximum connections set to 1024
    Listening on localhost:9292, CTRL+C to stop
    app/controllers/hello_controller.rb
    Hello World
    http://localhost:9292/hello
    app/controllers/hello_controller.rb
    Not Found
    http://localhost:9292/wat

    View Slide

  17. app/controllers/hello_controller.rb
    class HelloWorld
    def call(env)
    if env['PATH_INFO'] == '/'
    [301, {'Location' => '/hello'}, []]
    elsif env['PATH_INFO'] == '/hello'
    [200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
    else
    [404, {'Content-Type' => 'text/plain'}, ['Not Found!']]
    end
    end
    end
    app.rb

    View Slide

  18. app/controllers/hello_controller.rb
    class Redirect
    def initialize(app, from:, to:)
    @app = app
    @from = from
    @to = to
    end
    def call(env)
    if env["PATH_INFO"] == @from
    [301, {"Location" => @to}, []]
    else
    @app.call(env)
    end
    end
    end
    class HelloWorld
    def call(env)
    if env["PATH_INFO"] == '/hello'
    [200, {"Content-Type" => "text/plain"}, ["Hello World!"]]
    else
    [404, {"Content-Type" => "text/plain"}, ["Not Found!"]]
    end
    end
    end
    app.rb
    app/controllers/hello_controller.rb
    require_relative 'app'
    run Redirect.new(
    HelloWorld.new,
    from: '/',
    to: '/hello'
    )
    config.ru

    View Slide

  19. app/controllers/hello_controller.rb
    class Redirect
    def initialize(app, from:, to:)
    @app = app
    @from = from
    @to = to
    end
    def call(env)
    if env["PATH_INFO"] == @from
    [301, {"Location" => @to}, []]
    else
    @app.call(env)
    end
    end
    end
    class HelloWorld
    def call(env)
    if env["PATH_INFO"] == '/hello'
    [200, {"Content-Type" => "text/plain"}, ["Hello World!"]]
    else
    [404, {"Content-Type" => "text/plain"}, ["Not Found!"]]
    end
    end
    end
    app.rb
    app/controllers/hello_controller.rb
    require_relative 'app'
    use Redirect, from: '/', to: '/hello'
    run HelloWorld.new
    config.ru

    View Slide

  20. app/controllers/hello_controller.rb
    class Redirect
    def initialize(app, from:, to:)
    @app = app
    @from = from
    @to = to
    end
    def call(env)
    if env["PATH_INFO"] == @from
    [301, {"Location" => @to}, []]
    else
    @app.call(env)
    end
    end
    end
    class HelloWorld
    def call(env)
    if env["PATH_INFO"] == '/hello'
    [200, {"Content-Type" => "text/plain"}, ["Hello World!"]]
    else
    [404, {"Content-Type" => "text/plain"}, ["Not Found!"]]
    end
    end
    end
    app.rb
    app/controllers/hello_controller.rb
    require_relative 'app'
    use Rack::Deflater
    use Rack::Head
    use Rack::ConditionalGet
    use Rack::ETag
    use Redirect, from: '/', to: '/hello'
    run HelloWorld.new
    config.ru

    View Slide

  21. app/controllers/hello_controller.rb
    require_relative 'config/environment'
    run Rails.application
    config.ru
    app/controllers/hello_controller.rb
    $
    Terminal
    bin/rails console
    >> Rails.application.call({ ...env hash... })

    View Slide

  22. app/controllers/hello_controller.rb
    require_relative 'config/environment'
    run Rails.application
    config.ru
    app/controllers/hello_controller.rb
    $
    Terminal
    bin/rails console
    >> env = {
    >> 'REQUEST_METHOD' => 'GET',
    >> 'PATH_INFO' => ‘/posts/1’,
    >> 'HTTP_HOST' => 'localhost',
    >> 'HTTP_PORT' => '3000',
    >> # ...
    >> }
    >> Rails.application.call(env)

    View Slide

  23. app/controllers/hello_controller.rb
    require_relative 'config/environment'
    run Rails.application
    config.ru
    app/controllers/hello_controller.rb
    $ bin/rails console
    >> env = Rack::MockRequest.env_for('http://localhost:3000/posts/1')
    >>
    Terminal

    View Slide

  24. app/controllers/hello_controller.rb
    require_relative 'config/environment'
    run Rails.application
    config.ru
    app/controllers/hello_controller.rb
    $ bin/rails console
    >> env = Rack::MockRequest.env_for('http://localhost:3000/posts/1')
    >>
    Terminal
    status, headers, body = Rails.application.call(env)
    Processing by PostsController#show as HTML
    Parameters: {"id"=>"1"}
    CACHE Post Load (0.0ms) SELECT "posts".* FROM "posts" WHERE ...
    Rendering posts/show.html.erb within layouts/application
    Rendered posts/show.html.erb within layouts/application (0.3ms)
    Completed 200 OK in 23ms (Views: 21.1ms | ActiveRecord: 0.0ms)
    >> status
    => 200
    >> headers
    => { "Content-Type"=>"text/html; charset=utf-8”, ... }
    >> puts body.join('')


    My Blorgh

    Rails is Omakase
    There are lots of à la carte software environments in...


    View Slide

  25. app/controllers/hello_controller.rb
    $
    Terminal
    bin/rails middleware
    use Rack::Sendfile
    use ActionDispatch::Executor
    use ActiveSupport::Cache::Strategy::LocalCache::Middleware
    use Rack::Runtime
    use Rack::MethodOverride
    use ActionDispatch::RequestId
    use ActionDispatch::RemoteIp
    use Rails::Rack::Logger
    use ActionDispatch::ShowExceptions
    use ActionDispatch::DebugExceptions
    use ActionDispatch::Callbacks
    use ActionDispatch::Cookies
    use ActionDispatch::Session::CookieStore
    use ActionDispatch::Flash
    use ActionDispatch::ContentSecurityPolicy::Middleware
    use Rack::Head
    use Rack::ConditionalGet
    use Rack::ETag
    use Rack::TempfileReaper
    run Blorgh::Application.routes

    View Slide

  26. app/controllers/hello_controller.rb
    require_relative 'boot'
    require 'rails/all'
    Bundler.require(*Rails.groups)
    module Blorgh
    class Application < Rails::Application
    # Disable cookies
    config.middleware.delete ActionDispatch::Cookies
    config.middleware.delete ActionDispatch::Session::CookieStore
    config.middleware.delete ActionDispatch::Flash
    # Add your own middleware
    config.middleware.use CaptchaEverywhere
    end
    end
    config/application.rb

    View Slide

  27. app/controllers/hello_controller.rb
    $
    Terminal
    bin/rails middleware
    use Rack::Sendfile
    use ActionDispatch::Executor
    use ActiveSupport::Cache::Strategy::LocalCache::Middleware
    use Rack::Runtime
    use Rack::MethodOverride
    use ActionDispatch::RequestId
    use ActionDispatch::RemoteIp
    use Rails::Rack::Logger
    use ActionDispatch::ShowExceptions
    use ActionDispatch::DebugExceptions
    use ActionDispatch::Callbacks
    use ActionDispatch::Cookies
    use ActionDispatch::Session::CookieStore
    use ActionDispatch::Flash
    use ActionDispatch::ContentSecurityPolicy::Middleware
    use Rack::Head
    use Rack::ConditionalGet
    use Rack::ETag
    use Rack::TempfileReaper
    run Blorgh::Application.routes

    View Slide

  28. app/controllers/hello_controller.rb
    $ bin/rails
    >> env = Rack::MockRequest.env_for('http://localhost:3000/posts/1')
    >>
    Terminal
    console
    status, headers, body = Blorgh::Application.routes.call(env)
    Processing by PostsController#show as HTML
    Parameters: {"id"=>"1"}
    CACHE Post Load (0.0ms) SELECT "posts".* FROM "posts" WHERE ...
    Rendering posts/show.html.erb within layouts/application
    Rendered posts/show.html.erb within layouts/application (0.3ms)
    Completed 200 OK in 23ms (Views: 21.1ms | ActiveRecord: 0.0ms)
    >> status
    => 200
    >> headers
    => { "Content-Type"=>"text/html; charset=utf-8”, ... }
    >> puts body.join('')


    My Blorgh

    Rails is Omakase
    There are lots of à la carte software environments in...


    View Slide

  29. app/controllers/hello_controller.rb
    Rails.application.routes.draw do
    resources :posts
    end
    config/routes.rb

    View Slide

  30. app/controllers/hello_controller.rb
    Rails.application.routes.draw do
    get '/posts' => 'posts#index'
    get '/posts/new' => 'posts#new'
    post '/posts' => 'posts#create'
    get '/posts/:id' => 'posts#show'
    get '/posts/:id/edit' => 'posts#edit'
    put '/posts/:id' => 'posts#update'
    delete '/posts/:id' => 'posts#destroy'
    end
    config/routes.rb

    View Slide

  31. app/controllers/hello_controller.rb
    Rails.application.routes.draw do
    get '/posts' => 'posts#index'
    get '/posts/new' => 'posts#new'
    post '/posts' => 'posts#create'
    get '/posts/:id' => 'posts#show'
    get '/posts/:id/edit' => 'posts#edit'
    put '/posts/:id' => 'posts#update'
    delete '/posts/:id' => 'posts#destroy'
    end
    config/routes.rb
    PostsController.action(:index)

    View Slide

  32. app/controllers/hello_controller.rb
    class ActionController::Base
    def self.action(name)
    ->(env) {
    request = ActionDispatch::Request.new(env)
    response = ActionDispatch::Response.new(request)
    controller = self.new(request, response)
    controller.process_action(name)
    response.to_a
    }
    end
    attr_reader :request, :response, :params
    def initialize(request, response)
    @request = request
    @response = response
    @params = request.params
    end
    def process_action(name)
    event = 'process_action.action_controller'
    payload = {
    controller: self.class.name,
    action: action_name,
    # ...
    }
    ActiveSupport::Notifications.instrument(event, payload) do
    self.send(name)
    end
    end
    end
    rails/actionpack/lib/action_controller/base.rb

    View Slide

  33. app/controllers/hello_controller.rb
    class ActionController::Base
    def self.action(name)
    ->(env) {
    request = ActionDispatch::Request.new(env)
    response = ActionDispatch::Response.new(request)
    controller = self.new(request, response)
    controller.process_action(name)
    response.to_a
    }
    end
    attr_reader :request, :response, :params
    def initialize(request, response)
    @request = request
    @response = response
    @params = request.params
    end
    def process_action(name)
    event = 'process_action.action_controller'
    payload = {
    controller: self.class.name,
    action: action_name,
    # ...
    }
    ActiveSupport::Notifications.instrument(event, payload) do
    self.send(name)
    end
    end
    end
    rails/actionpack/lib/action_controller/base.rb

    View Slide

  34. app/controllers/hello_controller.rb
    class BlorghRoutes
    def call(env)
    verb = env['REQUEST_METHOD']
    path = env['PATH_INFO']
    if verb == 'GET' && path == '/posts'
    PostsController.action(:index).call(env)
    elsif verb == 'GET' && path == '/posts/new'
    PostsController.action(:new).call(env)
    elsif verb == ‘POST' && path == '/posts'
    PostsController.action(:create).call(env)
    elsif verb == 'GET' && path =~ %r(/posts/.+)
    PostsController.action(:show).call(env)
    elsif verb == 'GET' && path =~ %r(/posts/.+/edit)
    PostsController.action(:edit).call(env)
    elsif verb == 'PUT' && path =~ %r(/posts)
    PostsController.action(:update).call(env)
    elsif verb == 'DELETE' && path = %r(/posts/.+)
    PostsController.action(:destroy).call(env)
    else
    [404, {'Content-Type': 'text-plain', ...}, ['Not Found!']]
    end
    end
    end
    blorgh_routes.rb

    View Slide

  35. app/controllers/hello_controller.rb
    class BlorghRoutes
    def call(env)
    verb = env['REQUEST_METHOD']
    path = env['PATH_INFO']
    if verb == 'GET' && path == '/posts'
    PostsController.action(:index).call(env)
    elsif verb == 'GET' && path == '/posts/new'
    PostsController.action(:new).call(env)
    elsif verb == 'GET' && path == '/posts'
    PostsController.action(:create).call(env)
    elsif verb == 'GET' && path =~ %r(/posts/.+)
    PostsController.action(:show).call(env)
    elsif verb == 'GET' && path =~ %r(/posts/.+/edit)
    PostsController.action(:edit).call(env)
    elsif verb == 'PUT' && path =~ %r(/posts)
    PostsController.action(:update).call(env)
    elsif verb == 'DELETE' && path = %r(/posts/.+)
    PostsController.action(:destroy).call(env)
    else
    [404, {'Content-Type': 'text-plain', ...}, ['Not Found!']]
    end
    end
    end
    https://youtu.be/lEC-QoZeBkM

    View Slide

  36. app/controllers/hello_controller.rb
    Rails.application.routes.draw do
    get '/hello' => HelloWorld.new
    get '/hello' => ->(env) {
    [200, {'Content-Type': 'text/plain'}, ['Hello World!']
    }
    # redirect(...) returns a Rack app!
    get '/' => redirect('/hello')
    mount Sidekiq::Web, at: '/sidekiq'
    end
    config/routes.rb

    View Slide

  37. app/controllers/hello_controller.rb
    Rails.application.routes.draw do
    get '/posts' => PostsController.action(:index)
    get '/posts/new' => PostsController.action(:new)
    post '/posts' => PostsController.action(:create)
    get '/posts/:id' => PostsController.action(:show)
    get '/posts/:id/edit' => PostsController.action(:edit)
    put '/posts/:id' => PostsController.action(:update)
    delete '/posts/:id' => PostsController.action(:destroy)
    end
    config/routes.rb

    View Slide

  38. app/controllers/hello_controller.rb
    Hello World
    http://skylight.io/hello
    app/controllers/hello_controller.rb
    class HelloController < ActionController::Base
    def index
    # ...do stuff here...
    render plain: 'Hello World'
    end
    end
    app/controllers/hello_controller.rb

    View Slide

  39. app/controllers/hello_controller.rb
    $
    Terminal
    bin/rails middleware
    use Skylight::Middleware
    use Rack::Sendfile
    use ActionDispatch::Executor
    use ActiveSupport::Cache::Strategy::LocalCache::Middleware
    use Rack::Runtime
    use Rack::MethodOverride
    use ActionDispatch::RequestId
    use ActionDispatch::RemoteIp
    use Rails::Rack::Logger
    use ActionDispatch::ShowExceptions
    use ActionDispatch::DebugExceptions
    use ActionDispatch::Callbacks
    use ActionDispatch::Cookies
    use ActionDispatch::Session::CookieStore
    use ActionDispatch::Flash
    use ActionDispatch::ContentSecurityPolicy::Middleware
    use Rack::Head
    use Rack::ConditionalGet
    use Rack::ETag
    use Rack::TempfileReaper
    run Blorgh::Application.routes

    View Slide

  40. app/controllers/hello_controller.rb
    class ActionController::Base
    def self.action(name)
    ->(env) {
    request = ActionDispatch::Request.new(env)
    response = ActionDispatch::Response.new(request)
    controller = self.new(request, response)
    controller.process_action(name)
    response.to_a
    }
    end
    attr_reader :request, :response, :params
    def initialize(request, response)
    @request = request
    @response = response
    @params = request.params
    end
    def process_action(name)
    event = 'process_action.action_controller'
    payload = {
    controller: self.class.name,
    action: action_name,
    # ...
    }
    ActiveSupport::Notifications.instrument(event, payload) do
    self.send(name)
    end
    end
    end
    rails/actionpack/lib/action_controller/base.rb

    View Slide

  41. app/controllers/hello_controller.rb
    skylight.io

    View Slide

  42. app/controllers/hello_controller.rb
    skylight.io

    View Slide

  43. View Slide

  44. View Slide