Middleware is a very clever way of chaining our request through various different pieces of ruby code, but in a very modular way. It comes from Rack, and is not a formal standard, but is widely accepted and supported by much of the Ruby community especially by both servers and web frameworks. The original ideas are all based off Python’s WSGI Middleware.
Ruby On Rails has supported Rack since version 2.3, but improvements scheduled for Rails 3 should make Rails a much more friendly rack candidate.
A Middleware is a simple class instance that has two methods (at least), initialize and call. It is used to alter a HTTP request to and the response from your main Rack-compliant web application. It is a form of proxy but with code rather than separate software. A set of middleware and their respective endpoints are known as a Stack.
A Middleware Stack
An Endpoint is again a simple ruby class that only responds to call. Thus it is so similar to a middleware that in fact a class can be one and the same thing, if it wants to return a response instead of letting it cascade through to the main endpoint.
Almost the simplest middleware possible is the following:
class SimpleMiddleware
def initialize(app, options={})
@app = app
@options = options
end
def call(env)
# downward logic
status, headers, response = @app.call(env)
# upward logic
[status, headers, response]
end
end
Here, you can see the major features of a middleware, and indeed what makes it conform.
Firstly, the initialize method, which accepts two arguments. The first is a rack endpoint, and the second is some options that will be required later for some logic.
The other important feature is the call method, which accepts a hash of environment variables sent from the middleware further out. It also returns an array consisting of three things: the status, a hash of headers and a response body.
Simple eh?
As an example, here is a basic (imaginary) middleware stack:
# Top of the chain: the Listening Server
use SecondSimpleMiddleware # Inherits from SimpleMiddleware
use SimpleMiddleware
run ActionController::Routing::Routes
# Bottom of the chain: Your App
At initialization, the server goes through the list of middleware (shown by use in the list above. run is the endpoint) in reverse order (from the bottom upwards). The first middleware, SimpleMiddleware, gets initialized with the endpoint, ActionController::Routing::Routes, thus creating a rack application. Then SecondSimpleMiddlware gets initialised with the rack application created by initialising the SimpleMiddleware, which in turn creates another rack application to hand to the listening server. This all happens in a method called build_app which I will discuss later.
When a HTTP request comes in from a client, the server calls SecondSimpleMiddleware, which executes its downward logic, then calls SimpleMiddleware. SimpleMiddleware in turn executes it’s own downward logic, and calls ActionController::Routing::Routes, which executes your applications logic.
Cleverly, ActionController::Routing::Routes returns an array containing 3 important elements. The first is the http status, as an integer. The second is a hash of headers to send back in the response. The last is the response body, which should respond to each.
This array gets returned to SimpleMiddleware, which performs its upward logic before returning an array in the exact same format to SecondSimpleMiddleware. Then SecondSimpleMiddleware performs its upward logic, before returning an array of the exact same format back to the listening server, which formats it as a HTTP Response and sends it to the client.
A Request Through The Stack
I said before that a middleware can respond as an endpoint too. This is rather simple in fact. I shall have examples of this later in the series, but for the moment it is enough to realise that you can replace the statement containing @app.call(env) with some logic that just responds with the required 3 element array. To show you how this might work, here is a snippet:
class EarlyResponderMiddleware
def initialize(app, options={})
@app = app
@options = options
end
def call(env)
if env['path'] == "/foo"
body = "Not Found"
[404, {"Content-Length"=> body.length}, body]
else
@app.call(env)
end
end
end
And the following is what would happen if the path matched “/foo”:
A Request through the stack caught early and responded to without hitting your Application at all.
The beauty of this implementation is that middlewares can be added, moved and removed from this stack at will, without any other middlewares giving a damn. The exact implementation has been made so simple yet clever that an endpoint actually looks exactly the same as an endpoint with a middleware in front of it.
The following is the build_app method I mentioned earlier, which Rack calls to construct its app object to hand to a server.
def build_app(app)
middleware[options[:environment]].reverse_each do |middleware|
middleware = middleware.call(self) if middleware.respond_to?(:call)
next unless middleware
klass = middleware.shift
app = klass.new(app, *middleware)
end
app
end
This method takes the list of middleware that rails gives it, and then starts building, but it builds it from the bottom. It starts with the last middleware, and initializes that with the Rails application endpoint. It then iterates over the next middlewares, slowly layering them on top. Finally it returns the app. (The first two lines let you do fun things with Procs to define the middleware, and make sure if you are doing that, it actually works)
This means that SimpleMiddleware knows nothing about the middleware that called it, it only knows that there is a rack application beneath it that it should call when it gets a request. This is why we initialize it from the bottom upwards.
So far, everything I’ve said about middleware is actually only about Rack middleware.
In Rails 3, the default middleware stack is:
use ActionDispatch::Static # only development # part two
use Rack::Lock
use ActiveSupport::Cache::Strategy::LocalCache
use Rack::Runtime
use Rails::Rack::Logger # part three
use ActionDispatch::ShowExceptions
use ActionDispatch::RemoteIp
use Rack::Sendfile
use ActionDispatch::Callbacks # part four
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore # part five
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::MethodOverride
use ActionDispatch::Head
run Test::Application.routes # part six
This stack is very easy to alter using the config.middleware methods. They allow you to insert middleware anywhere in the stack. More information can be found in the “Rails on Rack” Rails Guide. You’ll also find a lot more API centric information about middleware in that article, whereas my articles are looking more at the implementation.