Tracing the lifecycle of a Rails run

by msypniewski511 in Ruby for Rails

Ruby for Rails: Ruby Techniques for Rails Developers

Overall

Stage 1: Web server to dispatcher.

The Web server—Apache, light TPD , or whatever it may be on a given system—
receives the request from the browser. The server configuration causes the server
to pass the request along to what will turn out to be a Rails application. The server
doesn’t know what Rails is; it just does whatever redirecting or deflecting of the
incoming request it’s set up to do.

Stage 2: dispatcher to controller.

The dispatcher’s job is to dispatch the request—that is, to send it to the appropriate controller.
Controllers are the subprograms in a Rails application that per-
form tasks. They reach back into the database and get data, they search and sort,
they test for password matches, and so forth. Typically, a Rails application has sev-
eral controllers, and each controller is capable of multiple actions. For example,
you may have a customer controller that can perform login, logout, edit (edit pro-
file), and other actions.

Stage 3: performance of a controller action

When the appropriate action, inside the appropriate controller, is executed, it has
automatic access to the following:

  • CGI data, including data from a submitted form (via the built-in params method)
  • The controller’s session information (via the built-in session method)

The controller action also has access to its own session information. Rails
applications can cache information from one invocation to another. This can be
handy, for instance, for enabling customers to navigate a site without having to log
in every time they go to a different part of the site. The login status is maintained
in the session cache and checked for validity.

Stage 4: the fulfilment of the view

You’re now on the downslope of the process. The rest of the controller’s job is to
pass the data to the view. The view fills in its template, resulting in an HTML docu-
ment that is then handed to the Web server and from there back to the original
Web client.

The Lifecycle of a Request

Establishing a connection between client and server.

Server

Parse and "understand" the request, and make a decision on how to service that request.
For simple requests, like service static assets, you can just configure the webserver to do that.
In more complicated scenarios server hand these requests off to Rails for further processing using RACK.

First the web server prepares a hash, which is conventionally called the "env hash". The env hash
contains all the information from the HTTP request – for example, REQUEST_METHOD contains
the HTTP verb, PATH_INFO contains the request path and HTTP_* has the corresponding
header values.

On the other hand, the "app" or framework must implement a #call method. The server will expect
it to be there and invoke it with the env hash as the only argument. It is expected to handle the request
based on the information in the env hash and return an array with exactly three
things in it (a.k.a. "a tuple of three").

HTTP status code,
hash containing the response headers,
array of response body

Rack app

Simplest app implementing rack specification

# app.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
# config.ru

require_relative 'app'

run HelloWorld.new
$ rackup

With middleware implementing basic route service

# app.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
# config.ru
require_relative 'app'

run Redirect.new(
  HelloWorld.new,
  from: '/',
  to: '/hello'
)
# config.ru
require_relative 'app'

use Redirect, from: '/', to: '/hello'

run HelloWorld.new

Rails

# config.ru
require_relative 'config/environment'

run Rails.application

Rails us different way to register middleware. Instead in config.ru we use $ rails middleware command to see middlewares.

➜  r4music1 git:(feature) ✗ rails middleware
use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActionDispatch::ServerTiming
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Sprockets::Rails::QuietAssets
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::ActionableExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use ActionDispatch::PermissionsPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
run R4music1::Application.routes

In case of controlling which middleware should be remove use conf/application.rb

# config/application.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

So according to rails middleware output we know that rack app in this case is R4music1::Application.routes.
This Rack app looks at the request URL, matches it against a bunch of routing rules to find the right controller/action to call. Rails generates this app for you by Rails based on your config/routes.rb.

# config/routes.rb

Rails.application.routes.draw do
  resources :posts
end

And finally Action Controller is rack app itself.
Finally, putting everything together, you can imagine the routes app is a rack app that looks something like this:

class R4music1Routes
  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

It matches the given request path and http verb against the rules defined in your routes config, and delegates to the appropriate Rack app on the controllers.

0 Replies


Leave a replay

To replay you need to login. Don't have an account? Sign up for one.