Jan 29 2008

Rack RESTful Dispatcher

Quoting the authors, Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call .

To test the theory in practice, I put together a superficial interface for writing RESTful HTTP services by implementing any of the DELETE, GET, POST, PUT, etc HTTP verbs in Ruby classes.

require "rubygems"
require "rack"

module RestfulDispatcher
  def call(env)
    request = Rack::Request.new(env)
    dispatcher = dispatcher_class.new(request)
    body = dispatcher.send(request.request_method.downcase)
    [200, {'Content-Type' => dispatcher.content_type}, body]
  end

  def dispatcher_class
    @dispatcher ||= Class.new(self.class) do
      attr_accessor :content_type

      def initialize(request)
        @request, @content_type = request, 'text/xml'
      end
    end
  end

  module SingletonMethods
    def start(handler, host, port)
      handler.run Rack::Lint.new(self.new), :Host => host, :Port => port
    end
  end

  def self.included(receiver)
    receiver.extend SingletonMethods
  end
end

Let's examine the example one method at a time.

All Rack applications must implement one method - call - which accepts one argument, the environment, which encapsulates data relevant to the HTTP roundtrip. The call method must return an array of 3 items: a greater than 100 integer representation of the response status, a hash holding name/value pairs representing the response's header entries and an array of strings - the body of the response.

In the case of the RestfulDispatcher module, call first wraps env in Rack::Request, a convenient and stateless interface to the Rack environment. We then create a new instance of a dispatcher class passing it the Rack Request. We will revisit this in more detail when talking about the dispatcher_class method. Instantiating a new dispatcher to handle the request should keep things thread safe. We then call a method on the dispatcher instance corresponding to the HTTP verb included in the request. This call should return the response body. Finally, adhering to the Rack standard, we return an array containing the response status code, headers and body.

dispatcher_class creates a new class by subclassing the service we will be defining and giving it a constructor that accepts a request object. We also expose the content_type field, in case we want to override it anywhere in our service's implementation.

Finally, we provide a start singleton method which we can call to start the service.

Mixing the RestfulDispatcher module in a class will effectively enable the class to act as a standalone RESTful service. All we need to do is implement instance methods that correspond to the HTTP verbs we want the service to respond to.

class FooService
  include RestfulDispatcher

  def get
    "<test></test>"
  end
end

require "thin"
FooService.start Rack::Handler::Thin, '127.0.0.1', 2323

Rack::Request#GET conveniently returns the data received in the request query string (e.g http://127.0.0.1/?key=hello) as a hash.

Thanks to Rack's modular nature, switching from Thin to Mongrel is as easy as replacing the last two lines of the code above with:

require "mongrel"
FooService.start Rack::Handler::Mongrel, '127.0.0.1', 2323