This post originated from an RSS feed registered with Ruby Buzz
by Christopher Cyll.
Original Post: The Mongrel Comet
Feed Title: Topher Cyll
Feed URL: http://feeds.feedburner.com/cyll
Feed Description: I'm not too worried about it. Ruby and programming languages.
Comet is a javascript technology used to receive events without AJAX
polling. You can read about Comet on
Wikipedia.
Mongrel is an excellent HTTP server for Ruby. Have a look at its cute dog.
While I was playing around with a Ruby based DHTML turn based strategy
game a few weeks ago, I found myself struggling with latency. I was
using an AJAX polling mechanism, and with all the game logic on the
server side, things felt laggy. There were any number of options I
could have tried. And, in fact, in the end, I just ignored the
problem.
However, in the process I read about Comet and began to wonder how
hard it would be to add it to Mongrel.
It turned out to be suprisingly easy... after a fashion.
COMET_FORMAT = "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nTransfer-Encoding: chunke\
d\r\nConnection: close\r\n".freeze
class HttpResponse
def comet(type, status=200)
write(Const::COMET_FORMAT % [status, HTTP_STATUS_CODES[status], type])
write("\r\n")
return HttpComet.new(self)
end
end
Above, you can see the first thing we do is add a comet() method to
our response object, this returns an object we can stream our data out
through. We'd use the HttpComet object like this, writing each integer
up to 100 to the connection:
class ExampleCometHandler < Mongrel::HttpHandler
def process(request, response)
comet = response.comet("text/plain")
(0..100).each do |i|
comet.write("#{i}");
sleep 1
end
comet.close
end
end
The code that makes this all work lives inside HttpComet. It uses
"chunked" HTTP transfer to send portions of the data along at a time.
class HttpComet
def initialize(response)
@response = response
end
def write(data)
size = data.size
@response.write(sprintf("%x;\r\n", size))
@response.write(data)
@response.write("\r\n")
@response.flush
end
def close
@response.write("0;\r\n")
end
end
Each chunk starts with a number of bytes in hex, followed by an \r\n,
then the payload. A chunk of size 0 indicates the end of the file. And
that's all there is.
Now, there's one problem. The way I'm doing this has the side effect
that only one user can be Comet streaming from any particular handler
instance. Registering another instance of the same handler at a
different URL and using some kind of redirect to get each user to
their own URL and handler can work around this, but it's ugly as sin.
But hey, I learned about HTTP chunked mode!
[Code Available Here]Update: Oh, it's worth mentioning, getting events to actually
trigger on receipt for all platforms is tricky, so instead of just
sending data, often Javascript is loaded into an iframe. You can see
an example of this in the source.