Rack RESTful Dispatcher
Tuesday, January 29th, 2008
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>#{@request.GET['key']}</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
