Rack middleware allows to work with HTTP requests on lower level than many of the frameworks allow, though it is not a really low level and represented by the same Ruby objects we used to work with. The Rack itself can be considered as basically a wrapper toolkit around the Ruby’s Net::HTTP library. So, the Rack middleware can be used to perform, for example, the following set of tasks:
In this post, I will try to describe using a middleware for enhanced request logging(actually, not so enhanced). I think that in real-world conditions this scenario is unlikely because such a middleware would create additional disk i/o and therefore slow down the request/response cycle. But for the understanding of Rack and middlewares, it is ok.
Let’s start with a bare minimum rack application, that only consists of 5 lines of code:
rackup, and the server will start listening at port 9292, serving empty 200 response for all requests.
Now let’s add an empty middleware:
Launching application will now result in
wrong number of arguments (given 1, expected 0) (ArgumentError)
Let’s add explicit
initialize definition in our middleware class:
Now the application starts, but visiting localhost:9292 will raise something like
NoMethodError at / undefined method `call' for #<RackLogger:0x005619c2c17cf8>
call method definition will result in
ArgumentError: wrong number of arguments (given 1, expected 0)
So, I am tired of this guessing approach and now will take a look at a typical middleware code, for example, Rack::Deflater From the code we can see the typical method signatures we need:
After this, our requests are successfully processed by our no-op middleware and the application works again.
Now, to actually log something, we can use the env hash, provided as the first argument to
For example, keys names
QUERY_STRING can provide interesting information for logging.
By the way, all of this info has already been outputted by Rack’s default logger, so we can use them to somehow decorate
output and write it to the filesystem.
Now, visiting the
http://localhost:9292/posts?by_date=today URL will result in something like:
Which is obviously something easily consumable by CSV library.
Now we can add actual filesystem writing:
The middleware is working and does what expected. To make the code more reusable, we should
add a configuration layer. Let’s do it the traditional way, via
And rewrite RackLogger class as following:
All we have left to do is to add the Configuration class itself:
For real-world usage, all these also should be wrapped in module namespace, but this is beyond the scope of the article.
To learn more about middlewares, you can dig directly to the rack codebase, which contains a number of middlewares, which you probably have been implicitly using on an everyday basis, when writing web applications in some of Ruby frameworks.