What is Rack? A Ruby on Rails Webserver Interface

Krupa Suthar
Krupa Suthar
04 Jun, 20194 min read
What is Rack? A Ruby on Rails Webserver Interface

As a Ruby on Rails developer at SoluteLabs, when I came across Rack application in one of our projects it encouraged me to dig deeper into insides of Rack and helped me to find the answers of below questions,

  1. What is the Rack application?
  2. What is Rack middleware?
  3. Where it is used?

What is Rack?

As per Rack, it provides a minimal interface between web servers that support Ruby and Ruby frameworks.

In simple terms, Rack sits between the ruby based application and web server helping them to interact with each other.

Creating Rack application

Rack application is nothing but the object that responds to the call method.

class Hello
  def call(env)
    [200, {"Content-Type" => "text/html"}, ["Hello there!"]]
  end
end

run Hello.new

To run our newly created application invoke rackup config.ru command. Now go to http://localhost:9292/ and our application will be available there.

When we hit the above URL, in server run command will be invoked and it will call method call on Hello.new object.

If you look at the call method, it accepts the environment hash which contains information about the incoming HTTP request and the environment in which application is running as an argument and returns an array of exactly three values: The status, headers, and body.

Now let’s take look at few of Rack’s terms:

run

It takes Ruby object which responds to method call as an argument and runs call method on it.

map

It takes a string as a parameter and maps incoming requests against it and if it matches it will run the block assign to handle that.

Use

It includes the middleware in rack application.

What is Rack Middleware?

Middleware

You can refer rack middleware as a small component that assists with the execution of a task. For example, You can think of different middleware doing different processes like logging, caching.

It can modify or halt requests before they reach the final middleware(The object which we assigned to run method).

Middleware Stack

When you include more than one middleware it is called middleware stack. Its implementation is similar to pipeline design pattern for a web server that uses Rack. It means every incoming request passes through each middleware in the same order as they are defined.

Built-in middlewares

There are many already built-in middleware components in rack available to use in your Rack application.

Creating custom Middleware and using it in Rack Application

Let’s create our own custom middleware Cookie which will add cookies to response.

class Cookie
  def initialize(app, expiry)
    @app = app
    @expiry = expiry
  end

  def call(env)
    status, headers, body = @app.call(env)
    headers = headers.dup
    opts = {
        expires: Time.now + @expiry,
        value: 'value',
        path: '/',
    }
    Rack::Utils.set_cookie_header!(headers, "mycookie", opts)
    [ status, headers, body ]
  end
end

First, starting from constructor you may have noticed the app parameter in it. The app is next application middleware and every middleware constructor must have to include it.

This middleware calls @app.call(call) first which will call the next middleware in the stack(In our case it is Hello) and return its response. After that Cookie middleware will add cookies to respond and return the response back to the application.

require './cookie'
class Hello
  def call(env)
    [200, {"Content-Type" => "text/html"}, ["Hello there!"]]
  end
end

use Cookie, 24 * 60 * 60 # one day

run Hello.new

As explained above use command is used for adding middleware in the stack, we can use Cookies middleware in our Rack application as above.

Rails and Rack

If rails project directory if you run rake middleware it will give the list of middlewares being used in rails as below,

use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
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::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
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 RailsRakeDemoApp::Application.routes

Every incoming request to your app will pass through this stack from up to bottom and finally giving its control to routes which dispatch request to the controller, controller process it and returns the response. This response will again pass through this middleware stack from bottom to up.

But how these middlewares are being invoked and where they are defined?

Every Ruby on Rails project have a config.ru file in its root directory and it has following content,

# This file is used by Rack-based servers to start the application.

require_relative 'config/environment'

run Rails.application

This file loads config/environment files and invokes Rails.application which will call Application defined in application.rb.

Now let’s look at what does application.rb have,

require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module DemoApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

Here Application is inherited from Rails::Application which is inherited from Engine. This Engine is a Rack application that has a call method defined in it which is being invoked when config.ru calls Rails.application. This is how Ruby on Rails application uses Rack.

Rails::Application class loads DefaultMiddlewareStack which have the same middleware stack defined in it which we went through earlier.

How can I use it?

Rails allow you to configure middleware stack for adding your custom middleware to stack, deleting middleware from the stack and, changing the position of any middleware from the stack.

Have a product idea?

Talk to our experts to see how you can turn it
into an engaging, sustainable digital product.

SCHEDULE A DISCOVERY MEETING