The Evil calls back
Published over 5 years ago

Happy new year!

Just an update on the two middlewares I contributed to rack-contrib. In case you don’t already know what is rack-contrib, it’s a project started by Ryan Tomayko as a playground for experimental Rack middlewares from the ruby community. There are 15 middlewares there already, so do check it out!

Rack::Evil

The name says it all. It’s pure evil, as it enables the rack application to return the response from any place during while it’s serving the request by doing throw :response, [status, header, body].

Example:

app = lambda do |env|
  template = <<-TEMPLATE
  Hello Template
  <%= throw :response, [404, {'Content-Type' => 'text/html'}, 'From Template'] %>
  Never
  TEMPLATE
  result = ERB.new(template).result(binding)

  [200, {'Content-Type' => 'text/plain'}, "Ran the template"]
end

And the response reaching the client will not be “Ran the template” with 200 status. But it’ll be “From Template” with 404 status!

Rack::Callbacks

Now that more people are getting on board with the concept of middlewares, people have started using middlewares for pure before/after filter kind of things. And I think don’t think that’s the best way moving forward. That’s the problem Rack::Callbacks tries to alleviate.

Rack::Callbacks lets you wrap your rack application with a series of before and after callbacks.

Example :

class TimeZone
  def initialize(default)
    @default = default
  end

  def call(env)
    env['rack.timezone'] = find_timezone(env) || @default
  end
end

class CompressBody
  def call(response)
    status, headers, body = response

    compressed_body = zip_body(body)
    [status, headers, compressed_body]
  end
end

app = Rack::Callbacks.new do
  before TimeZone, "default"

  run YourPrimaryRackApp.new

  after CompressBody
end

The flow of execution is very apparent here. All the before callbacks are run first, then the actual application and after callbacks at last.

before Callback, arg1, arg2….argx

before() takes Callback class as the first argument. Optional arguments are passed to Callback#initialize. This is very similar to the regular rack middlewares. However before callbacks are quite different from middlewares.

Callback#call accepts a single argument : env. However, the return value of Callback#call is simply discarded.

run Application

Runs the primary rack application.

after Callback, arg1, arg2….argx

after() takes Callback class as the first argument. Optional arguments are passed to Callback#initialize. Just like before() and middlewares. However, after callbacks are substantially different from before callbacks and middlewares:

Callback#call accepts a single argument : response, which is the rack response returned by the application or other after filters. Callback#call also must return a valid rack response. Return value of Callback#call is supplied to the next after filters in the stack, and the final after filter returns the response to the client.