Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks.
Our first project in the Code Reading group was Rack. At first I thought it was going to be a complex project to go through but Rack’s logic is really simple. This is an overview of how it actually works.
Indeed, the Rack API is as simple as it gets:
A Rack application is any Ruby object that responds to
call. It takes exactly one argument, the environment hash, and returns an Array of exactly three values: The status, the headers, and the body.
Based on this, a simple Rack app could be this:
We can run the app by saving this class to a file named
config.ru and use the
rackup tool that Rack provides for easily running Rack apps.
#use are two of the methods that the Rack DSL provides:
This will boot Thin (or WEBrick if you don’t have Thin installed). You can override the server if you want. Point to http://0.0.0.0:9292 to see your app.
It gets simpler! Lambdas also respond to
call (since they’re Proc objects), so in our rackup file we could just do:
builder.rb implements the simple DSL we can use to add Middlewares to the Stack. Now we can use
use method to add Middlewares to the stack.
We can see in our browser that this adds the headers. The important thing here is to understand how the Middleware stack works. In our case, the Proc is the endpoint -our main application- and above it sits our new Middleware.
If we take a look at
builder.rb (which is a key file to understanding the stack) we can see that
#use along with
#to_app builds an array of Procs that each take an app and builds another app out of it, by prepending a Middleware to it (a Rack app with a Middleware in front of it is a Rack app). Note Lines 25 & Line 31 which do the trick:
So if we had an imaginary stack like this
then the server in response to a request from a client, would go through the stack in reverse order. The first Middleware, “YoMiddleware” gets initialized with “OurApp”, thus creating a Rack app. Then “AnotherMiddleware” gets initialized with that Rack app that was just created and the same process is repeated for next Middlewares till the stack is finished.
It could look something like this:
Each Middleware in the stack knows the next one but nothing else. Control is passed by calling the next Middleware. Middlewares can run before or after an application, it depends on where the Middleware wants to work.
The real power comes when you add, remove, move Middlewares to the stack at will and the remaining Middlewares can not care less. The implementation is so simple yet clever that an endpoint looks exactly the same as an endpoint with a middleware in front of it.
Our next project in Code Reading will propably be Sinatra. Stay tuned.