Archive for May, 2007

Comet with Rails + Mongrel

Tuesday, May 8th, 2007

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.

Comet is the term that seems to be catching on for server-push via XmlHttpRequest. Possible applications include chat clients or a stock ticker. Anything that wants constant updates will be both responsive and less demanding of server resources if it waits for data to be pushed to it, instead of opening a new status query every few seconds.

Since the server can’t initiate a connection to the user’s browser, the only possible solution is to have the browser hold a connection open indefinitely, waiting for an update. Since Rails is single-threaded, however, this means that one whole server instance would be tied up by this connection - clearly infeasible in almost all situations.

You might say, “Why not have another small server listening on a separate port to hold on to these push-status connections?” Good idea - except that XmlHttpRequest won’t let you connect to another port. This is because the port is considered part of the hostname, and connecting to another hostname from within the javascript sandbox would be a big security no-no. (It would be trivially easy, for example, to inject a little javascript into a site which caused all of its visitors’ browsers to start hammering another unrelated site as soon as they visited the homepage.)

Juggernaut gets around this with a little hidden Flash component. This is a nifty idea, but for me it is unappealing because Flash is not readily available for my platform (Ubuntu AMD64). More importantly, I’d prefer to avoid building technology that depends on a proprietary plugin built by a monolithic, old-fashioned (i.e., shrink wrap) software company.

So holding open connections to Rails won’t work due to its controller lock. But as was demonstrated in the previous entry, a mongrel handler won’t have that problem. I’ll extend the auction example shown there to use server-push.


require 'active_record'

class StatusHandler < Mongrel::HttpHandler
   def process(request, response)
      id = request.params['PATH_INFO'].slice(1, 20)
      current = request.params['QUERY_STRING']

      while status(id) == current do
         sleep 0.2
      end

      response.start(200) do |head, out|
         head["Content-Type"] = "text/html"
         out.write status(id)
      end
   end

   def status(id)
      connection.select_value("select status from auctions where id=#{id.to_i}")
   end

   def connection
      ActiveRecord::Base.connection
   end
end

uri "/status", :handler => StatusHandler.new, :in_front => true

This assumes your auctions table has a field named “status,” which I’m using as an integer, but any type should work. http://localhost:3000/status/1 now delivers just one value, the status. Where it gets interesting is something like http://localhost:3000/status/1?100, assuming that the status of auction id=1 is currently set to 100 in the database. Now, the connection will hang and wait for the value to change. (You’ll see the database queries in development.log, but no web hits.) Pop open a sql shell and run “update auctions set status=101″ and the connection will resolve immediately, printing out the new value.

Here’s a simple example of making an ajax call to this url from within a page:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
   <%= javascript_include_tag :defaults %>
</head>
<body>
   Status is now: <span id="status"></span>

   <script language="javascript">
      function respawn()
      {
         new Ajax.Updater('status', '/status/1?' + $('status').textContent, { onComplete: respawn });
      }

      respawn();
   </script>
</body>
</html>

Experiment with updating the status value in the sql shell and you’ll see that the page always updates instantly. To watch the connections, open the Firebug console, click Options in the upper-right, and make sure “Show XMLHttpRequests” is checked. Reload the page and you’ll now see a POST each time you update the status. There will always be an active one at the bottom, waiting, waiting for the status update.

And there you go. Server-push connections with only Rails and Mongrel.

Update: Mere minutes after I finished writing this article, I came across Shooting Star, a Rails plugin for adding Comet to your apps. So far this looks a little heavy-weight for my purposes, and somewhat platform-dependent so far - not to mention that they push the meteor metaphor a bit far in their method naming. Still, this may be a more robust solution than my little hack, so check it out. If anyone has tried Juggernaut, Shooting Star, and my hack, I’d be curious to hear a comparison.

HOWTO: Custom Mongrel Handlers

Sunday, May 6th, 2007

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.

Working With Rails

Tuesday, May 1st, 2007

I’ve been aware of Working With Rails for a while, and have even connected with a few developers who have done work for Bitscribe through it. Cool concept, definitely. Just recently I noticed that someone had recommended me (one of the contributors to Gyre - thanks, Michael!) so I decided to make a real profile. Interesting to note that, with one recommendation, my popularity is 89%. Guess there are a lot of empty records up there.

The profile form asks how long you’ve been working with Ruby, and Rails. For the former I looked at the datestamp of my first fooling-around Ruby script. For the latter I hit the svn log of my first Rails project. I was surprised to see that I’ve been doing Rails for exactly a year this month, with my Ruby tinkerings predating that by about six months. Woah, really? Ruby and Rails feel so comfortable now that it seems like years since I’ve used anything else. It’s gratifying that time seems to stretch out during periods of rapid change - I guess time doesn’t always fly when you’re having fun.