Ruby Rack Middleware Tutorial


So you discovered Ruby's Rack 'web framework framework', and would like to know how to extend it, this is done using Rack middleware.

Basic Rack Application

The tiny example below shows that a Rack app is simply any object that responds to the call method, which returns an http status code, headers, and body. Middleware can be utilized via the use method which, in turn autoload's a Ruby file containing the middleware. Autoloading of middleware is simply a Rack convention, but helps to keep things clean

use Rack::ContentLength

app = lambda { |env| [200, { 'Content-Type' => 'text/html' }, 'Hello World'] }
run app

Running The Application

Save the code above in a file named 'basic_rack.ru', the *.ru extension corresponds to Rack's rackup executable. These files are regular Ruby files.

Start the app using the following command, then visit http://0.0.0.0:3000/

$ rackup basic_rack.ru -p 3000

Middleware Ordering

Your application is called, and then middleware is invoked in the order that you specify. These middlewares call each other, acting as a set of 'filters' for the response, so it is important to note that the ordering to which you 'use' them can be important, and have an effect on the results.

The following example illustrates that the body contents can be altered by several middleware's:

module Rack
  class Upcase
    def initialize app
      @app = app
    end
   
    def call env
      puts 'upcase'
      p @app
      puts
      status, headers, body = @app.call env
      [status, headers, [body.first.upcase]]
    end
  end
end

module Rack
  class Reverse
    def initialize app
      @app = app
    end
   
    def call env
      puts 'reverse'
      p @app
      puts
      status, headers, body = @app.call env
      [status, headers, [body.first.reverse]]
    end
  end
end

use Rack::Upcase
use Rack::Reverse
use Rack::ContentLength

app = lambda { |env| [200, { 'Content-Type' => 'text/html' }, 'Hello World'] }
run app

As you can see in the terminal output, our Upcase object's application is actually the Reverse middleware, and Reverse's application is the next one in line which happens to be ContentLength.

upcase
#<Rack::Reverse:0x5574e0 @app=#<Rack::ContentLength:0x557620 @app=#<Proc:0x00561210@rack.ru:38>>>

reverse
#<Rack::ContentLength:0x557620 @app=#<Proc:0x00561210@rack.ru:38>>

Realworld Example of JSON Middleware

Below is a very simple realworld example which translates the body results to json when the response Content-Type header is that of json (application/json). This sort of middleware can really clean up your app, help others, and well really this is where this sort of functionality belongs!

require 'json'

module Rack
  class JSON
    def initialize app
      @app = app
    end
   
    def call env
      @status, @headers, @body = @app.call env
      @body = ::JSON.generate @body if json_response?
      [@status, @headers, @body]
    end
   
    def json_response?
      @headers['Content-Type'] == 'application/json'
    end
  end
end

use Rack::ContentLength
use Rack::JSON

app = lambda { |env| [200, { 'Content-Type' => 'application/json' }, { :some => 'json', :stuff => ['here'] } ] }
run app

Additional Information

This article has some good examples of URL mapping with Rack, as well as explicitly using the adapter of your choice instead of using rackup: http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder

Comments

Thanks for the tutorial. As newbie to Rack, very hard to figure out without this sort of page.

There's a Rack coding contest starting now if you want to enter. It's at http://www.coderack.org

Some great prizes and it promises to be a lot of fun.

Nice explanation!