I'm trying to send a large file through readfile()
.
However, nothing is sent to the browser and readfile()
returns 0
(not false
!).
The file I'm trying to send is 4GiB of size and readable by PHP.
I am setting set_time_limit(0)
to allow a lengthy download process.
I have tried to send the file in a while-loop contraption with fread()
in 4K chunks and echo
, but that aborts randomly (without error) after 500 - 2500 MiB downloaded and never manages to complete the download.
The following test code
$f = '/var/www/tmp/largefile.dat';
var_dump(file_exists($f));
var_dump(is_readable($f));
var_dump(filesize($f));
$rf = readfile($f);
var_dump($rf);
produces the following output:
bool(true) bool(true) int(4294967296) int(0)
The testfile was created using the following command:
dd if=/dev/zero of=largefile.dat bs=1M count=4096
What am I doing wrong and how do I fix it?
Edit 2014-07
Upgrading to a new Apache2 version has solved the problem for now.
PHP readfile uses the page buffer to store the file before send it. If it fails due to insufficient memory it does not throw a memory error only fail.
Ensure that the page that performing the download is not using page buffer( http://us1.php.net/manual/es/ref.outcontrol.php)
Also is better for big files to use fopen an fread to read the file and put the content.
Also with fread you can make some code to allow the download to be resumed.
Here there's a good example: Resumable downloads when using PHP to send the file?
To disable ouput buffering you should try to clean and end buffer before process start with ob_end_clean() or disable the output buffering using ini_set('output_buffering', 0);
In the readfile documentation there's an example about how to use fread for long files instead of readfile:
http://ca2.php.net/manual/en/function.readfile.php#48683
function readfile_chunked($filename,$retbytes=true) {
$chunksize = 1*(1024*1024); // how many bytes per chunk
$buffer = '';
$cnt =0;
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
Also there's an example with partial download support in the same page:
function smartReadFile($location, $filename, $mimeType='application/octet-stream') {
if(!file_exists($location)) {
header ("HTTP/1.0 404 Not Found");
return;
}
$size=filesize($location);
$time=date('r',filemtime($location));
$fm=@fopen($location,'rb');
if(!$fm) {
header ("HTTP/1.0 505 Internal server error");
return;
}
$begin=0;
$end=$size;
if(isset($_SERVER['HTTP_RANGE'])) {
if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)){
$begin=intval($matches[0]);
if(!empty($matches[1])){ $end=intval($matches[1]); }
}
}
if($begin>0||$end<$size){
header('HTTP/1.0 206 Partial Content');
}else{
header('HTTP/1.0 200 OK');
}
header("Content-Type: $mimeType");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Range: bytes $begin-$end/$size");
header("Content-Disposition: inline; filename=$filename");
header("Content-Transfer-Encoding: binary\n");
header("Last-Modified: $time");
header('Connection: close');
$cur=$begin;
fseek($fm,$begin,0);
while(!feof($fm)&&$cur<$end&&(connection_status()==0)) {
print fread($fm,min(1024*16,$end-$cur));
$cur+=1024*16;
}
}