Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use deflated/gzipped content with an XHR onProgress function?

I've seen a bunch of similar questions to this get asked before, but I haven't found one that describes my current problem exactly, so here goes:

I have a page which loads a large (between 0.5 and 10 MB) JSON document via AJAX so that the client-side code can process it. Once the file is loaded, I don't have any problems that I don't expect. However, it takes a long time to download, so I tried leveraging the XHR Progress API to render a progress bar to indicate to the user that the document is loading. This worked well.

Then, in an effort to speed things up, I tried compressing the output on the server side via gzip and deflate. This worked too, with tremendous gains, however, my progress bar stopped working.

I've looked into the issue for a while and found that if a proper Content-Length header isn't sent with the requested AJAX resource, the onProgress event handler cannot function as intended because it doesn't know how far along in the download it is. When this happens, a property called lengthComputable is set to false on the event object.

This made sense, so I tried setting the header explicitly with both the uncompressed and the compressed length of the output. I can verify that the header is being sent, and I can verify that my browser knows how to decompress the content. But the onProgress handler still reports lengthComputable = false.

So my question is: is there a way to gzipped/deflated content with the AJAX Progress API? And if so, what am I doing wrong right now?


This is how the resource appears in the Chrome Network panel, showing that compression is working:

network panel

These are the relevant request headers, showing that the request is AJAX and that Accept-Encoding is set properly:

GET /dashboard/reports/ajax/load HTTP/1.1 Connection: keep-alive Cache-Control: no-cache Pragma: no-cache Accept: application/json, text/javascript, */*; q=0.01 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.99 Safari/537.22 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 

These are the relevant response headers, showing that the Content-Length and Content-Type are being set correctly:

HTTP/1.1 200 OK Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Content-Encoding: deflate Content-Type: application/json Date: Tue, 26 Feb 2013 18:59:07 GMT Expires: Thu, 19 Nov 1981 08:52:00 GMT P3P: CP="CAO PSA OUR" Pragma: no-cache Server: Apache/2.2.8 (Unix) mod_ssl/2.2.8 OpenSSL/0.9.8g PHP/5.4.7 X-Powered-By: PHP/5.4.7 Content-Length: 223879 Connection: keep-alive 

For what it's worth, I've tried this on both a standard (http) and secure (https) connection, with no differences: the content loads fine in the browser, but isn't processed by the Progress API.


Per Adam's suggestion, I tried switching the server side to gzip encoding with no success or change. Here are the relevant response headers:

HTTP/1.1 200 OK Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Content-Encoding: gzip Content-Type: application/json Date: Mon, 04 Mar 2013 22:33:19 GMT Expires: Thu, 19 Nov 1981 08:52:00 GMT P3P: CP="CAO PSA OUR" Pragma: no-cache Server: Apache/2.2.8 (Unix) mod_ssl/2.2.8 OpenSSL/0.9.8g PHP/5.4.7 X-Powered-By: PHP/5.4.7 Content-Length: 28250 Connection: keep-alive 

Just to repeat: the content is being downloaded and decoded properly, it's just the progress API that I'm having trouble with.


Per Bertrand's request, here's the request:

$.ajax({     url: '<url snipped>',     data: {},     success: onDone,     dataType: 'json',     cache: true,     progress: onProgress || function(){} }); 

And here's the onProgress event handler I'm using (it's not too crazy):

function(jqXHR, evt) {     // yes, I know this generates Infinity sometimes     var pct = 100 * evt.position / evt.total;      // just a method that updates some styles and javascript     updateProgress(pct); }); 
like image 207
Jimmy Sawczuk Avatar asked Feb 26 '13 19:02

Jimmy Sawczuk


People also ask

How do I know if Gzipped?

You can tell using Developer Tools (F12). Go to the Network tab, select the file you want to examine and then look at the Headers tab on the right. If you are gzipped, then you will see that in the Content-Encoding.

What is gzip deflate?

gzip is based on the DEFLATE algorithm, which is a combination of LZ77 and Huffman coding. DEFLATE was intended as a replacement for LZW and other patent-encumbered data compression algorithms which, at the time, limited the usability of compress and other popular archivers.


1 Answers

A slightly more elegant variation on your solution would be to set a header like 'x-decompressed-content-length' or whatever in your HTTP response with the full decompressed value of the content in bytes and read it off the xhr object in your onProgress handler.

Your code might look something like:

request.onProgress = function (e) {   var contentLength;   if (e.lengthComputable) {     contentLength = e.total;   } else {     contentLength = parseInt(e.target.getResponseHeader('x-decompressed-content-length'), 10);   }   progressIndicator.update(e.loaded / contentLength); }; 
like image 65
Nat Avatar answered Oct 08 '22 21:10

Nat