mod_xsendfile Firefox恢复问题

时间:2014-03-25 10:43:53

标签: apache firefox x-sendfile

我们正在尝试将mod_xsendfile与Apache一起使用,以有效地处理大文件下载(> 1 GB)。安装后,配置如下:

<IfModule mod_xsendfile.c>
<Directory "/abs_path/to/dl">
    XSendFile on
    XSendFilePath /abs_path/to/files_dir
</Directory> 
</IfModule>

下载脚本没什么特别的,只是检查是否存在要下载的文件,并根据文档设置标题,如下所示:

header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header("X-Sendfile: " . $file);

不间断的下载可以与我们测试过的任何用户代理一起使用,并且HTTP-Range可以很好地处理除Firefox以外的所有用户(测试版本27和28)。 Firefox可以暂停下载,但每次都恢复失败。

这些是使用Live HTTP标头扩展程序捕获的http标头:

初次下载:

http://www.oursite.com/dl/test-xs.php?ID=TestFileID

GET /dl/test-xs.php?ID=TestFileID HTTP/1.1
Host: www.oursite.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: some cookie string...
Connection: keep-alive

HTTP/1.1 200 OK
Date: Tue, 25 Mar 2014 10:22:46 GMT
Server: Apache
X-Powered-By: PHP/5.3.28
Content-Disposition: attachment; filename="TestFile.ext"
Last-Modified: Sun, 02 Mar 2014 18:20:36 GMT
Content-Length: 84406272
Connection: close
Content-Type: application/octet-stream

...当Firefox尝试恢复时:

http://www.oursite.com/dl/test-xs.php?ID=TestFileID

GET /dl/test-xs.php?ID=TestFileID HTTP/1.1
Host: www.oursite.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: same cookie string...
Connection: keep-alive
Range: bytes=11238434-
If-Unmodified-Since: Sun, 02 Mar 2014 18:20:36 GMT

...服务器返回404

HTTP/1.1 404 Not Found
Date: Tue, 25 Mar 2014 10:23:03 GMT
Server: Apache
X-Powered-By: PHP/5.3.28, PHP/5.3.28
Content-Disposition: attachment; filename="TestFile.ext"
X-Sendfile: /abs_path/to/files_dir/TestFile.ext
X-Pingback: http://www.oursite.com/xmlrpc.php
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

...这显然导致Firefox无法恢复下载(现在只能取消并从头开始重新启动)。

考虑到其他浏览器和下载管理器中的所有内容都符合预期,我们尝试过:

  1. 有没有人经历过类似的行为?
  2. 任何人都可以解释或指出它的潜在原因 下载脚本或配置代码?
  3. 修改

    经过一些更多的测试后发现问题归结为Firefox在恢复下载时发送的If-Unmodified-Since标题。尽管此标头已正确设置为从Apache收到的Last-Modified响应标头的值,但服务器由于某种原因不喜欢它,并以404响应。通过更改.htaccess:

    从请求中删除If-Unmodified-Since标头
    <Files test-xs.php>
      RequestHeader unset If-Unmodified-Since
    </Files>
    

    ...简历在包括Firefox在内的所有地方都能正常运行。

    如果要同时修改要下载的文件,这种方法当然是不正确的,但是它为我们完成了工作,因为我们使用不同的约定来提供同一文件的较新版本。

    这显然更像是一个黑客而不是正确的实现,所以不确定这是否应该被标记为答案,我将其留作原始问题的补充。

    显然出现了新问题:

    • 有没有更好的方法来解决这个问题?
    • 这是mod_xsendfile中的错误吗?

2 个答案:

答案 0 :(得分:1)

@alternize's solution大部分都是正确的,但提供的代码段为:

SetEnvIf Range .+ HAS_RANGE_HEADER
RequestHeader unset If-Range env=!HAS_RANGE_HEADER
RequestHeader unset If-Unmodified-Since env=!HAS_RANGE_HEADER

实际上不会取消包含Range标头的请求的指定标头。

”否定匹配,因此上述代码段实际上会为所有请求取消设置这些标题,但带有Range标题的请求。

要从具有If-Range标头的请求中正确删除If-Unmodified-SinceRange标头,您可以使用相同的指令,但删除这样的否定:

SetEnvIf Range .+ HAS_RANGE_HEADER
RequestHeader unset If-Range env=HAS_RANGE_HEADER
RequestHeader unset If-Unmodified-Since env=HAS_RANGE_HEADER

这已在apache 2.2.15上得到确认。

答案 1 :(得分:0)

mod_xsendfile似乎不适用于浏览器发送的一些缓存标头。例如,当使用“Range”标题恢复下载时,chrome会发送“If-Range”标题:

第一个请求标题:

GET /foo.webm

Range: bytes=0-

第一个回复标题:

206 Partial Content

Content-Length: 54376097
Content-Range: bytes 0-54376096/54376097
Content-Type: video/webm
ETag: "78976c9d1a595cba56e24bec6f2f1178"

=&GT;视频开始播放

现在用户将擦洗到文件中的稍后位置

第二个请求标题:

GET /foo.webm

If-Range: "78976c9d1a595cba56e24bec6f2f1178"
Range: bytes=54373845-54376096

第二个回复标题:

200 OK

Content-Length: 54376097
ETag: "78976c9d1a595cba56e24bec6f2f1178"

=&GT;发送整个文件而不是请求的字节范围

解决方法

如果存在“Range”标题,

取消设置有问题的请求标头:

SetEnvIf Range .+ HAS_RANGE_HEADER
RequestHeader unset If-Range env=!HAS_RANGE_HEADER
RequestHeader unset If-Unmodified-Since env=!HAS_RANGE_HEADER