A simple Ruby Rack router

Motivation

  • I want to be able to handle http requests.  I like the simplicity of Rack, but I want more control over the initial mapping.  I like Sinatra, but I want to easily handle request methods other than GET, PUT, POST, DELETE, and HEAD. So, the Router class below maps regular expressions to handler functions.

Requirements/Environment

  • Mac os x 10.5.8
  • Ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]
  • Rack gem version 1.1.0

Code

class Router
  def initialize(routes)    
    @routes = routes
  end
  def default
    [ 404, {'Content-Type' => 'text/plain'}, 'file not found' ]
  end
  def call(env)
    @routes.each do |route|
      match = env['REQUEST_PATH'].match(route[:pattern])
      if match
        return route[:controller].call( env, match )
      end
    end
    default
  end
end

Usage

# assumes router code is in router.rb
require 'router'

use Rack::CommonLogger
use Rack::ShowExceptions
use Rack::Lint
use Rack::Static, :urls => ["/static"]

run Router.new([
  {
    :pattern => %r{^/page1$}, 
    :controller => lambda do |env, match|
    
      [ 200, {'Content-Type' => 'text/html'}, 'page 1' ]

    end
  },
  {
    :pattern => %r{^/}, 
    :controller => lambda do |env, match|
    
      [ 200, {'Content-Type' => 'text/html'}, 'index!' ]
  
    end
  }
  
]);

Run it on the command line using Rack’s native Rackup ($ rackup config.ru) or via Ryan Tomayko’s shotgun, which conveniently auto-reloads.

Reference

Update Mar 5, 2011

  • because Ruby < 1.9 doesn't preserve ordering in hashes, I’ve updated the code to use an array of routes. It’s more verbose, but now the route matching correctly runs top to bottom.
  • I also added a method for overriding the default response from the router

Update Mar 9, 2011