Comet with Rails + Mongrel
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.
May 8th, 2007 at 10:12 am
Will the AJAX request timeout if the status doesn’t change for a long time?
May 8th, 2007 at 2:42 pm
Nope. I experimented with it and Firefox doesn’t ever seem to timeout on ajax requests, and this handler does not.
A production site would probably want to wrap the sleep loop in something like:
require 'timeout' timeout do begin ... rescue response.start(200) { |h,o| out.write "timed out" } end endThat’s just off the top of my head, there’s probably a better HTTP response code for this situation.
May 8th, 2007 at 3:10 pm
Seeing more and more mongrel handlers being developed is fascinating. This one especially!
May 9th, 2007 at 1:25 am
how about IE and safari ?
May 9th, 2007 at 9:04 am
Reading the FAQ on Juggernaut it states :
Juggernaut > Comet because :
# It’s much less of a hack
# It doesn’t crash your browser (Comet can do this after a while)
Is there any substance to these claims ?
May 9th, 2007 at 9:18 am
i also found this :
http://cyll.org/blog/tech/2006-08-09-themongrelcomet.html
doesn’t look like its been worked on for a while
May 9th, 2007 at 3:57 pm
I’m wondering a few things about this. One being the speed and effectiveness of using mongrel to keep the connection open. Juggernaut is quite fast, especially if you use some javascript hackery to make client initiated actions seem instantaneously instead of having it sent to the server, then back via Jug. I will be looking into how to cluster Jug very soon as well and then begin testing it’s scalability. In theory, since this comet is in mongrel, clustering mongrel also would cluster the push technology. That’s not so for Juggernaut. But then again, keeping the app and push servers separate, less stress is put on each individual server.
Like weepy said, it doesn’t look like anything has been written recently on this other than this post. Is there a proof of concept demo anywhere?
May 9th, 2007 at 11:25 pm
a current benefit of Jug over this method currently is that there’s no polling in Jug. — this method has essentially moved the polling to the server-side.
I guess this could be fixed if we could get it to accept local connections from Rails ?
May 10th, 2007 at 1:31 pm
I’m not sure I agree with your views on Adobe. They have, after all, just open sourced Flex. As to comet it has some drawbacks. As far as I can remember IE can only have 2 connection streams open at the same time and has been known to crash with long running comet connections. Firefox isn’t perfect either.
However, Alex from Dojo has solved the crossdomain issue using some iframe hackery. You might want to check out his comet framework.
May 10th, 2007 at 1:50 pm
[…] Also, this chap has been experimenting with comet and mongrel. The problem being that Comet can only connect to the same host (and port) the original request was from. However, I hear Alex Russell from dojo has cracked that with some sort of iframe hackery (but there are some caveats) . Anyways, I still retain the opinion that using a flash socket is the best method available at the moment (and frankly, the 2% of people who don’t have flash turned on probably don’t have JavaScript turned on either). […]
May 11th, 2007 at 4:54 am
This surely looks good. Just concerned about one thing. Won’t this approach kill the database if used in a live system ? Because we need to constantly poll the database for any kind of updates in order to be fast. What could be the possible work arounds ? I guess it’d help to use memcached or it might help to build some kind of bridge/middleman between mongrel comet handlers and the database. Suggestions ?
Thanks,
Pratik
May 11th, 2007 at 8:13 am
Also, this works fine when I update the polled value from console. But lets say, on the same page where you have the polling ajax request and you try to update the polled value using another ajax call, both the requests waits indefinitely. I tried this with a single mongrel server and also with 3 mongrel servers behind a pen cluster.
Thanks,
Pratik
May 11th, 2007 at 12:57 pm
Regarding killing the database on a live system: a simple one-row read accessed by the primary key index should be very, very, very fast on any kind of modern database. I don’t think this will be a bottleneck for most cases. On the very high end, yes you might want to look some sort of memory cache.
Regarding doing an update in one ajax call and a write on another: I just tried this and had no difficulties. I added this code to the view:
<%= link_to_function "Update", "new Ajax.Updater('update_result', '/comet/update')" %> <div id="update_result"></div>And in the controller:
def update ActiveRecord::Base.connection.execute("update auctions set status=status+1") render :text => "done" endWorks like a charm.
May 11th, 2007 at 4:18 pm
[…] Adam @ Bitscribe » Comet with Rails + Mongrel (tags: RubyonRails mongrel comet ajax web) […]
May 13th, 2007 at 7:46 am
>> Just concerned about one thing. Won’t this approach kill the database if used in a live system ?
The only scalable way to deal with this is to allow Rails to inform the Comet client that an update has occured. This is the way Jug works and it seems to work well.
It would require some server threading system - perhaps event machine would be a good way to go ?
weepy
July 1st, 2007 at 11:00 am
[…] ç”æ¡ˆæ˜¯ No and Yes。用普通的方法 Rails ä¸èƒ½åšåˆ°ä»¥ä¸Šæ•ˆæžœã€‚Rails 是一個單線程的環境 ,也就是說全部 Request 也由åŒä¸€æ”¯ Thread 控制。在以上情境ä¸ã€Œå« Client 在 Request ä¸ç‰å¾…直至有信æ¯ã€ç‰æ–¼å«æ•´å€‹ server åœä¸‹ä¾†ã€‚幸好,Rails 單線程ä¸ç‰æ–¼ Server ä¹Ÿå¿…é ˆå–®ç·šç¨‹ã€‚Adamçš„ Comet with Rails + Mongrel ä¸ç¤ºç¯„了怎樣用自訂 HttpHandler 讓 Rails å¯ä»¥æŒçºŒåœ°è·Ÿ Client 連接。我們å¯ä»¥åœ¨ Client Side å‘¼å« AJAX,在 Mongrel çš„ HttpHandler ä¸æŠŠé€™äº› Request 轉æˆå‘ MOM 的呼å«ï¼Œå¦‚下圖。 […]
July 3rd, 2007 at 10:50 pm
Based on your hacks, I tried to implement a HttpHandler that communicate with JMS server using STOMP and response user with AJAX. (http://www.reality.hk/articles/2007/07/03/740/)
However, it will deadlock during bidirectional communication. I suspects this could related to green thread system of Ruby, where any blocking syscall like IO blocks all threads. If this is the case, it takes more works to making it work like shooting star or juggernaut
July 4th, 2007 at 1:53 pm
Sluying -
Interesting work. I don’t know anything about Stomp, but looking at what is done in your mongrel handler code I can see that this is fundamentally the same thing that is happening when you connect to a database, which is also a server being connected to via socket. There are two differences in the implementation, though: for one, ActiveRecord maintains a persistent connection throughout the life of the Mongrel process, rather than opening a new one on each request; and the database connectors are probably all written in C.
Assuming you’ve ruled out everything else, I’d probably start by trying to hold open a persistent connection to port 61613 through a wrapper class as part of your Rails app. If that’s still producing the same results, you could rewrite the key functions in that class in C, thereby avoiding Ruby threading/socketing issues.
July 4th, 2007 at 10:20 pm
[…] ç”æ¡ˆæ˜¯ No and Yes。用普通的方法 Rails ä¸èƒ½åšåˆ°ä»¥ä¸Šæ•ˆæžœã€‚Rails 是一个å•线程的环境 ,也就是说全部 Request 也由åŒä¸€æ”¯ Thread 控制。在以上情境ä¸â€œå« Client 在 Request ä¸ç‰å¾…直至有信æ¯â€ç‰äºŽå«æ•´ä¸ª server åœä¸‹æ¥ã€‚幸好,Rails å•线程ä¸ç‰äºŽ Server 也必须å•线程。Adamçš„ Comet with Rails + Mongrel http://adam.blogs.bitscribe.net/2007/05/08/comet-with-rails-mongrel/ ä¸ç¤ºèŒƒäº†æ€Žæ ·ç”¨è‡ªè®¢ HttpHandler 让 Rails å¯ä»¥æŒç»åœ°è·Ÿ Client 连接。我们å¯ä»¥åœ¨ Browser å‘¼å« AJAX,在 Mongrel çš„ HttpHandler ä¸æŠŠè¿™äº› Request 转æˆå‘ MOM 的呼å«ï¼ŒæŠŠæ¶ˆæ¯åŒ–为 HTTP Response é€å›ž browser,如下图。 […]
July 6th, 2007 at 7:20 am
Adam,
I believe I found the difference. In your example you update the auction status externally, where I try to update the auction status in Rails.
Try the example: http://www.reality.hk/~siuying/tmp/poll.tgz
1. Uncompress the file and start mongrel by: mongrel_rails start -C config/mongrel.yml
2. Enter the poller URL http://127.0.0.1:3000/
3. Press “Updater”, updater page opened. If we update status there, the status in poller page is updated immediately.
4. Open another poller, it loads correctly
5. Update status, you will find no page getting update
Any idea?
July 6th, 2007 at 4:24 pm
Yeah, this has nothing to do with your Rails app: browsers typically limit the number of XmlHttpRequest connections open at once. In Mozilla browsers, the most you can have is two. So once you’ve opened two updater pages, a third one will just hang as it waits for one or the other requests to complete. I don’t think there’s any real solution to this - if your app expects users to keep many pages with updaters on them open at once, this approach to Comet just won’t work - you’ll need something like Juggernaut instead. I think for most apps, though, the user will only ever have one window open, so this shouldn’t be a problem. For development purposes, you may just want to use two different browser processes (i.e. on different users, or perhaps use Firefox for one and Opera for the other).
July 6th, 2007 at 9:06 pm
Oh thanks! I dont realized this is browser issue.When I open another browser, it works.
But the problem is, if people open two page with same browser, it blocks your server! Not a very secure web application…
July 8th, 2007 at 7:13 am
After careful experiment, it is really my firefox hangs not rails. Thanks again!
July 14th, 2007 at 6:19 pm
I think you should take a look at orbited, an open source distributed comet server. It has a very simple api that allows you to send events from ruby to any browser connected to the comet server. While Most of the material is python oriented, there is a ruby client.
Orbited solves many problems, least of all scaling the app. (It uses an architecture not unlike memcached.) Take a look and see what you think.
July 16th, 2007 at 1:48 pm
Michael -
Interesting, I hadn’t seen this before. (It should be noted that the term “Comet” actually originates in the Python world, as part of Dojo.) Here’s the Orbited homepage, but unfortunately the documentation is extremely sparse. (The “docs” link is a 404 error!) This appears to be the best bit of info: CherryChat
August 2nd, 2007 at 9:00 am
> Comet can only connect to the same host (and port) the original request was from.
So how does juggernaut solve this?
Port 1: Webserver
Port 2: Juggernaut
Port 1 Port 2
??
August 2nd, 2007 at 9:01 am
Port 1 not equal Port 2
(my last post was somehow crippled)
August 15th, 2007 at 9:24 pm
I think juggernaut solves this by using flash (an embedded .swf file that gets sent to the browser) and not javascript to do the connection. Flash doesn’t have the same port restrictions as javascript and so it gets around that problem.