Share via


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.

Comments

  • Anonymous
    April 05, 2010
    Can you comment if the 2 KB XDR prelude requirement potentially going to be fixed with IE 9? Also, thanks for the info on Chrome. I had tried a COMET-like approach /w a keep-alive SCRIPT that sends JS notifications but it didn't work in browsers that don't appear to do partial-rendering (Webkit). Didn't bother testing Opera. Looks like Opera won't do XHR COMET, so I assume by saying "in any browser built in the last few years" means that it's doable in Opera but Opera needs a different approach.

  • Anonymous
    April 05, 2010
    As of Chrome, I believe, it is actually Webkit. Both Safari and Chrome are based on Webkit engine and has the 2K prelude padding issue.

  • Anonymous
    April 05, 2010
    Morgan: My tests showed that Safari 4.03 on Windows doesn't have the buffering issue. Safari and Chrome use different underlying networking stacks.

  • Anonymous
    April 06, 2010
    The comment has been removed

  • Anonymous
    April 06, 2010
    Anyway to do this and still have it work in IE6 or 7 ?

  • Anonymous
    April 06, 2010
    @Mike: Any way to do... what? COMET? Sure, there are other COMET techniques like IFRAME/SCRIPT tags, or you could use XMLHTTPRequest for long-poll requests but simply use it to transmit one (not endless) message at a time before resetting/reopening. The "Even Faster Websites" book describes your options and the tradeoffs.

  • Anonymous
    April 11, 2010
    The comment has been removed

  • Anonymous
    May 05, 2010
    Thank you very much Eric, for your answer! I deplore the fact that we should just go through solutions (APIs and libraries) to make a real streaming, while Firefox is already running full. Until other browsers followed, it remains nonetheless the simulation AJAX / PHP. In any case, I do not regret to see that they contacted to clarify this particular point, thanks to you, I do hair for more tears me know what object I get the data, it is simply not possible except on Firefox.

  • Anonymous
    September 17, 2010
    Note that the Chrome prelude can be avoided by setting Content-Type:application/octet-stream.  As per ... code.google.com/.../detail

  • Anonymous
    September 20, 2010
    BTW, I just tested IE9 beta support for XDR streaming it appears to be broken.  Even with a 16K of prelude and 2K padding on each chunk I'm not getting any 'onprogress' events.  Add to this that streaming iframes appear to also be broken and it's not clear there's any way to do streaming HTTP responses in IE9. ... I'm also unable to get streaming iframes ("forever frames") ... and XHR requests still do not provide access to in-progress response streams Can anyone confirm/refute this?  If I'm right, then there would appear to be no built-in way of doing streaming HTTP responses in IE9 (!)  I sure would love to be proven wrong on this.

  • Anonymous
    September 20, 2010
    @Robert: My test page (linked above) appears to work exactly as it did in IE8. I'm not sure what "streaming iframes appear to also be broken" means... do you have a test case somewhere? Do you have a buffering proxy server  (e.g. Fiddler) between your computer and the Internet?

  • Anonymous
    November 25, 2010
    Thanks for the informative post. I'm having trouble getting XDomainRequest to work with binary data. It seems to truncate the responseText as soon as it encounters a null byte. In Firefox you can force the browser to deliver unprocessed bytes by using xhr.overrideMimeType("text/plain; charset=x-user-defined") but there doesn't seem to be a way to do this with XDomainRequest. Adding a "Content-Type: text/plain charset=x-user-defined" header on the server doesn't work either. Can you help? Thanks in advance.

  • Anonymous
    November 25, 2010
    XDomainRequest does not have a binary interface. To use with non-text, first base64 to string.

  • Anonymous
    November 25, 2010
    Thanks for quick(!!) and definitive answer. Unfortunately I can't use base64 in my case, since I'm trying to prime the browser cache with an ActiveX CAB file while showing a progress of the download. Appreciate the help though, I think I'll have to find another solution.

  • Anonymous
    November 25, 2010
    Brian--that doesn't work for a different reason. XDR has an  isolated cache not shared by the rest of the browser. If the CAB is same origin you can use the Download binary behavior to prep the cache.

  • Anonymous
    March 04, 2011
    Hi This page is really useful - I have been looking for days for this topic and when I landing on this page I pretty much got all my answers - so thakyou. 2 questions: I can get the streaming going on some versions of ie, but version 8.0.7600.16385 (and possibly others) seems to never stream - even with the prelude! It just shows the final complete response at the end. Also, I find that in some version of IE, a request made to my own servlet will stream the response, but on the second attempt IE will not submit a new request to the server at all - could this be a server response cache header issue? i.e. send back "do not cache" or "cache-control:private"and IE will always resubmit the request?

  • Anonymous
    March 04, 2011
    @Tom: You should never see streaming of responseText using XHR in IE, any version-- the code simply hasn't ever worked that way. As for the "Don't see a second request", the only things I could imagine are that you made your XHR in synchronous mode, or you are somehow hitting the connections-per-server limit and the second request is being queued.

  • Anonymous
    March 04, 2011
    Hi Eric Thanks for your response - it really is much appreciated! My code is using the XDomainRequest object - not XHR. So using XDR with prelude on IE, I would expect to see streaming - isn't that right? And yet on that version of IE (8.0.76...) it seems I do not. Here is my complete response 3:Beginning XDR Test. 13:[XDR-onload]. responseText: [2kb byte prelude] Begin1... 2... 3... 4... 5... 6... 7... 8... 9... 10. Done. Any thoughts? Could this be a settings issue on IE? Also, with the second request going missing issue, I am also using XDR, not XHR. I should point out that the requests are not concurrent. It is a case of 1 request completes fully over a longish period of time, some time passes, then a second request is made but IE actually sends nothing to the server.

  • Anonymous
    March 04, 2011
    @Tom: You should only see the first behavior if you have a buffering proxy server (e.g. Fiddler in non-streaming mode). For the second one, yes, it's possible that your first response was simply cached-- What response headers do you have on the first response?

  • Anonymous
    June 27, 2011
    Hello, I'm developing a JavaScript library supporting HTTP Streaming and WebSocket -- jQuery Stream ( code.google.com/.../jquery-stream ). To perform HTTP Streaming with Internet Explorer 8+ I chose XDomainRequest as a transport, but I overlooked the fact that no cookies will be sent. Ignoring that fact finally resulted in problem that every request generates new session so the client's state is not maintained. In fact, this problem can be solved by rewriting url to contain the session id, but this solution is obtrusive and has a security issue. Besides that, Some of server-side web framework such as Ruby on Rails and Django does not allow to pass the session id. To maintain user's session with them, an additional coding is required, viloating they design principle. Consequently I'm trying to replace XDomainRequest way with classical Hidden Iframe way being used with IE6 and IE7. Is there any room for improvement with XDomainRequest.? I don't want to use classical Iframe transport and to bother the server for maintaining session. Thanks for any ideas.

  • Anonymous
    June 27, 2011
    @Donghwan: Suppressing cookies is a deliberate and integral part of the XDR security model. There are no plans to change this behavior.

  • Anonymous
    October 25, 2012
    Alert reader Andres notes that one downside of using XDR/XHR for streaming is that the responseText will grow larger and larger, consuming more and more memory on the client. That's true, and as far as I know, there's no great workaround short of periodically destroying the object and reconnecting using a new one. HTML5 WebSockets (supported by IE10+) and Server Sent Events (supported by non-IE) shouldn't exhibit the constant-memory-growth problem.

  • Anonymous
    February 11, 2013
    I'm implementing server push solution. Is there a way to clear XDomainRequest.responseText, since it gets filled with the every response chunk? (as in xhr.responseText with multipart where it contains only the last response part.) [EricLaw] No, this is one of the limitations of using streaming in XHR/XDomainRequest, as mentioned in the article above.