Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apache/PHP large file download (>2Gb) failing

Tags:

php

apache

I'm using a PHP script to control access to download files. This works fine for anything under 2Gb but fails for larger files.

  • Apache and PHP are both 64bit
  • Apache will allow the file to be downloaded if accessed directly (which I can't allow)

The guts of the PHP (ignoring the access control):

if (ob_get_level())  ob_end_clean();

error_log('FILETEST: '.$path.' : '.filesize($path));
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($path));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
exit;

The error log shows the file size fine

[Tue Apr 08 11:01:16 2014] [error] [client *.*.*.*] FILETEST: /downloads/file.name : 2251373807, referer: http://myurl/files/

But the access log has a negative size:

 *.*.*.* - - [08/Apr/2014:11:01:16 +0100] "GET /files/file.name HTTP/1.1" 200 -2043593489 "http://myurl/files/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0"

And so browsers refuse to download the file. In fact, using wget, it's not sending anything:

$ wget -S -O - http://myurl/files/file.name
--2014-04-08 11:33:38--  http://myurl/files/file.name
HTTP request sent, awaiting response... No data received.
Retrying.
like image 463
Rick Avatar asked Apr 08 '14 10:04

Rick


1 Answers

Try to read the file in chunks and expose them to the browser instead of filling your local memory with 2GB and flushing all at once.

Replace readfile($path); by:

@ob_end_flush();
flush();

$fileDescriptor = fopen($file, 'rb');

while ($chunk = fread($fileDescriptor, 8192)) {
    echo $chunk;
    @ob_end_flush();
    flush();
}

fclose($fileDescriptor);
exit;

8192 bytes is a critical point in some cases, refere to php.net/fread.

Adding some microtime variables (and comparing with the pointer position of the file descriptor) will also allow you to controll the maximum speed of the download.

*(Flushing the output buffer also slightly depends on the webserver, use those commands to be sure it at least tries to flush as much as possible.)

like image 62
Daniel W. Avatar answered Nov 15 '22 17:11

Daniel W.