Roll Your Own Web Framework
In my last post we talked about a variety of Ruby web app frameworks. If you're disgusted by the recent proliferation of options (and who doesn't hate options?), you can blame Rack, as it provides the foundation for many of these upstarts.
Rack is to Ruby as WSGI is to Python; a clean, minimalist interface between web frameworks and web app servers. What this means to you, Joe Average Ruby Developer, is that you can use Rack to build your own frameworks and tools with ease, and then deploy them without writing server specific interfaces for Mongrel, WEBrick, or FastCGI (or Thin, Ebb, and Fuzed!). It'll just work.
Why would you want to build your own web framework? That's certainly a good question. There are lots of choices out there now, and there's a good chance that one of them fits your needs. On the other hand, perhaps you have a specific requirement or an idea for a wholly different approach, or maybe you just want a better understanding of how the existing solutions work. You can also use Rack to implement fast lightweight request processing tools, for when you don't need the overhead of a more full-featured framework. So it's definitely worth learning about.
Anyway, the beauty of Rack is that it basically distills the interface between web frameworks and servers down to a single method, #call. This method must be implemented in any web framework or application you want to build on Rack. The #call method always receives a hash of environment details as an argument and needs to return an array containing a status, headers, and a response body. Rack makes this even easier on us by providing convenient helpers like Rack::Request and Rack::Response.
The following (rack_test_app.rb) is a super-simple Rack application. To execute it, you'll need to have both Rack and Mongrel installed, of course (gem install rack mongrel).
{geshibot lang="rack"}require 'rack'
class RackTestApp
def call(env)
@request = Rack::Request.new(env)
@response = Rack::Response.new
#answer(@request.path_info) # look up the route and process the request
@response.write("Hello World! My path is #{@request.path_info}")
@response.finish
end
end
Rack::Handler::Mongrel.run(RackTestApp.new, {:Host => "127.0.0.1", :Port => 8080}){/geshibot}
This quick script kicks off a Mongrel process listening to port 8080 on localhost and will hand all requests that it receives to the class we've created. Rack makes sure that each request triggers the #call method, and we use the request and response helpers to wrap that request (we don't have to do this, but they're awfully nice conveniences). We can use @response to iteratively generate the response body, which is buffered until @response.finish is called, at which point the response is delivered to the client.
Run this code and visit http://127.0.0.1:8080/foo/bar in a browser, and you should see a simple "Hello World" message followed by the path you used to access it (just to show how the @request object can be used). So, although we're not going to be giving Merb or Rails a run for their money just yet, this is certainly a good starting point for further exploration.
If you want to build something that resembles a reusable framework, your next thought might turn to routing, and how you could direct different requests to different methods for handling. For a first attempt, you might choose to define your routing endpoints inline with the responses using a syntax like
answer "/foo/bar" { @response.write("This is the foo bar page") } in your framework client code. The framework class could read this and use it to dynamically define responder methods and add route/method pairs to a routing Hash for later retrieval. Then, when a URL is requested, the corresponding method could be looked up in a Hash and invoked to handle the request. You could parse URLs into component parts as well, locating controller names, action names, resource identifiers, and so on.
In any case, these are just a few ideas to get you started thinking about the sort of things you can build with Rack. If you're interested in further examples, you can check out DialTone, a simple "microframework" I threw together in just a couple hours a few nights ago.
At a whopping 70 lines of code, DialTone isn't anything you'll want to use for serious work (heh), but it's useful as an easily-grokked educational tool. If nothing else, it should demonstrate exactly how much you can do with so little by leveraging a common HTTP interface like Rack.
The source is available at http://github.com/zapnap/dialtone.

