Please read my blog's comment policy here.
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.
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:
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,
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.
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.
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.
Morgan: My tests showed that Safari 4.03 on Windows doesn't have the buffering issue. Safari and Chrome use different underlying networking stacks.
I really think there are some exciting times ahead for real-time platforms powered by Comet. So much so that I'm working on one.
I've written a few blog posts that contain some additional information that might also be useful to people who find this post interesting.
Using Fiddler to trick Silverlight into allowing a crossdomain Web Request. Shows the streaming Fiddler option you talk about here in a video: http://www.leggetter.co.uk/2009/10/30/using-fiddler-to-trick-silverlight-into-allowing-a-crossdomain-web-request.html
Anyway to do this and still have it work in IE6 or 7 ?
@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.
There are ways to do Comet in all browsers - products like ours Liberator - http://www.freeliberator.com and most other Comet servers implement various technique to achieve cross browser and cross version support.
This is why things like WebSocket, albeit a good move in the right direction, don't really change things for most people - as they should be using a Comet server that hides this all away from them. WebSocket is just another tool in the Comet server implementors tool box. For other developers it is a fast way to create a website that most people wont be able to use.
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.
Note that the Chrome prelude can be avoided by setting Content-Type:application/octet-stream. As per ...
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.
@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?
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.
XDomainRequest does not have a binary interface. To use with non-text, first base64 to string.
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.
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.