SitePen Support
Webtide

The Future of Comet: Part 2, HTML 5’s Server-Sent Events

by Jacob RusJanuary 10th, 2008

HTML 5 and Comet

Comet doesn’t have to be a hack. Currently, as we saw last time, Comet relies on undocumented loopholes and workarounds, each one with some drawbacks. We can make Comet work effectively in every browser, using streaming transports on subdomains of the same second-level domain, or using script tag long polling across domains. But this leaves Comet developers implementing (and more frustratingly, debugging) several transports across several browsers. Traps are numerous and easy to stumble into.

Recognizing the benefit of a straight-forward, standardized Comet technique, the WHATWG’s HTML 5 specification includes sections about server-sent events, and a new event-source element, which aim to de-hackify Comet. For now, only Opera has implemented these, and its implementation remains incomplete, but both Mozilla and Apple have committed to HTML 5, and an implementation is at least in-the-works for Safari.

Every Comet developer should familiarize himself with these specifications, because they provide the best streaming transport for Opera since version 8.5, and can only grow in importance as browsers adopt them. Furthermore, HTML 5 is a work-in-progress, and now is the best time to provide feedback to the WHATWG. Those interested in the future of Comet should comment now, before the specifications have been repeatedly implemented and can no longer be easily modified.

Basics of the server-sent events data format

The server-sent events portion of HTML 5 defines a data format for streaming events to web browsers, and an associated DOM API for accessing those events, by attaching callback functions to particular named event types. The format, which is sent by a Comet server with content type application/x-dom-event-stream, is straight-forward. Each event is made up of key-value pairs. For example:

key 1: this is the value associated with key 1
key 2: this value for key 2 stretches
key 2: over three lines, which are concatenated
key 2: by a browser supporting server-sent events
; these lines, which each begin with a `;`, are
; comments, and are ignored by the browser

key 1: after a pair of newlines, this is a new
key 1: event, with its own set of key-value pairs
; the following key has an empty corresponding value
empty key

...

Each key-value pair is known as a field, and several special fields are defined in the specification. In particular, the Event field names the event as a specific type. The browser can attach different callback functions to specific named event types. If omitted, the event type is assumed to be message. Also, though it is only mentioned in examples, the data field is useful for our event payloads.

So an event stream sent by Orbited might look something like:

Event: orbited
data: this is our event payload for the
data: first Orbited event

Event: orbited
data: this is the payload for the second
data: Orbited event

Event: ping
data: \o/

Event: orbited
data: here's the third Orbited event

...

The specification mandates that all files are UTF-8, but considers any of carriage return (\r), line feed (\n), or both (\r\n) an acceptable newline—in the browser, multiple lines of a single field are joined with \n alone.

The event-source element

Server-sent events are received by objects supporting the RemoteEventTarget interface, which merely means that they support two methods, addEventSource and removeEventSource, each of which takes a URI string as input, and adds or removes it, respectively, from the list of event sources for the object.

In addition to any JavaScript objects supporting server-sent events, HTML 5 defines an event-source HTML element, which declaratively indicates the use of a Comet source in a web page. As well as supporting the addEventSource and removeEventSource methods, the event-source element has a src attribute. When the src is changed, the event-source closes its previous connection, and opens a new Comet connection to the new URI.

Cross-domain usage

HTML 5 allows connections across domains, through use of the Access-Control HTTP header, as defined in a separate W3C specification (which applies identically to normal XHR usage and to server-sent events). A request is made for a resource as usual, but if that resource on the server (in this case, an event stream from a Comet server), includes the Access-Control HTTP header with values allowing the use of the resources, browsers will treat it as if it came from the same domain as the main document. If the header is not found, or if it denies the requested use, browsers will behave as if the resource does not exist (so that denied requests reveal no information about the resource).

Additionally, HTML 5 defines a “cross-document messaging” mechanism, which allows cooperation between documents (perhaps in iframes, etc.) from different domains, using a postMessage function.

Opera’s implementation

Opera has, since version 8.5, implemented a subset of these HTML 5 technologies. Opera versions since 8.5 support the event-source element, and recent versions have pure-JavaScript interfaces as well.

To support versions back to 8.5, we must create event-source elements, set their src attribute, and attach them to the document. Then we can add “event listener” callback functions to the event-source for each type of named event in the event stream. In Orbited, we use the following JavaScript to accomplish this:

connect_server_sent_events: function () {
  var es = document.createElement('event-source');
  es.setAttribute('src', this.url);
  document.body.appendChild(es);

  var event_cb = this.event_cb;
  es.addEventListener('orbited', function (event) {
    var data = eval(event.data);
    if (typeof data !== 'undefined') {
      event_cb(data);
    }
  }, false);
},

Where this.event_cb is some callback function, a property of the Orbited object, which will receive the event payload of every orbited event. By default we send payloads in JSON format, so evaling each yields a JavaScript object.

Also, it is quite easy to test browsers for server-sent events support from JavaScript, using code something like:

if ((typeof window.addEventStream) === 'function') {
  // ... browser supports server sent events
} else {
  // ... no support.  fall back on another transport
}

Caveats

Opera’s implementation differs from the specification in a few key ways, however, so Comet application authors must be careful.

  • Opera ignores events without a defined Event field, rather than assuming its value to be message. It is possible to include multiple named event types, attaching separate event listeners for each type.
  • Event payloads must be in the data field. A callback attached to the event source will receive an event object as input, with event.data set to the value of the data field.
  • At present, Opera only supports linefeed (\n) characters as newlines in event streams, and will silently fail if carriage returns are used in newlines (\r\n and \r are not supported newlines).

Legacy support

Even though Opera is the only browser to natively implement server-sent events, it is possible for a Comet server to treat the last several versions of Safari and Firefox as if they did, with a few caveats. We can use a modified XHR streaming technique (as described in part 1), and implement our own parsing for the application/x-dom-event-stream format (at least the version supported by Opera) in JavaScript. To pull this off we must make one concession to Safari: 256 bytes of dummy data at the beginning of our event stream, so that Safari will begin incremental parsing.

Our Comet server can then be blissfully unaware that these browsers have no real server-sent event support, and we can get away with implementing only two transports on the server side: the iframe transport, supported by most browsers since at least 1999 or 2000, and—using the htmlfile ActiveX object—capable of a flawless user experience back to Internet Explorer 5.01; and server-sent events.

To make them still more compatible, we might be able to build support for event-source into Firefox and Safari using JavaScript alone, creating objects supporting the addEventSource, removeEventSource methods, and capable of dispatching named event types to event listeners. Adding such support would require deeper magic than I currently possess as a journeyman JavaScript hacker; if any masters can shed insight, please comment here, or shoot an email to the Orbited mailing list.

In Orbited, we have not yet tried reducing XHR streaming to server-sent events for Firefox and Safari, but it is on the to-do list.

Arbitrary DOM events and controversy

In addition to this straight-forward Comet transport, server-sent events provide more general, and potentially powerful, capabilities, whose complexity is somewhat controversial. Indeed, the whole server-sent events section of the specification currently includes a notice that it may be removed. I expect that it will change at least somewhat from its current form before the specification is finished.

The specification demands that every element supporting the EventTarget interface should also support the RemoteEventTarget interface. A Comet server can thereby send arbitrary DOM events to page elements. This includes events such as mouse clicks, key presses, etc. As specified, event streams can also include a Target field, which can target events to the top level of the document, or to specific element IDs. And browser vendors could, if they desired, add further objects supporting the RemoteEventTarget interface, potentially enabling declarative Comet applications.

So the theory goes. Many of us Comet developers, including Michael Carter and Alex Russell (in a recent IRC discussion), remain unconvinced that there is a benefit in pushing application logic from browser-side JavaScript to the server side. We expect that real-time applications will always have a significant need for client-side logic, so the specification may as well embrace that—and remain as simple as possible. Specification simplicity benefits not only browser vendors who must build conforming and compatible implementations but also Comet developers who must learn its ins and outs.

But I hope server-sent events are not scrapped altogether. Comet would benefit greatly from specification and intentional browser support. The version supported by Opera strikes a reasonable balance, meeting the needs of Comet developers without going beyond those needs.

Conclusion

With improving browser support, creation of more high-quality open-source Comet servers, better developer resources such as Comet Daily, and the examples provided by big-name Comet applications, Comet’s future looks bright. Any standardization efforts by browser vendors that further reduce barriers to entry will only lead to more and better Comet applications.

HTML 5 is coming, with at least Opera, Apple, and Mozilla committed to its adoption, and one way or another it will include improved Comet support. But the Comet-related portions of HTML 5 are still very much unfinished, and in need of feedback from browser vendors and from us, the community of Comet developers. The discussions are transparent and easy to join, and time for action is now. We should figure out what we need and tell the W3C and the WHATWG about it. They are all ears.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Comet on Wikipedia

by Jacob RusJanuary 9th, 2008

A few weeks ago, I mostly finished a complete rewrite of the Wikipedia article about Comet,* and at the moment it is the most comprehensive explanation of what Comet is, why Comet is hard, and why Comet matters. But of course, bringing only my own voice and point of view, I can’t tell the whole story. So this post is intended first and foremost as a heads up and a request for review and commentary. What am I overlooking or minimizing? Are there any inaccuracies? Are there any great Comet resources I haven’t linked to in some footnote? In particular, I’d appreciate comments on the history section from those who were there when Comet-like techniques were first employed.

Eventually (not in the immediate future, but probably around the next time I have to prepare some conference slides of some sort), I plan to make a series of pretty diagrams to go along with the article, including several modeled after Jesse James Garrett’s, showing the timing of communication in various web application architectures, and a few more of server network topologies. If anyone has suggestions for other diagram types, feel free to suggest those too.

* For comparison, see the version before I started drastic changes.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

The Future of Comet: Part 1, Comet Today

by Jacob RusDecember 11th, 2007

Introduction

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.

A few years ago, before it was widely used, or had been named, Ajax was in the same boat. It relied on an obscure and proprietary object, the XMLHttpRequest (XHR), created by Microsoft, and only slowly adopted by other browsers, one at a time. Today however, Ajax through the XHR object is nearly universally supported, with at least 4 popular and interoperable implementations (XHR is a native JavaScript object in IE 7). It is specified by a W3C working draft and dozens of books have been written on the subject.

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

“Forever-frame” 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 script tags—each of which calls some predetermined callback function—wrapped in a never-ending HTML document. The client-side JavaScript sets up the event callback function to do whatever it likes with the event payloads.

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.

XHR multipart

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 Content-type: multipart/x-mixed-replace HTTP header would be replaced in the browser as each multipart section arrived. When Mozilla adopted the XHR object, they added the same support for multipart streams in XHR responses. This means client-side JavaScript is called each time a new event arrives, and the contents of the 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.

XHR streaming

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 responseText does keep growing indefinitely as more events come in, so the connection should be restarted every once in a while. Also, custom format parsing in JavaScript could lead to bugs, and the stream requires an initial padding of 256 bytes of data before Safari will begin to trigger events.

XHR long-polling

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 iframe streaming.

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 tags—sometimes called “JSONP”—in a method described by Alex Russell last year, which I call “CometP”. We use JavaScript to create a 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.

Stay tuned

With this impressive list of transports, we can cover all recent browsers (IE 5+, Safari 1.3+, Firefox 1.0+; plus Opera 8.5+ as described next time) without unseemly side effects, using our choice of streaming or long-polling, and can support browsers back much further than that, with a few usability niggles. Unfortunately, however, this requires careful implementation of our server code and browser-side JavaScript, which must coordinate to agree on a transport. This coordination problem, along with browsers’ cross-domain security policies, prevent us from using real-time sources in the kinds of mashups built from other web services. The barriers to entry for Comet are much higher than for non-real-time content, and success relies on loopholes in existing browser objects. Wouldn’t it be nice if every browser could receive event streams across domains securely and efficiently via a single mechanism? Wouldn’t it be nice if web authors could write the same straight-forward HTML and JavaScript for every browser, and send a single format from the server? Stay tuned for part 2, which plots a course to such a future.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Copyright 2008 Comet Daily, LLC. All Rights Reserved