Casey Dreier’s Rack presentation

This evening Casey Dreier of Singlebrook Technology gave an excellent talk on Rack. As he explained, Rack the Ruby gem provides a common interface between Ruby applications and web servers. It allows you to develop a Rails application locally using Mongrel or Unicorn and deploy it on Apache. You can “mount” applications to paths on your domain, allowing you to do things like “drop in” the Resque admin screens, which are built in Sinatra. Or in our brave new world, take your app that currently runs on Apache/MRI and port it to JRuby/Trinidad with a minimum of fuss.

You can think of middleware as an around filter for your application. To call your application, Rack starts at the bottom of the middleware stack and bubbles up to the application. The application yields its response, and bubbles that response back up the call stack. Through middleware, you have the opportunity to manipulate the server environment variables (session, cookies, url, environment variables) before the request ever gets to Rails. You can even prevent the request from getting to Rails at all. On the flip side, you can manipulate the response all the way back out, and even replace the Rails response with “Hello world” if you like. Its main use in Rails is to prepare the environment to invoke an action on the controller.

I learned a couple of things in Casey’s talk:
1. That a Rails 3 controller is a Rack endpoint. I did not realize that I could “use” middleware beyond the initializers and the dispatcher.
2. That I should not be afraid to mount Sinatra apps using config.ru and bypass the now gigantic Rails middleware stack.

Then my feelings began to diverge. I came to Casey’s talk having watched Aaron Patterson’s keynote at RailsConf, in which I showed all the hacking he had to do to get Rack to work with Rails 3.1 output streaming. Along the way he saw that Rails 3 is actually slower than Rails 2, mainly because the tall middleware stack triggers Ruby’s garbage collector in a mean way.

Casey’s simple example was a timer middleware that appends the response time to the response body in an HTML comment. Simple enough and fairly taken. Then there was a quick look at his recent gem, “rack-unscripted”. The gem inserts a piece of middleware that looks for the end of the head tag and the beginning of the body tag, and inserts some HTML snippets that, if you don’t have JS enabled, will tell you that you should enable it if you want the application to work. The snippets themselves are clever – I will give them a further look.

My first response betrays my having just watched Patterson’s talk, with three months of exile from Rails in the PHP world under my belt. Why would you add a layer to the call stack for a 0.1% use case? Why perform gratuitous regex subs on the response body when you could add that to your layout? Casey’s response was twofold: 1. He wanted to learn Rack, and this was the immediate use case. 2. Now he can include it in any application via a gem.

Then I started thinking. Inserting HTML into the response body is not the concern of middleware. If it’s not going to be a helper function used in the layout, it could be an after filter on ActionController::Base. You can still put it in a gem for ease of installation. It properly belongs in the V part of MVC; and middleware is for abstracting away non-MVC concerns so that the application itself remains focused on models, views, and controllers.

Having said that, I sure wish that the framework of my current project (Yii PHP framework), has middleware. Because the project is targeted at IE7 as a minimum (at least not IE6!), I wanted to introduce the Rails way of simulating PUT and DELETE requests. That is to add a hidden input named ‘_method’ to the top of a form. If $_POST[‘_method’] exists then set $_SERVER[‘REQUEST_METHOD’] to its value. Perfect use for middleware: manipulate the environment variables so your app thinks it’s dealing with a DELETE request before it gets to the router.

In the end, I had to do it a different way. I had already extended CWebApplication to handle namespaced controllers, and updated index.php to call MWebApplication instead of CWebApplication. I added a call in createController that does the _method check. In retrospect that would be a place to introduce support for “inbound” middleware.

What do you think? Both from an efficiency standpoint, and from separation of concerns (concern that all hell breaks loose when you start manipulating the views somewhere other than the view layer).

Thanks Casey for the engaging talk, and to Singlebrook for hosting the Ithaca Web Group.

May 20, 2011 in Uncategorized