Solving Comet to the browser with CometCatchr
November 30, 2009
I’ve been playing a lot with Comet lately. It started with Notify.io, in which I decided to prove that HTTP streaming was a simpler alternative to XMPP in getting messages to the desktop. That went quite well, but it was easy because it wasn’t all that different from a socket connection. Then I built a yet-to-be-announced site that uses real-time updates, and I was forced to deal with Comet to the browser. That’s a bit more complicated.
Actually, I’m arguably a veteran of Comet in the browser. I was doing it before it had a name, all the way back in 2005. A friend and I were using it (without knowing that it was terribly novel) to build a real-time strategy game in the browser called AjaxWar. I haven’t really done a lot with it since, but I was hoping after almost 5 years there would be all kinds of advances in libraries and tricks that would make it super easy.
That was not really the case.
There are things, but not easy things. The Bayeux protocol? I guess all that would be easy to do if there were a lightweight Javascript library for it. But there isn’t really. There’s a jQuery one, but it’s completely undocumented. Plus I was just sitting there thinking, do I need all this? Handshaking? Message envelopes?
I was also hoping for actual persistent connections (that’s what we did in AjaxWar), but it turns out the standard today is long-polling. This is a semi-persistent connection that drops after every message and then reconnects and waits for the next message. I also wanted JSONP and cross-domain support. So I ended up using the dynamic script tag technique:
<script type="text/javascript"> $(document).ready(function(){ waitForMsg(); }); function waitForMsg() { $('body').append('\<script type="text/javascript" src="http://mycometserver.com/channel?callback=gotMsg">\<\/script>'); } function gotMsg(msg) { // Do something with it waitForMsg(); } </script>
It’s fairly elegant in its simplicity and cross-browser support. But it has some weird side-effects that I’m not sure if any of the fancier systems got around. For one, it keeps the browser loading. While it’s waiting for messages, the browser says that page is still loading. I also ran into some issues where if I included, say, a Google Calendar widget on the page, it might not decide to keep the connection open (or even start it). I ended up putting a delay on the first call to waitForMsg() until after the calendar widget was likely loaded.
So it’s a bit brittle. You don’t know if it stops working. Therefore you can’t do retries. And you never know if you happen to miss a message between connections (unlikely, but something to worry about). Plus I think if you hit Escape it also kills it.
But this worked well enough for my projects. I knew that if I found a better way, I’d switch over to it, but it was good enough.
…heh…
Then today I decided to solve the problem right once and for all with a project called CometCatchr.
CometCatchr a lightweight Flash component to be used by Javascript that gives you a persistent connection for Comet streams.
I know a very small number of people won’t agree with my approach using Flash, but I tend to be pragmatic. I trust Flash is generally available and the benefits completely outweigh everything else to me. It’s also not new. Even the Bayeux protocol includes Flash as supported connection type. Still, I couldn’t find any simple Flash component that gave me what I wanted.
CometCatchr gives me Javascript callbacks on messages, maintains a persistent connection across messages, retries on lost connections, works in all browsers, supports (participating) cross-domain message sources, and just freaking works.
It was a drop-in replacement to my previous technique that worked right out of the box:
<script type="text/javascript"> function gotMsg(msg) { // Do something with it } </script> <embed type="application/x-shockwave-flash" width="0" height="0" src="CometCatchr.swf?url=http://mycometserver.com/channel&callback=gotMsg"></embed>
It simplified my code, not just client-side, but now I don’t have to support JSONP callbacks on the server. CometCatchr also parses the JSON messages before passing the callback, so that’s taken care of too. I realize it seems less than ideal to couple this with JSON payloads, but I didn’t say it wasn’t an opinionated component.
In fact, it’s very opinionated. It likes single-line JSON messages sent via HTTP using chunked transfer encoding. That’s because that’s how I do Comet streams. Actually, that’s how Twitter does them, too. I’m quite alright with such constraints, but if you want to make changes, it’s MIT licensed and super simple to hack on.
So for the time being, I’ve more or less solved simple Comet to the browser, particularly for me. It also might be worth knowing that another motivation for building this component is that I intend to solve Comet and real-time stuff in the browser entirely… but uhh, yeah. Stay tuned.
November 30, 2009 at 5:18 am
You might be interested in the HyBi activity of the IETF:
“HyBi coordinates work in the IETF related to several proposals for bidirectional, “long poll”, and “reverse” HTTP: mechanisms where the connection is still initiated by the client but communication is initiated by the server.”
http://trac.tools.ietf.org/bof/trac/wiki/HyBi
November 30, 2009 at 9:17 am
Yeah. I mean, perhaps the reason there wasn’t a good solution was because people just stopped trying when Web Socket was announced. As for Reverse HTTP, I always liked it, but I haven’t seen much as far as implementation. And the semantics are just kind of an awkward use of HTTP — sending a message to clients is done as a “request”? It just doesn’t sit right with me.
I noticed Lisa Dusseault is involved with HyBi. I’ve worked with her before on some projects. I’m surprised BOSH isn’t included in their agenda.
Also, the whole bi-directional thing is a bit overrated I think. 80% of real-time web apps can get by with a one-way event stream to the client and discrete Ajax requests to the server.
November 30, 2009 at 10:52 am
There’s a discussion of BOSH, Bayeux and other techniques in the “Best practices” draft – http://www.ietf.org/id/draft-loreto-http-bidirectional-01.txt. As I understand, the group’s recommendation for a bi-di protocol is BWTP – http://www.ietf.org/id/draft-wilkins-hybi-bwtp-00.txt
I’m not involved in any way in the activities of the group, but I do like their approach with analyzing the current practices. That said, I do agree that today a single server->client event stream with occasional client->server calls is OK for most apps, but I’m guessing they want a more sound, long-term technology.
And I do agree on Reverse HTTP – it works, but feels a bit awkward. Just like using Flash ;)
Anyway, great post and nice library! Definitely better having more options than less.
November 30, 2009 at 11:40 am
To my mind, the “bidirectional” part is really about stressing the ability for the server to initiate messages to the client. We thought that “hybi” would be a cute name (pronounced “hi, bye”) and didn’t intend it to limit the theme too much.
The BWTP proposal is an individual submission that the WG can consider. I know it’s a little arcane interior IETF process but the name ‘wilkins’ means it’s an individual submission, and only ‘ietf-hybi’ indicates a WG document in progress.
In fact, in the BOF in Hiroshima where there was consensus to actually form the WG once we fine-tune the charter, there was also agreement to do Web Sockets first before rechartering to do anything else if anything else is still wanted. Web Sockets has some problems with intermediaries and brittleness that we think we know how to address
Thx for bringing up hybi (and thanks jeff for bringing it to my notice :)
November 30, 2009 at 11:54 am
Yeah, at first impression, I like BWTP over Reverse HTTP and Bayeux, so I’d like to see it get further along.
Speaking of Web Sockets and implementations, I almost brought up Michael Carter, et al’s work on Orbited because they’re doing some really great stuff. I believe they even support a Web Socket interface so you can use it now in any browser. The reason I didn’t use it here was that I don’t actually want a bare socket, nor the full Orbited stack.
March 2, 2010 at 8:11 am
Only just seen this blog. Although your use case seems very simple compared to what a lot of the Comet providers can do I still think using an existing Comet solution would benefit you in terms of handling edge cases like connection errors etc. There’s no need to write things from scratch all the time.
March 2, 2010 at 8:12 am
And you suggest?
March 2, 2010 at 9:32 am
Have a look on http://cometdaily.com/maturity.html – there are lots of others too.
March 2, 2010 at 10:13 am
Have a look at http://simonwillison.net/2007/Dec/5/comet/
Jetty and dojox.cometd are a very easy way to get into comet.
March 2, 2010 at 5:40 pm
Thank you. I guess I was familiar with these already.
I can’t help but be slightly defensive when being accused of Not Invented Here. That’s not how I roll. If I do something like this, it’s because I have very specific requirements, and I feel that my experience in doing this sort of stuff since before it had a name justifies my dissatisfaction in the existing solutions… some of which were created by people I know.
March 5, 2010 at 2:08 am
> I can’t help but be slightly defensive when being accused of Not Invented Here. That’s not how I roll.
You say that, but here’s a post on a widely read comet blog about how someone implemented almost exactly what you did for a well established comet server in around an hour: http://cometdaily.com/2008/02/14/is-comet-becoming-over-complicated/ nearly two years before you did it. He’s not the only one either, many of the significant comet servers out there do the same thing when they can’t do anything better.
On top of that, people have been using applets to stream data into web pages for more than 10 years now, which is exactly the same idea, just using a different brower plug in to do the work.
This isn’t to say that your solution isn’t valuable in its own way, but it’s most certainly an example of NIH. While you may have ‘specific requirements’, the correct answer for by far the majority of people is to use an established solution, which will almost certainly have better performance, better connectivity to other messaging systems, better firewall traversal characteristics, better resilience, better support and a better API than a ‘roll your own solution’.
March 5, 2010 at 3:46 am
For some reason I’m stumped why this is even a big deal. I’m also confused why you include servers when this is just a client. I suppose you might be right that this is an example of NIH for clients. Hell, I’ve always heard of people doing this or something like it for, yes, nearly 10 years. However, I’ve never found a client implementation I cared about. One that was this simple, focused, straight-forward with source available. Hell, that link you point to… where’s the source? How do I actually use it?
The point is it only took several hours to implement this and the amount of energy to make it work it various cases where it isn’t good enough is either just as easy or not within the scope of the point of this project.
But seriously I’m losing interest in this conversation. I compare it to the little Flash copy and paste widgets. There are plenty of those and nobody cares.
I’m not saying this is the solution that covers every case, but in my experience it covers probably 80% of cases in 20% of the code than other solutions. And I like that. If you don’t, go away.