We are trying to use mod_xsendfile with Apache to efficiently handle large file downloads (> 1 GB). After installation the configuration looks like:
<IfModule mod_xsendfile.c>
<Directory "/abs_path/to/dl">
XSendFile on
XSendFilePath /abs_path/to/files_dir
</Directory>
</IfModule>
The download script does nothing fancy, just checks for the existence of file to download and sets headers as per documentation, like this:
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header("X-Sendfile: " . $file);
Uninterrupted downloads work fine with any user agent we've tested with, and HTTP-Range does work fine with all but Firefox (tested versions 27 and 28). Firefox can pause a download, but resuming fails every time.
These are the http headers captured with Live HTTP headers extension:
Initial download:
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
... and when Firefox tries to resume:
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
... the server returns a 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
... which obviously causes Firefox to fail resuming the download (which can now only be cancelled and restarted from beginning).
Considering everything works as expected in other browsers and download managers we've tried:
- Has anybody experienced similar behavior?
- Can anyone explain or point out the potential cause of it in our download script or configuration code?
Edit:
After doing some more testing it turned out that the issue is down to If-Unmodified-Since
header being sent by Firefox when resuming the download. Even though this header is correctly set to the value of Last-Modified
response header received from Apache, the server does not like it for some reason and responds with 404
. After stripping the If-Unmodified-Since
header from request by changing the .htaccess:
<Files test-xs.php>
RequestHeader unset If-Unmodified-Since
</Files>
... resume works fine everywhere including Firefox.
This method of course is not correct if the file to download has been modified in the mean time, but it does the job for us because we use a different convention for serving newer versions of the same file.
This obviously feels more like a hack than a correct implementation, so not being sure if this should be marked as an answer, I'm leaving it as addition to original question.
New questions obviously arise:
- Is there a better way to overcome this issue?
- Is this a bug in mod_xsendfile?