Most of you probably know what Rack does but here’s a quick reminder :
Rack’s a web server interface powering most of Ruby’s web frameworks (Rails, Sinatra…) with a nice little protocol which gives you the possibility to manipulate the incoming http Request hash and the outgoing Responses in nicely pilled up stack of “Middlewares”.
For example if you bin/rake middleware
a Rails app you get this :
# http request hash
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
# ... loooong list
use Rack::ETag
run Bookstore::Application.routes
# Bookstore response
so…
- The Request you send from the browser gets processed in
Rack:Sendfile
all the way down toBookstore::Application.routes
- Then the Response of
Bookstore
is similarly processed all the way up back toRack::Sendfile
and in your your browser.
Ok now that we have the basics how does this actually work in pure ruby terms ? Because the concept is sure easy to grasp but does it need plenty of magick to be used ? Or can it be easily reproduced in a different context ?
Well let me first create my own little middleware stack.
My own little Rack stack
As you can see below, to comply with Rack’s protocol, each middleware has a #call(env)
method which takes the request hash (aka: env). It also initialize itself with the next middleware in the stack :
class Hello
def initialize(app)
@app = app
end
def call(env)
env, body = @app.call(env + "hello request, ")
[env, body + "have nice time on firefox!"]
end
end
class Welcome
def initialize(app)
@app = app
end
def call(env)
env, body = @app.call(env + "welcome on the server...")
[env, body + "it was nice seeing you, "]
end
end
class DoNothing
def initialize(app) @app = app; end
def call(env) @app.call(env); end
end
app = lambda { |env| [env, "app says: "]}
#...
Rack’s DSL (as seen with the Rails middlewares) will use it like this :
use Hello
use Welcome
use Donothing
run app
Which is just some syntactic sugar for :
donothing = DoNothing.new app
welcome = Welcome.new donothing
hello = Hello.new welcome
puts hello.call("env says: ")
# OUTPUT
# => env says: hello request, welcome on the server...
# => app says: it was nice seeing you, have nice time on firefox!
As you see the order is inverted in Rack’s DSL to reflect the actual order in which every middleware processes (calls) the request : from top to bottom.
And that’s it ! Super easy !
Another way of seeing it is like this :
Hello.new( Welcome.new( DoNothing.new( app ))).call("env says: ")
It’s interesting to note at this stage that this is conceptually close to a well known pattern which wraps methods at runtime : the Decorator Pattern.
And indeed it’s not difficult to take another step and consider Rack middleware as a general purpose abstraction.