Bookmark with del.icio.us submit Progressive%20XMLHttpRequest digg.com reddit

Progressive XMLHttpRequest

Perhaps it's subtle, but the draft spec for XMLHttpRequest calls for support for progressive response handling:

4.7.6 The responseText attribute

The responseText attribute must return the result of running these steps:

1. If the state is not LOADING or DONE return the empty string and terminate these steps.

2. Return the text response entity body.

To rephrase for my purposes, responseText should return the intermediate contents of the response when an XMLHttpRequest is interrogated during the LOADING state. It'll take a little work to handle these partial responses as valid script, but let's first address browser support. Firefox and Webkit browsers already support this behavior if you set the Content-Type header of your response correctly. IE8 throws an exception when responseText is accessed before readyState reaches COMPLETE.

I ran a modified version of the streaming response tests I used in my last post to verify progressive XHR handling. The server returns several chunks in 100ms intervals that include script that indicates how much of the response was received before it is first handled by the browser.

  Bytes Buffered
Configuration Firefox 3.5 Chrome 3.0 IE 8
Tranfer-Encoding: chunked 111 536 N/A
Content-Type: text/html
Tranfer-Encoding: chunked
111 N/A N/A
Content-Type: text/plain
Tranfer-Encoding: chunked
111 85 N/A
Content-Type: application/x-javascript
Tranfer-Encoding: chunked
111 111 N/A

For Webkit browsers, it's critical to specify a Content-Type of "text/plain" or "application/x-javascript" when returning script content to an XHR for progressive handling. Seems reasonable, but it's easy to neglect. In my testing, I didn't see any change in behavior in the presence of a "charset" param.

Note that Microsoft's documentation for XMLHttpRequest now refers the to draft specification. I'm hopeful that we'll be seeing support for progressive responses soon.

Now, since we'll be interpreting partial response content as executable script, we'll need to do something to ensure that each chunk we evaluate terminates on a complete expression. For this test, I added delimiters between valid blocks of source:

window.aFunction();
// -- //
window.bFunction();
// -- //

Where //--// is the delimiter. When outputting using chunked transfer encoding, you might organize code so that a delimiter is present at the end of each chunk boundary. On each readyState change, if the state is LOADING or DONE, I call a function to read the new content, identify a safe place to trim it, and append it to a buffer.

var index = 0;
var buffer = '';
var DELIMITER = '//--//';

function handlePartialResponse(request) {
  var i = request.responseText.lastIndexOf(DELIMITER);
  if (i > index) {
    i += DELIMITER.length;
    var newChunk = request.responseText.substr(index, (i - index));
    buffer += newChunk;
    index = i;
    flushBuffer();
  }
}

Finally, we evaluate the contents of the buffer. It's not necessary to remove the delimiter, since it's a valid JavaScript comment.

function flushBuffer() {
  window.eval(buffer);
  buffer = '';
}

What would you use this for? Consider this technique for the response channel in your next Comet app or any time you're able to deliver part of a script response while doing expensive server side work to produce the rest.

Comments

Neat idea. Instead of:

request.responseText.lastIndexOf(DELIMITER);

you might want to use:

request.responseText.indexOf(DELIMITER, index);

to avoid losing packets if more than one comes in during an event for some reason.

I use this technique for the comet client side in all browsers but IE. Works great.

Why not just use newline for your delimiter? Yes it forces developers to keep their updates to a single line, but it's simpler and doesn't rely on a specially formatted comment.

Matt makes a good point that multiple updates can come in during a polling interval. By using newline, you can just split the response on \n, and then parse each entry in the array. Seems faster and cleaner than parsing as string.

Also, application/octet-stream will also progressively download in webkit browsers.

I've gotten IE to stream to the client, but you have to use it's ActiveX foundation and open a series of nested iFrames to establish persistent communications. The up shot is that it's significantly more reliable than streaming XHR And the buffer can be flushed without resetting the connection.

This site is great!!

Post a Comment

(optional)
(optional)