HOWTO: Custom Mongrel Handlers
There doesn’t seem to be any good documentation for creating custom Mongrel handlers. The Mongrel site seems to be completely silent on the subject. I was able to extract what I needed to know from this article and Ezra Zygmuntowicz’s slides.
It’s actually quite easy to make a mongrel handler, so here’s a quick tutorial that should tell you everything you need to know.
First, why would you want to make a mongrel handler? The main reason would be speed and scalability. Mongrel is multithreaded even though Rails isn’t. Mongrel is fast, Rails is slow. Of course, Rails is Rails, and Mongrel is just a webserver; but many types of apps may have some services that are hit far more than others, and thus it would be worth writing what amounts to a stripped-down version of the controller action in order to handle just those requests.
For example, if you were writing eBay in Rails, you might want to implement the API calls which are used to check the status of an auction as a custom mongrel handler. This part of the site may be accessed with great frequency by third party apps trying to keep a current status, and chances are generating the results are pretty simple (converting a row in the database to XML and printing it out).
Here’s what that might look like:
class StatusHandler < Mongrel::HttpHandler
def initialize
@mutex = Mutex.new
end
def process(request, response)
id = request.params['PATH_INFO'].slice(1, 20) # trim leading slash
response.start(200) do |head, out|
head["Content-Type"] = "application/xml"
out.write status(id).to_xml
end
end
def status(id)
rows = @mutex.synchronize { ActiveRecord::Base.connection.select_all("select * from auctions where id=#{id.to_i}") }
return { 'error' => ‘No such record’ } if rows.length < 1
return rows.first
end
end
uri "/status", :handler => StatusHandler.new, :in_front => true
Name this status_handler.rb and drop it into the root dir of your Rails project. Instead of running script/server, execute this command:
mongrel_rails start -S status_handler.rb
Assuming you’ve set up a little database with some sample data in the table named by the handler (”auctions” in my example), accessing the url http://localhost:3000/status/1 will show the data for the record with id=1.
Now what’s so hot about this? For one, it’s fast - see Erza’s slides for benchmarks. But more importantly - to my mind - is that a long request won’t hold up any others. Try putting a “sleep 10″ as the first line of the process method. Restart your server and hit the status url again. The connection will hang temporarily, but now open another tab and hit any other page in your Rails app. Notice that it displays right away, even though the other tab is still loading.
The downside is that you don’t have Rails, and as it turns out, we like Rails. So suddenly you’re stuck doing a lot of your own dirty work. Here, for example, I load up ActiveRecord and manage the database connection and raw sql manupulation. (This whole thing could be done in one line as a Rails controller: respond_to { |f| f.xml { Auction.find(params[:id]).to_xml } }) Parsing the request string can be time-consuming so I went for simplicity - String#slice instead of a regular expression or tokenization. You also have to protect against CGI parameter attacks, which I again simplify with a to_i.
I wasn’t able to figure out how to load a custom handler from a mongrel yaml config file. It seems like the keyword should be config_script, but it doesn’t seem to produce the same result as -S. Anyone knows how to make this work, please comment.
Now that you know how to write a mongrel handler, the real fun can begin. In my next post I’ll describe how this can be used for server-push connections.
Update: Rick Olson improved the code by trimming out the superfluous establish_connection, I’ve used his version above.
May 7th, 2007 at 10:08 am
Hey Adam, nice post. This stuff is complicated enough that it helps to have lots of people’s takes on it. Anyway, I can answer your question about loading a custom handler from a config script. I ran into the same problem. The keyword is, in fact, config_script, but in your yaml, you’ve got to precede it with a colon like this:
—
:config_script: lib/mongrel_handlers/jsonp_handler.rb
I don’t know why it wants the colons, but that’s what the examples look like on the Mongrel site and that’s what works (treating them as hash keys?). Once you’ve got that setup, you can just load the conf file as normal:
mongrel_rails start -C config/mongrel_conf.yml
And mongrel will find the handler (you can see it in the printout if you launch mongrel without the -d flag).
May 8th, 2007 at 3:10 am
[…] In my last post I described how to create a mongrel handler. I said you might want to do this for optimization purposes, but my own interest came about in an attempt to solve the server-push problem with Rails. […]
May 8th, 2007 at 2:41 pm
The mongrel site includes my tutorial on using “upload progress gem”:http://mongrel.rubyforge.org/docs/upload_progress.html, which is implemented as a simple mongrel handler. This is a good tutorial though. I’m working on a similar article for a minimal mongrel handler.
Greg: the : in front of the yaml keys is so the values can be accessed with a symbol. yaml[:config_script] vs yaml[’config_script’].
May 8th, 2007 at 2:49 pm
One thing to keep in mind: since your handler is being run alongside a rails app, you shouldn’t have to establish the connection of the ActiveRecord object. It should be the exact same class that’s being used in the rails app. You also might want to put a mutex around your DB calls.
http://pastie.caboo.se/59978
May 8th, 2007 at 4:17 pm
Rick -
Excellent, I wasn’t even thinking about the fact that the mongrel process is actually executing in the same Ruby VM.
What’s the point of the mutex, though? The database certainly can handle simultaneous queries; is ActiveRecord not threadsafe? I though that ActionController was the only offender in that dept.
In any case, I’ll update the post with your improved version.
May 10th, 2007 at 5:21 pm
[…] Custom Mongrel Handlers (tags: ruby mongrel web programming) […]
May 11th, 2007 at 3:28 am
I’m wondering if it’s possible to subclass the handler from Mongrel::Rails::RailsHandler instead of Mongrel::HttpHandler and get the entire rails environment inside the handler ?
May 11th, 2007 at 4:07 am
Pratik -
As Rick pointed out, you actually do have most of the Rails environment. Mongrel is running in the same VM process as Rails. What you don’t get is ActionController, which includes stuff like automatic parsing of your CGI parameters, sessions, and template rendering. (It should be possible to render ERB with lower-level calls, although ERB is pretty slow too, so if you’re doing this for optimization’s sake that might be counterproductive.)
July 1st, 2007 at 11:00 am
[…] HOWTO: Custom Mongrel Handler - Building mongrel http handler […]