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 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
  2. 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
  3. 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???
  4. 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
  5. 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.
  6. 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 # => #<Headers @host=...> ) # => #<Response @status=200 @body=...> 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'
  7. 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”.
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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)
  15. 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('') <!DOCTYPE html> <html> <head><title>My Blorgh</title></head> <body> <h1>Rails is Omakase</h1> <p>There are lots of à la carte software environments in...</p> </body> </html>
  16. 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
  17. 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
  18. 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
  19. 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('') <!DOCTYPE html> <html> <head><title>My Blorgh</title></head> <body> <h1>Rails is Omakase</h1> <p>There are lots of à la carte software environments in...</p> </body> </html>
  20. 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
  21. 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)
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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