Rack Middleware - Deep Details Step by Step


Rack middleware is a hot topic these days and anyone who is reading Ruby-related blog sites must have heard about it already. However, if you didn’t, here’s a short quote from the Rack site:

  • Rack provides an minimal interface between webservers supporting Ruby and Ruby frameworks.
  • During last couple of years, Rack has became de facto standard in Ruby web development world, providing unified and and simple interface for frameworks creators. Today, Ruby on RailsRamazeSinatra and many others use it by default to talk to web servers, including MongrelThin or Apache via Passenger.



Brief history

CGI was first widely used method of running Ruby scripts on server-side. You could run it like shell, perl or python scripts inside Apache using special module. Apache and FastCGI was preffered method of deploying Rails applications, until it was replaced by solutions using HTTP proxy and a “cluster” of dedicated Ruby servers (e.g. Mongrel). When Phusion Passenger (mod_rails) arrived and started supporting Rack – we had clear winner: Rack dominated Ruby web development world.

Middleware

Rack is more than interface that can be used to talk to web server. It’s used to group and order modules, which are usually Ruby classes, and specify dependency between them. Rack::Builder puts these modules on top of each other, creating stack-like structure of final web application.
If you are using Ruby on Rails, it is usually used as top-level module. In fact, Rails frameworks creates a few own modules for dispatching requests, sessions handling or parsing parameters. For more information look into new Rails on Rack guide.
What is really interesting is Rack’s simple architecture. Rack middleware module must only: – have constructor that takes next application in stack as parameter
– respond to “call” method, that takes environment hash as a parameter. Returning value from this call is an array of: status code, environment hash and response body.

Installation

$ gem install rack 

First steps

Now that we have Rack installed, we want to try how it works. Rack comes with little funny web application called “Lobster”, that can be used as a demo.
Rack use configuration file with extension .ru, that instructs Rack::Builder what middleware should it use and in which order. Let’s create one:
# config.ru
require 'rack'
require 'rack/lobster'
run Rack::Lobster.new
To start your newly created app, you need to use “rackup” command:
$ rackup config.ru 
Application will be available by default on port 9292, so you need to go to http://localhost:9292 to see it. It’s really a lobster!
To get an idea on how lobster is rendered, have a look at it’s source (/usr/lib/ruby/gems/1.8/gems/rack-0.9.1/lib/rack/lobster.rb in my installation).

Building your own middleware module

We can place our own midleware before Rack::Lobster using config.ru file we just created. Simpliest thing that could possibly work is just calling Lobster and returning it’s response. Let’s call our middleware a Shrimp.
# shrimp.rb
class Shrimp
  def initialize(app)
    @app = app
  end
  def call(env)     
   @app.call(env) 
 end
end
# config.ru
use Shrimp          
require 'rack'
require 'rack/lobster'
require 'shrimp'     
run Rack::Lobster.new
We run our it by “rackup config.ru”.
OK, let’s do something more “useful”. Say, we need to render a shrimp each time anyone calls a lobster…
# shrimp.rb
class Shrimp
 SHRIMP_STRING = <<BEGIN
 |///
 .*----___// <-- it was supposed to be a walking shrimp...
 <----/|/|/|
 BEGIN
  def initialize(app)
   @app = app
  end
 end

 def call(env)
   puts SHRIMP_STRING
   @app.call(env)
 end
Start a server, go to see a lobster in your browser and look into server output… there is a shrimp! Neat! ;)
But rendering to standard output is not everything you can do. You could want to add a shrimp to response body, after the actual lobster! Why not?
# shrimp.rb
class Shrimp
 SHRIMP_STRING = <<BEGIN
 |///
 .*----___// <-- it was supposed to be a walking shrimp...
 <----/|/|/|

 BEGIN
   def initialize(app)
    @app = app
   end

  status, headers, response = @app.call(env)

  def call(env)
    puts SHRIMP_STRING
    response_body = ""
    response_body += "<pre>#{SHRIMP_STRING}</pre>"
    response.each { |part| response_body += part }
    headers["Content-Length"] = response_body.length.to_s
  end

  [status, headers, response_body]
 end
Please note we did two extra things here. First thing is that response from called app, doesn’t always need to be a String. Rack specification says it mustonly response to “each”, so we use block to get response body.
Second thing is updating headers. HTTP protocol requires that “Content-Length” must specify overall length of returned response – so we update this value because we added our shrimp string there.

Using Rack middleware with Rails

This is described in details in Rails on Rack guide guide, so only brief description here.
Place your middleware module to lib/shrimp.rb and add “config.middleware.use ‘Shrimp’” to your environment.rb.

More reading

Comments

Post a Comment