Comet is a giant hack. Even when it works flawlessly and efficiently, bringing us real-time interactive applications, deftly weaving around firewalls and browsers, avoiding unfriendly side effects, and cutting latency to near zero, it does so using mechanisms unforeseen by browser vendors, and unspecified by web standards.
Comet, going forward, has the same potential for standardization and specification. The WHAT Working Group, in their Web Applications 1.0 spec, commonly known as HTML5, created a specification for server-sent events, along with an
event-source element to be added to HTML. These mechanisms, like other Comet “transports” such as htmlfile ActiveX objects, “forever frame” iframes, or XHR streaming, allow a browser to open a persistent connection to a server, and receive events in real time as they are available. But unlike other transports, server-sent events would be straight-forward, standardized and vendor supported, sans negative side effects, and optimized for the task.
Server-sent events are currently only implemented by Opera, and that implementation is far from complete, requiring some finesse on the server side. But as other browsers adopt server-sent events, and the compatibility kinks are worked out of implementations, it will be possible to slowly decrease the number of transports a Comet server must implement for cross-browser compatibility, while increasing reliability and performance.
In this part 1 of 2, I’ll describe the existing transports, their benefits and their shortcomings.
Trade-offs of existing transports
At present, web applications using Comet have a choice of several Comet techniques, which generally fall into two categories—streaming and long-polling—and which have various trade-offs in features, browser-compatibility, and unpleasant side effects. No one transport is perfect, but using a combination of them it is possible to build a decent user experience, while keeping latency down and throughput up on the server side.
Orbited implements most of these transports, and is easily extensible to support the rest of them, or any custom transports.
“Forever-frame” iframe streaming
iframes provide the easiest-to-implement widespread Comet transport. On the client side, we open up an invisible
iframe and set its source to our Comet server. Then on the server side, we send a series of
This works because browsers render pages incrementally, as they arrive. Unfortunately, to start this incremental rendering, Safari requires a kilobyte of data at the beginning of the stream. In Orbited, we use 100
<span></span> tags for this. But at some point we’ll perhaps switch this to some tasteful ASCII art. Also, having a constantly loading
iframe causes browsers to leave the loading bar visible indefinitely, a side effect which cannot be worked around in several browsers, and in some browsers continually shows a wait cursor. That said, this transport works in all browsers released in the last 10 years, so if user experience is less important than maximal compatibility, it can be a good choice.
htmlfile ActiveX object
The Gmail team, in their Google Talk project, figured out how to exploit a mysterious ActiveX object called “htmlfile” for their Comet streaming in Internet Explorer. It’s a bit tricky to implement, as Michael Carter explained, because on the client side, we must create one of these objects, open an
iframe inside it which receives events as in the iframe streaming case, then carefully avoid the random-seeming limitations of Explorer’s garbage collector, or we’ll be cut off mid-stream. Also, it only works in Explorer. But if we are careful, it seems to work flawlessly back to at least IE 5.01, with no untoward side effects.
Back in the days of the Netscape/Microsoft browser wars, so-called push technology was developed for streaming whole page updates to browsers, a technique widely used by “real-time” webcams of the day. Any page or image sent with a
responseText are replaced with the new content. Unfortunately, this transport only works in Gecko-based browsers, and does not alert the browser to severed connections.
Recommended by Safari developers, the XHR streaming transport works by opening up an XHR request from the browser and sending a stream of events in some custom data format from the Comet server (Orbited calls its XHR streams
application/x-orbited-event-stream). The XHR object triggers a callback each time new data arrives in the stream (with ready state 3), and that callback can parse the
responseText to obtain each event. This transport works well in Webkit- and Gecko-based browsers (IE doesn’t trigger ready state 3 until the connection is closed), but the
Long-polling works differently than streaming. Instead of stringing together every event in a single indefinitely long server response, our server keeps each connection open until it has an event, but then closes it after sending a response. Then the browser immediately opens another connection, ensuring that the server can pass it events in real time. This is less effective than streaming transports with respect to bandwidth overhead and throughput; it works, however, in all recent browsers—without the ugly side effects of
Script tag long-polling (“CometP”)
If we need completely cross-domain Comet, browser security policies render all of the previous transports inoperable. Instead, we must turn to “dynamic”
script tag, setting its source to our Comet server. This connection stays open until the server has new data to send, at which point the browser executes the script, and we open up another script tag to wait for the next event. The performance here is comparable to XHR long-polling, but this works across domains.
Flash and other plugins
All of the previous techniques, whether standards-based or proprietary, rely on native browser technologies. It is also possible to use a Flash, Java, or Silverlight socket to stream events from a server to browsers. This has the latency characteristics of other streaming transports, but doesn’t suffer from negative side effects, works in any browsers with the requisite plugin installed, and can probably be made to work across domains. Unfortunately, it relies on the presence of a third-party plugin, meaning it cannot work on every platform (iPhone anyone?), or whenever the plugin in question is turned off by the user, and might also be blocked by zealous firewalls which restrict traffic to port 80. Most of all, moving away from open web technologies leaves a sour taste in my mouth.