我正在使用XMLHttpRequest和Range标头以5 MB的块下载一个~50MB的文件。事情很有效,除了检测我下载最后一个块时。
以下是第一个块的请求和响应的屏幕截图。请注意,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
对象有什么不明白的地方吗?
甚至 weirder 甚至可以按预期在Firefox 30中运行:
因此,在xhr.responseText.length
与Content-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出现故障。这应该作为错误提交吗?在哪里?
答案 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-Length
和Content-Range
标头,并将其替换为您的请求参数生成的标头。鉴于我无法自己重现这个问题,以下只是猜测:
resource_size_
变量在您的情况下必须具有错误的值,大于所请求文件的实际大小。此变量是根据请求的第一个块中的Content-Range
标头确定的,也许您在那里缓存了一个服务器响应,表明文件较大。