【问题标题】:XMLHttpRequest and Chrome developer tools don't say the same thingXMLHttpRequest 和 Chrome 开发者工具不一样
【发布时间】:2014-07-14 20:12:07
【问题描述】:

我正在使用 XMLHttpRequest 和 Range 标头以 5 MB 的块下载约 50 MB 的文件。一切都很好,除了检测我何时下载了最后一个块。

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

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

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

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

请注意,我正在请求下一个 5 MB(即使它超出了总文件大小)。没什么大不了的,服务器以文件的最后一部分进行响应,并且标头指示返回的实际字节范围。

但真的吗?

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

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

更奇怪的是它可以在 Firefox 30 中按预期工作:

所以在xhr.responseText.lengthContent-Length 不匹配以及这些标头在xhr 对象和网络工具之间不一致,我不知道如何解决这个问题。

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

更新:我已经确认服务器本身正在正确发送请求,尽管最后一个块的请求中的 Range 标头超出了范围。这是原始 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 出现了故障。这应该作为错误提交吗?在哪里?

【问题讨论】:

  • xhr.responseText.length 是响应中的字符数,而不是 Content-Length 标头中指示的字节数。某些 unicode 字符(或强制转换为 unicode 的二进制位)每个字符使用多个字节。 chrome 可能会第二次猜测无效的范围标头(例如与文件结尾重叠的标头),firefox 也可能如此,但似乎只有一种方法(ff)适用于您的情况。修复 REQUEST 标头,然后重试。
  • 谢谢@dandavis。看我的更新。我直接在 telnet 中运行了请求,服务器的原始输出符合预期,这意味着(我认为?)Chrome 在发出 XMLHttpRequest 或其他东西时一定会出现故障......
  • 我建议 chrome 可能正在做一些特别的事情,这里没有显示,通过内部将请求与响应联系起来。看起来服务器的输出很好,但 chrome 也可能会考虑来自请求的输入(特别是范围 0-52/50),这告诉它期待更多。当您是浏览器时,有时聪明是愚蠢的。
  • @dandavis 我想我明白你在说什么,Chrome 可能会假设它不应该做的事情。但是......输入/请求不包括总文件大小:只有响应的 Content-Range 有。
  • 我要说的是,您在第一个屏幕截图中显示的 Range 请求标头(0-5242879 标头)可能会欺骗 chrome。为什么你每次都回复我不知道的回复,但如果我是你,我会尝试更改请求......

标签: javascript ajax google-chrome firefox xmlhttprequest


【解决方案1】:

主要问题是您将二进制数据作为文本读取。请注意,服务器以 Content-Type: application/octet-stream 响应,它没有明确指定编码 - 在这种情况下,浏览器通常会假定数据以 UTF-8 编码。虽然长度大部分不会改变(值 0 到 127 的字节在 UTF-8 中被解释为单个字符,而具有更高值的字节通常会被替换字符替换),您的二进制文件肯定会包含一些有效的 multi -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 9 开始,Firefox 还支持 responseType = "moz-chunked-text"responseType = "moz-chunked-arraybuffer",它们允许在不诉诸远程请求的情况下以块的形式接收数据。 Chrome 似乎不打算实施它,而是在实施Streams APIworking

编辑:我无法重现您的问题,即 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 标头确定的,也许您在那里缓存了一个服务器响应,表明文件更大。

【讨论】:

  • 感谢详细的解释!但该文件实际上是文本 (.csv),即使服务器说它是 application/octet-stream。应该没关系,因为文件中无论如何都没有超过 127 的字节......我主要关心的是 JavaScript 调用 xhr.getResponseHeader("Content-Length") 与“网络”选项卡对 Content-Length 所说的不匹配。这是Chrome中的错误吗? (FF也是一样)
  • @Matt:根据你的截图,Firefox 显示了正确的内容长度值。至于 Chrome,我倾向于不信任调试器。如果你做console.log(xhr.getResponseHeader("Content-Length"))怎么办?
  • (对,对不起,我的意思是 FF 似乎是正确的。)console.log 行在 Chrome 中显示与调试器相同(不正确)的值。
  • 太棒了,感谢您对此进行调查并深入研究 Chromium 源代码!这些细节可能至少会帮助我创建一个可重现的测试用例。这是一个巨大的帮助。
猜你喜欢
  • 2020-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-07
  • 2016-07-09
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
相关资源
最近更新 更多