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

前端 未结 8 2340
醉酒成梦
醉酒成梦 2020-12-08 00:11

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 whi

相关标签:
8条回答
  • 2020-12-08 00:51

    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:

    screenshot

    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.

    0 讨论(0)
  • 2020-12-08 00:52

    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:

    • SO: Content-Length not sent when gzip compression enabled in Apache?
    • Apache Module mod_deflate doc
    • PHP filsize function doc
    0 讨论(0)
  • 2020-12-08 00:57

    This solution worked for me.

    I increased deflate buffer size to cover biggest file size I may have, which is going to be compressed generally, to around 10mb, and it yielded from 9.3mb to 3.2mb compression, in apache configuration so content-length header to be returned instead of omitted as result of Transfer Encoding specification which is used when loading compressed file exceeds the buffer size, refer to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding for more info about chunked encoding header which is used in compression as well as more info about deflate buffer size in https://httpd.apache.org/docs/2.4/mod/mod_deflate.html#deflatebuffersize.

    1- Include the following in your apache configuration, and note buffer size value is in bytes.

    <IfModule mod_deflate.c>
    DeflateBufferSize 10000000
    </IfModule>
    

    2- Restart apache server.

    3- Incldue the following in your .htaccess file to make sure content-length header is exposed to JS HTTP requests.

    <IfModule mod_headers.c>
        Header set Access-Control-Expose-Headers "Content-Length"
    </IfModule>
    

    4- In onDownloadProgress event before calculating progress total percentage append following to retrieve total bytes value.

    var total = e.total;
    if(!e.lengthComputable){
    total = e.target.getResponseHeader('content-length') * 2.2;
    } 
    

    5- Note, I learnt by comparing, that lengthComputable is set to false, as flag indicates if content-length is passed in header, while relying not on Content-Length header omission but actually it’s Content-Encoding header, as I found when it is passed in file response headers, lengthComputable is only then set to false, it seems as a normal behaviour as part of JS HTTP requests specification Also, the reason why I multiplied by 2.2 the total from compressed content-length, because it achieves more accurate download/upload progress tracking with my server compression level and method, as the loaded total in HTTP progress returned reflects the decompressed data total instead of compressed data thus it requires tweaking the code logic a little bit to meet your server compression method as it may vary than mine, and first step is to examine the general difference of the compression across multiple files and see if multiplying by 2 e.g. results with closest value to the decompressed files size i.e. original size and multiply accordingly and yet make sure by multiplication the result is still smaller or equal but not bigger than original file size, so for the loaded data its guaranteed reaching and most likely as well as slightly surpassing 100 in all cases. Also, there is hacky enhancement for this issue solution that is by capping progress calculation to 100 and no need to check if progress exceeded while taking the relevant point on assuring to reach 100% into implementation must be addressed.

    In my condition, this allowed me, to know when each file/resource loading has completed i.e. check total to be like the following where >= used to take into account slight surpassing 100% after compressed total multiplication to reach decompressed or if percentage calculating method was capped to 100 then use == operator instead, to find when each file completed preloading. Also, I thought about resolving this issue from roots, through storing fixed decompressed loaded totals for each file i.e original file size and using it during preloading files e.g. such as the resources in my condition to calculate progress percentage. Here is following snippet from my onProgress event handling conditions.

    // Some times 100 reached in the progress event more than once.
    if(preloadedResources < resourcesLength && progressPercentage < 100) {
        canIncreaseCounter = true;
    }
    if(progressPercentage >= 100 && canIncreaseCounter && preloadedResources < resourcesLength) {
        preloadedResources++;
        canIncreaseCounter = false;
    }
    

    Also, note expected loaded total usage as fixed solution it's valid in all circumstances except when oneself have no prior access to files going to preload or download and I think its seldom to happen, as most of times we know the files we want to preload thus can retrieve its size prior to preloading perhaps through serving via PHP script list of sizes for the files of interest that is located in a server with HTTP first request, and then in second, preloading request one will have each relevant original file size and or even before hand store as part of code, the preloaded resources fixed decompressed size in associative array, then one can use it in tracking loading progress.

    For my tracking loading progress implementation live example refer to resources preloading in my personal website at https://zakaria.website.

    Lastly, I'm not aware of any downsides with increasing deflate buffer size, except extra load on server memory, and if anyone have input on this issue, it would be very much appreciated to let us know about.

    0 讨论(0)
  • 2020-12-08 00:58

    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

    0 讨论(0)
  • 2020-12-08 01:02

    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.

    0 讨论(0)
  • 2020-12-08 01:07

    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

    0 讨论(0)
提交回复
热议问题