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

前提是你 提交于 2019-11-28 04:29:23

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);
};

I wasn't able to solve the issue of using onProgress on the compressed content itself, but I came up with this semi-simple workaround. In a nutshell: send a HEAD request to the server at the same time as a GET request, and render the progress bar once there's enough information to do so.


function loader(onDone, onProgress, url, data)
{
    // onDone = event handler to run on successful download
    // onProgress = event handler to run during a download
    // url = url to load
    // data = extra parameters to be sent with the AJAX request
    var content_length = null;

    self.meta_xhr = $.ajax({
        url: url,
        data: data,
        dataType: 'json',
        type: 'HEAD',
        success: function(data, status, jqXHR)
        {
            content_length = jqXHR.getResponseHeader("X-Content-Length");
        }
    });

    self.xhr = $.ajax({
        url: url,
        data: data,
        success: onDone,
        dataType: 'json',
        progress: function(jqXHR, evt)
        {
            var pct = 0;
            if (evt.lengthComputable)
            {
                pct = 100 * evt.position / evt.total;
            }
            else if (self.content_length != null)
            {
                pct = 100 * evt.position / self.content_length;
            }

            onProgress(pct);
        }
    });
}

And then to use it:

loader(function(response)
{
    console.log("Content loaded! do stuff now.");
},
function(pct)
{
    console.log("The content is " + pct + "% loaded.");
},
'<url here>', {});

On the server side, set the X-Content-Length header on both the GET and the HEAD requests (which should represent the uncompressed content length), and abort sending the content on the HEAD request.

In PHP, setting the header looks like:

header("X-Content-Length: ".strlen($payload));

And then abort sending the content if it's a HEAD request:

if ($_SERVER['REQUEST_METHOD'] == "HEAD")
{
    exit;
}

Here's what it looks like in action:

The reason the HEAD takes so long in the below screenshot is because the server still has to parse the file to know how long it is, but that's something I can definitely improve on, and it's definitely an improvement from where it was.

Ivan Castellanos

Don't get stuck just because there isn't a native solution; a hack of one line can solve your problem without messing with Apache configuration (that in some hostings is prohibited or very restricted):

PHP to the rescue:

var size = <?php echo filesize('file.json') ?>;

That's it, you probably already know the rest, but just as a reference here it is:

<script>
var progressBar = document.getElementById("p"),
    client = new XMLHttpRequest(),
    size = <?php echo filesize('file.json') ?>;

progressBar.max = size;

client.open("GET", "file.json")

function loadHandler () {
  var loaded = client.responseText.length;
  progressBar.value = loaded;
}

client.onprogress = loadHandler;

client.onloadend = function(pe) {
  loadHandler();
  console.log("Success, loaded: " + client.responseText.length + " of " + size)
}
client.send()
</script>

Live example:

Another SO user thinks I am lying about the validity of this solution so here it is live: http://nyudvik.com/zip/, it is gzip-ed and the real file weights 8 MB



Related links:

AdamJonR

Try changing your server encoding to gzip.

Your request header shows three potential encodings (gzip,deflate,sdch), so the server can pick any one of those three. By the response header, we can see that your server is choosing to respond with deflate.

Gzip is an encoding format that includes a deflate payload in addition to additional headers and footer (which includes the original uncompressed length) and a different checksum algorithm:

Gzip at Wikipedia

Deflate has some issues. Due to legacy issues dealing with improper decoding algorithms, client implementations of deflate have to run through silly checks just to figure out which implementation they're dealing with, and unfortunately, they often still get it wrong:

Why use deflate instead of gzip for text files served by Apache?

In the case of your question, the browser probably sees a deflate file coming down the pipe and just throws up its arms and says, "When I don't even know exactly how I'll end up decoding this thing, how can you expect me to worry about getting the progress right, human?"

If you switch your server configuration so the response is gzipped (i.e., gzip shows up as the content-encoding), I'm hopeful your script works as you'd hoped/expected it would.

We have created a library that estimates the progress and always sets lengthComputable to true.

Chrome 64 still has this issue (see Bug)

It is a javascript shim that you can include in your page which fixes this issue and you can use the standard new XMLHTTPRequest() normally.

The javascript library can be found here:

https://github.com/AirConsole/xmlhttprequest-length-computable

David Mulder

The only solution I can think of is manually compressing the data (rather than leaving it to the server and browser), as that allows you to use the normal progress bar and should still give you considerable gains over the uncompressed version. If for example the system only is required to work in latest generation web browsers you can for example zip it on the server side (whatever language you use, I am sure there is a zip function or library) and on the client side you can use zip.js. If more browser support is required you can check this SO answer for a number of compression and decompression functions (just choose one which is supported in the server side language you're using). Overall this should be reasonably simple to implement, although it will perform worse (though still good probably) than native compression/decompression. (Btw, after giving it a bit more thought it could in theory perform even better than the native version in case you would choose a compression algorithm which fits the type of data you're using and the data is sufficiently big)

Another option would be to use a websocket and load the data in parts where you parse/handle every part at the same time it's loaded (you don't need websockets for that, but doing 10's of http requests after eachother can be quite a hassle). Whether this is possible depends on the specific scenario, but to me it sounds like report data is the kind of data that can be loaded in parts and isn't required to be first fully downloaded.

I do not clearly understand the issue, it should not happen since the decompression should done by the browser.

You may try to move away from jQuery or hack jQuery because the $.ajax does not seems to work well with binary data:

Ref: http://blog.vjeux.com/2011/javascript/jquery-binary-ajax.html

You could try to do your own implementation of the ajax request See: https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest#Handling_binary_data

You could try to uncompress the json the content by javascript (see resources in comments).

* UPDATE 2 *

the $.ajax function does not support the progress event handler or it is not part of the jQuery documentation (see comment below).

here is a way to get this handler work but I never tried it myself: http://www.dave-bond.com/blog/2010/01/JQuery-ajax-progress-HMTL5/

* UPDATE 3 *

The solution use tierce third party library to extend (?) jQuery ajax functionnality, so my suggestion do not apply

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!