COMET Streaming in Internet Explorer

The request/response nature of HTTP works very well for traditional web pages, but to build dynamic AJAX applications, it’s often desirable for the server to be able to send data to the client on its own schedule. You could imagine, for instance, scenarios like an online game, or an event viewer, where the server may want to notify the client of something on an irregular schedule.

The COMET model allows a web application to push data to the browser without the browser explicitly requesting it—the Wikipedia page is fairly comprehensive, but my favorite reference is the “Scaling with COMET” chapter by Dylan Schiemann in Steve Souders’ second book on web performance, Even Faster Websites. While improved alternatives (like Server-Sent Events and Web Sockets) will likely eventually take over, workable COMET implementations can run in any browser built in the last few years.

One challenge with using XMLHTTPRequest is that browsers behave differently when it comes to streaming of content. A common complaint against XMLHTTPRequest in IE was that it doesn’t stream content. More specifically, the responseText property cannot be queried when readyState=3 (Receiving) and it only becomes available when readyState=4 (Loaded). This means that while your JavaScript will know when the transfer of the response body starts, you must wait for the response to complete before examining it. If your goal is to stream down a “never-ending” stream of data, you’ll find that IE’s XHR object never gets to the Loaded readyState and thus you cannot examine the “partial” response.

We had planned on fixing this for IE8 but unfortunately found that we couldn’t do so easily. IE’s “native” XMLHTTPRequest is simply a wrapper around the existing MSXML implementation, and the MSXML implementation performs this buffering internally. Since IE doesn’t install MSXML, we couldn’t simply change that implementation ourselves.

Update: IE10's newly enhanced CORS-capable XmlHttpRequest implementation can now stream responses as they are read from the server.

I put together a simple Streaming Test page where you can examine the behavior of your browser. The test uses AJAX to download a stream that contains ten small blocks of data, each sent 1 second apart. An event handler attached to the onreadystatechange event displays what data has been received. It’s interesting to see how different browsers behave when you click the “Try XMLHTTPRequest” button:

  1. IE cannot show the responseText until readyState=Loaded
  2. Chrome 4.1 doesn’t get to readyState=2 until the entire response is available
  3. Opera 10.51 appears to fire the onreadystatechange function only once when first entering readyState=3 (Receiving)
  4. Firefox 3.6 and Safari 4 fire onreadystatechange as each block is received, and make these blocks available in the responseText property

One point to keep in mind about streaming content in this way with XHR—even in the browsers where XHR behavior works as you might hope—is that the responseText property will grow and grow, increasing the amount of memory consumed by your page. In past betas, we’ve found degenerate pages which will exhaust the system’s virtual memory if left running overnight.

In Internet Explorer 8, we introduced the new XDomainRequest object—beyond support for making cross-origin requests, this object directly wraps URLMon and thus it doesn’t inherit the buffering behavior for MSXML. Naturally, the IE8 enginering team had test cases which confirmed that XDomainRequest objects properly support streaming, calling the onprogress event as the response blocks are received from the network.

Unfortunately, it turns out that our official test suite was based on streaming large responses back to the client. The problem is that, by-default, URLMon (the network wrapper below XDomainRequest) will not bubble up notifications of download progress in the first 2kb of the response. So, if you click “Try XDomainRequest” button on the test page, you will find that you don’t see the onprogress notifications until the entire response is returned.

A workaround for this problem is to send down a two kilobyte “prelude” at the top of the response stream—my test simply sends 2kb of spaces. After that initial block is received, the onprogress event is fired as each subsequent block is received from the network. Interestingly, you will also find that this “prelude” workaround resolves the Chrome 4.1 XHR behavior described in Point #2 above. When you send this prelude, Chrome’s XHR object will get to readyState=3 and begin reporting blocks as they are received.

Until next time,

-Eric

PS: Folks using Fiddler to debug with streaming content should be aware of two factors. First, Fiddler buffers most HTTP responses by default—you must disable Fiddler's buffering if you want content to be streamed to the browser as it normally is. Second, it’s important to understand that Fiddler’s Response Inspectors will not show an incomplete HTTP response. For streaming responses that never end, Fiddler will not show the partial body unless you use the newly introduced COMETPeek command on the context menu. The COMETPeek command will copy the current partial response body into the Response Inspectors for your viewing pleasure.

Update: IE10's newly enhanced CORS-capable XmlHttpRequest implementation can now stream responses as they are read from the server.