XMLHttpRequest和Chrome开发人员工具不会说同样的话

时间:2014-07-14 20:12:08

标签: javascript ajax google-chrome firefox xmlhttprequest

我正在使用XMLHttpRequest和Range标头以5 MB的块下载一个~50MB的文件。事情很有效,除了检测我下载最后一个块时。

以下是第一个块的请求和响应的屏幕截图。请注意,Content-Length为1024 * 1024 * 5(5 MB)。另请注意,服务器使用前5 MB正确响应,并在Content-Range标头中正确指定整个文件的大小(在/之后):

first chunk

当我将响应正文复制到文本编辑器(Sublime)时,我只得到5,242,736个字符而不是Content-Length所示的预期5,242,880:

actual length

为什么缺少144个字符?对于下载的每个块都是如此,尽管确切的差异会有所不同。

然而,特别奇怪的是 last 块。服务器响应文件的最后~2.9 MB(而不是整个5 MB),并且在响应中显然正确地指出了这一点:

last chunk

请注意,我正在请求接下来的5 MB(即使它超出了总文件大小)。没什么大不了的,服务器会响应文件的最后一部分,标题表示返回的实际字节范围。

但是真的吗?

当我使用Javascript调用xhr.getResponseHeader("Content-Length")时,我在Chrome中看到了另一个故事:

Chrome dev tools don't agree

XMLHttpRequest对象告诉我,在文件末尾之外还下载了另外5 MB。我对xhr对象有什么不明白的地方吗?

甚至 weirder 甚至可以按预期在Firefox 30中运行:

Firefox XHR works

因此,在xhr.responseText.lengthContent-Length不匹配且xhr对象与网络工具之间未达成一致的标题之间,我不知道如何解决这个问题。< / p>

造成这些差异的原因是什么?

更新我已经确认服务器本身正在正确发送请求,尽管最后一个块的请求中有超出范围的标头。这是原始HTTP请求的输出,这要归功于良好的'ol telnet

HTTP/1.1 206 Partial Content
Server: nginx/1.4.5
Date: Mon, 14 Jul 2014 21:50:06 GMT
Content-Type: application/octet-stream
Content-Length: 2987360
Last-Modified: Sun, 13 Jul 2014 22:05:10 GMT
Connection: keep-alive
ETag: "53c30296-2fd9560"
Content-Range: bytes 47185920-50173279/50173280

因此看起来Chrome出现故障。这应该作为错误提交吗?在哪里?

1 个答案:

答案 0 :(得分:4)

主要问题是您正在将二进制数据作为文本读取。请注意,服务器以Content-Type: application/octet-stream响应,但未明确指定编码 - 在这种情况下,浏览器通常会假设数据以UTF-8编码。虽然长度大部分不变(值为0到127的字节被解释为UTF-8中的单个字符,而具有较高值的​​字节通常会被替换字符replaced替换),您的二进制文件肯定会包含一些有效的多个-byte UTF-8序列 - 这些将组合成一个字符。这解释了为什么responseText.length与从服务器接收的字节数不匹配。

现在您可以使用request.overrideMimeType() method强制某些特定编码,ISO 8859-1特别有意义,因为前256个Unicode代码点与ISO 8859-1相同:

request.overrideMimeType("application/octet-stream; charset=iso-8859-1");

这应确保一个字节始终被解释为一个字符。更好的方法是将服务器响应存储在明确用于处理二进制数据的ArrayBuffer中。

var request = new XMLHttpRequest();
request.open(...);
request.responseType = "arraybuffer";
request.send();

...

var array = new Uint8Array(request.response);
alert("First byte has value " + array[0]);
alert("Array length is " + array.length);

根据MDN, 从Chrome 10,Firefox 6和Internet Explorer 10开始支持responseType = "arraybuffer"。另请参阅:Typed arrays

附注:Firefox还支持responseType = "moz-chunked-text"responseType = "moz-chunked-arraybuffer",从Firefox 9开始,允许以块的形式接收数据而无需求助于远程请求。 Chrome似乎并不打算实施它,而是working实施Streams API

修改:我无法重复您的Chrome向您说明响应标题的问题,至少在没有您的代码的情况下。但是,负责的代码应该是partial_data.cc中的此功能:

// We are making multiple requests to complete the range requested by the user.
// Just assume that everything is fine and say that we are returning what was
// requested.
void PartialData::FixResponseHeaders(HttpResponseHeaders* headers,
                                     bool success) {
  if (truncated_)
    return;

  if (byte_range_.IsValid() && success) {
    headers->UpdateWithNewRange(byte_range_, resource_size_, !sparse_entry_);
    return;
  }

此代码将删除服务器返回的Content-LengthContent-Range标头,并将其替换为您的请求参数生成的标头。鉴于我无法自己重现这个问题,以下只是猜测:

  • 此代码路径似乎仅用于可以从缓存中满足的请求,因此我想如果清除缓存,事情将正常工作。
  • resource_size_变量在您的情况下必须具有错误的值,大于所请求文件的实际大小。此变量是根据请求的第一个块中的Content-Range标头确定的,也许您在那里缓存了一个服务器响应,表明文件较大。