dingus

/ˈdɪŋəs/ Something whose name is unknown or forgotten

Rack Cheatsheet

July 2022

Rack interface

Middleware

Middleware is the atom of Rack Land. It’ has a very simple interface! It’s #initialize must accept a single argument, this is the next middleware in the stack. The current middleware should send #call to this middleware in it’s own #call method.

Middleware must also respond to #call, which must accept a single argument and must return the “magic triple”. The argument passed is the Rack “environment hash”, it contains information about the HTTP request and Rack-specific variables1. The magic triple is broken down below.

[
  200,                              # HTTP Status Code, must be an `Integer`
  {"Content-Type" => "text/plain"}, # Headers, must be a `Hash`, but can be empty
  ["Hello world."]                  # Body, must respond to `#each`
]

Applications

Applications are just a special case middleware. Because they’re at the bottom of the stack they do not need to implement #initalize, they must respond to #call.

Rack-up DSL

The entry point to Rack! Normally added to a config.ru file and booted with a sever with a Rack handler build in (like Puma).

Running an application

Using a proc:

run proc {
  [200, {"Content-Type" => "text/plain"}, ["Hello world."]]
}

Using a class:

class HelloWorld
  def call(_env)
    [200, {"Content-Type" => "text/plain"}, ["Hello world."]]
  end
end

run HelloWorld.new

Adding middleware

Without configuration

use Rack::ShowExceptions

With configuration

use Rack::Auth::Basic, "Rack Cheatsheet" do |username, password|
  OpenSSL.secure_compare('secret', password)
end

Two applications, one config.ru

APP_1 = proc {
  [301, {"Location" => "/hello-world"}, []]
}

APP_2 = proc {
  [200, {"Content-Type" => "text/plain"}, ["Hello world."]]
}

map "/hello-world" do
  run APP_2
end

run APP_1

Warming up applications on boot

warmup do |app|
  client = Rack::MockRequest.new(app)
  client.get('/warm-cache')
end

Request handing

Using the environment hash directly can be a little unwieldy, Rack::Request is a helpful wrapper to make it more ergonomic.

run lambda { |env|
  request = Rack::Request.new(env)

  unless request.scheme == 'https'
    return [301, {"Location" => "https://#{request.host}#{request.fullpath}"}, []]
  end

  if request.get?
    [200, {"Content-Type" => "text/plain"}, ["Secure hello world."]]
  else
    [405, {}, []]
  end
}

The response object

A convenience class for generating responses, calling #to_a or #finish will return a magic triple.

run lambda { |_env|
  response = Rack::Response.new

  response.write "<p>"

  case Time.now.hour
  when 0..11
    response.write "Good morning!"
  when 12..17
    response.write "Good afternoon!"
  when 18..23
    response.write "Good evening!"
  end

  response.write " <time>It's #{Time.now.strftime("%l:%M %P")}</time>.</p>"

  response.finish
}