为什么ProgressEvent.lengthComputable是false?

19
我正在使用XMLHttpRequest在Google Chrome、Safari和Firefox中加载JSON文件。在这三个浏览器中,我都能够正确接收到ProgressEvent,其中包含了.loaded属性的正确值。然而,.lengthComputable属性的值是false,而.total属性的值是零。我已经检查了HTTP头部分的Content-Length是否被发送并且是否正确,结果是正确的。响应正被gzip编码,但是Content-length正确显示了编码后的长度(在解压缩之前)。
为什么我的ProgressEvent中没有总长度可用呢?
以下是HTTP头:
HTTP/1.1 200 OK
ETag: "hKXdZA"
Date: Wed, 20 Jun 2012 20:17:17 GMT
Expires: Wed, 20 Jun 2012 20:17:17 GMT
Cache-Control: private, max-age=3600
X-AppEngine-Estimated-CPM-US-Dollars: $0.000108
X-AppEngine-Resource-Usage: ms=2 cpu_ms=0 api_cpu_ms=0
Content-Type: application/json
Content-Encoding: gzip
Server: Google Frontend
Content-Length: 621606

注意:文件是通过Google App Engine提供的。

以下是JavaScript代码:

var req;
if (window.XMLHttpRequest){
    req = new XMLHttpRequest();
    if(req.overrideMimeType){
        req.overrideMimeType( "text/json" );
    }
}else{
    req = new ActiveXObject('Microsoft.XMLHTTP');
}

// Listen for progress events
req.addEventListener("progress", function (event) {
    console.log(event, event.lengthComputable, event.total);
    if (event.lengthComputable) {
        self.progress = event.loaded / event.total;
    } else if (this.explicitTotal) {
        self.progress = Math.min(1, event.loaded / self.explicitTotal);
    } else {
        self.progress = 0;
    }
    self.dispatchEvent(Breel.Asset.ON_PROGRESS);
}, false);

req.open('GET', this.url);
注意:那段代码中的console.log正在显示数百个带有最新的.loaded的事件,但是.lengthComputable始终为false,并且.total始终为零。self指的是负责此XMLHttpRequest的对象。 答案:注意:那段代码中的console.log正在显示数百个带有最新的.loaded的事件,但是.lengthComputable始终为false,并且.total始终为零。self指的是负责此XMLHttpRequest的对象。

1
你能让我们看看你的 JavaScript 代码吗?它是用来查看这些数据的吗? - tkone
1
你尝试在非 Google 应用引擎服务器上使用过吗?如果 lengthComputable 为 false,则 xhr 对象不知道文件的长度。我们在这里使用 GAE,并且大部分功能都存在严重问题,这并不令人惊讶。 - tkone
我已经尝试了另一个服务器,但它给我分块编码,因此没有Content-Length。在这种情况下,lengthComputable == false是有意义的。我以后会再尝试另一个。从头文件来看,GAE的行为似乎是正常的,但我和你一样怀疑它。 - Simon Cave
查看其兼容性:https://developer.mozilla.org/zh-CN/docs/Web/API/ProgressEvent.lengthComputable - bagz_man
5个回答

19

如果在XMLHttpRequestProgressEvent事件中,lengthComputable属性的值为false,那么这意味着服务器没有在响应中发送Content-Length头。

如果您正在使用nginx作为代理服务器,这可能是罪魁祸首,特别是如果它没有将Content-Length头从上游服务器通过代理服务器传递到浏览器。


如果您设置了一个被忽略的内容长度头,则还必须设置响应的缓冲区大小,以便能够容纳您发送的字节。否则,在HTTP 1.1中它将恢复响应分块。 - J. Allen
2
或者,许多年后,在Chrome中使用gzip压缩的内容时,即使已经设置了正确的标头。 :-( - Arjan

12

2017年,Firefox中一切正常,但Chrome不显示gzip压缩内容的进度条。

这似乎是由于规范曾经不清楚loadedtotal指的是压缩还是未压缩的内容所致。2014年6月26日起,XMLHttpRequest规范明确指出它们应该指代发送(已压缩)的内容:

6.1. 使用ProgressEvent接口触发事件

[...] 给定发送长度,[...]触发事件[...],ProgressEventloaded属性初始化为发送,如果长度不为0,则lengthComputable属性初始化为真,total属性初始化为长度

然而,2015年的Chromium bug报告 "XHR的进度事件应处理gzipped内容" 说明事情是不同的,并声明:

当编码时,total仍然为0,lengthComputable未设置。

事件本身仍然被触发,而event.loaded仍然有值。但是Chrome正在实时解压缩gzip的内容,并且(今天)将loaded设置为解压后的长度,而不是传输长度。这不能与Content-Length标头的值进行比较,因为那是压缩内容的长度,所以loaded将变得大于内容长度。

最好的情况下,可以假设一些压缩因子来将loadedContent-Length进行比较,或者让服务器添加一些自定义标头来提供原始长度或真实的压缩因子,假设Chrome的实时解压缩不会改变。

我不知道Chrome对于Content-Encoding的其他值做了什么。


至少:我认为event.loaded的值代表解压后的大小(而不是传输长度)与https://crbug.com/763700无关... - Arjan

9

使用req.upload.addEventListener进行上传

req.addEventListener 事件的lengthComputable属性始终为false

req.upload.addEventListener("progress", function (event) {
    console.log(event, event.lengthComputable, event.total);
    if (event.lengthComputable) {
        self.progress = event.loaded / event.total;
    } else if (this.explicitTotal) {
        self.progress = Math.min(1, event.loaded / self.explicitTotal);
    } else {
        self.progress = 0;
    }
    self.dispatchEvent(Breel.Asset.ON_PROGRESS);
}, false);

我曾经看到req.addEventListenerevent.lengthComputable也为真。但对于大请求,它似乎是假的:我尝试过一个10MB的JPG。虽然我没有进行广泛的测试,但你可能是正确的,req.uploadlengthComputable更常为真。 - trysis
1
...但正如名称所示:这是用于从浏览器上传到服务器的,而不是用于从服务器下载到浏览器的。 - Arjan

2

只需在PHP或Apache/Nginx中进行设置即可。

header("Content-Encoding: none");

问题已解决。

0

在控制器代码中设置Content-Length。这将设置event.lengthComputable

var fileSize = new FileInfo(filePathAbs).Length;
context.Response.AddHeader("Content-Length", fileSize.ToString());
context.Response.AddHeader("content-disposition", "inline;filename=" + fileName);

var req = new XMLHttpRequest();

req.open('GET', '/EReader/GetDocument?p=@Model.EncodedTempFilePath');
req.responseType = "arraybuffer";
req.onprogress = function (event) { 
    if (event.lengthComputable) {
        percentage = Math.floor(event.loaded * 100 / event.total); // give the percentage
        var elem = document.getElementById("bar");
        elem.style.width = percentage + '%';
    }
};

你好,你只需要检查一下是否是基于Chrome的浏览器,通过检查uaData("用户代理数据")是否可用。内容长度信息由event.loaded和event.total设置。然后你可以以任何方式获取信息,通过设置(event.lengthComputable || !chrome)。对我来说很有效。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接