The Evil calls back
Published over 4 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!
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!
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() 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.
Runs the primary rack application.
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.