如何在XHR onProgress函数中使用缩减/ gzipped内容?

Jim*_*zuk 42 javascript compression jquery xmlhttprequest

我之前看到过一些类似的问题,但我还没有找到一个完全描述我当前问题的问题,所以这里有:

我有一个页面,通过AJAX加载一个大的(介于0.5和10 MB之间)JSON文档,以便客户端代码可以处理它.加载文件后,我没有任何我不期望的问题.但是,下载需要很长时间,因此我尝试利用XHR Progress API呈现进度条,以向用户指示文档正在加载.这很好用.

然后,为了加快速度,我尝试通过gzip和deflate压缩服务器端的输出.这也很有效,但是,我的进度条停止了工作.

我已经研究了一段时间的问题,发现如果Content-Length没有使用请求的AJAX资源发送正确的头文件,onProgress事件处理程序就无法按预期运行,因为它不知道它在下载中有多远.发生这种情况时,lengthComputablefalse在事件对象上设置一个名为call的属性.

这是有道理的,所以我尝试使用未压缩和压缩的输出长度显式设置标头.我可以验证是否正在发送标头,我可以验证我的浏览器知道如何解压缩内容.但onProgress处理程序仍然报告lengthComputable = false.

所以我的问题是:有没有办法使用AJAX Progress API来压缩/缩小内容?如果是这样,我现在做错了什么?


这是资源在Chrome网络面板中的显示方式,表明压缩正在运行:

网络面板

这些是相关的请求标头,显示请求是AJAX并且Accept-Encoding设置正确:

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
Run Code Online (Sandbox Code Playgroud)

这些都是相关的响应报头,显示出Content-LengthContent-Type被正确设置:

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
Run Code Online (Sandbox Code Playgroud)

对于它的价值,我在标准(http)和安全(https)连接上尝试了这一点,没有区别:内容在浏览器中加载正常,但不由Progress API处理.


根据Adam的建议,我尝试将服务器端切换到gzip编码,但没有成功或改变.以下是相关的响应标头:

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
Run Code Online (Sandbox Code Playgroud)

重复一遍:内容正在被正确下载和解码,这只是我遇到麻烦的进度API.


按照Bertrand的要求,这是请求:

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

这是onProgress我正在使用的事件处理程序(它不是太疯狂):

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);
});
Run Code Online (Sandbox Code Playgroud)

Nat*_*Nat 14

您的解决方案稍微更优雅的变化是设置一个标题,如'x-decompressed-content-length'或HTTP响应中的任何内容,以及以字节为单位的内容的完整解压缩值,并从onProgress中的xhr对象读取它处理程序.

您的代码可能类似于:

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);
};
Run Code Online (Sandbox Code Playgroud)


Jim*_*zuk 9

我无法解决使用onProgress压缩内容本身的问题,但我提出了这个半简单的解决方法.简而言之:在HEAD请求的同时向服务器发送GET请求,并在有足够信息的情况下呈现进度条.


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);
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

然后使用它:

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

在服务器端,在请求和请求(应表示未压缩的内容长度)X-Content-Length上设置标头,并中止在请求上发送内容.GETHEADHEAD

在PHP中,设置标题如下:

header("X-Content-Length: ".strlen($payload));
Run Code Online (Sandbox Code Playgroud)

如果是HEAD请求,则中止发送内容:

if ($_SERVER['REQUEST_METHOD'] == "HEAD")
{
    exit;
}
Run Code Online (Sandbox Code Playgroud)

这是它在行动中的样子:

截图

HEAD在下面的屏幕截图中花费这么长时间的原因是因为服务器仍然需要解析文件以了解它有多长,但这是我可以肯定改进的东西,并且它肯定是从它的位置改进的.