This seems to be one of those areas of programming where there is no hard and fast rule as to what works. I tried countless permutations of the tricks I found on this page and elsewhere on the web until I finally found something that works. This might be overkill, but it works so I'm leaving it as is. So far this was tested on Mac/Apache and Win32/Apache servers, using Firefox, Safari and Chrome clients. I've tested this with files up to 151MB and as small as a few Kb. As usual, YMMV.
<?php
$realpath = "/some/absolute/path.exe";
$mtime = ($mtime = filemtime($realpath)) ? $mtime : gmtime();
$size = intval(sprintf("%u", filesize($realpath)));
// Maybe the problem is we are running into PHPs own memory limit, so:
if (intval($size + 1) > return_bytes(ini_get('memory_limit')) && intval($size * 1.5) <= 1073741824) { //Not higher than 1GB
ini_set('memory_limit', intval($size * 1.5));
}
// Maybe the problem is Apache is trying to compress the output, so:
@apache_setenv('no-gzip', 1);
@ini_set('zlib.output_compression', 0);
// Maybe the client doesn't know what to do with the output so send a bunch of these headers:
header("Content-type: application/force-download");
header('Content-Type: application/octet-stream');
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != false) {
header("Content-Disposition: attachment; filename=" . urlencode(basename($F['FILE']['file_path'])) . '; modification-date="' . date('r', $mtime) . '";');
} else {
header("Content-Disposition: attachment; filename=\"" . basename($F['FILE']['file_path']) . '"; modification-date="' . date('r', $mtime) . '";');
}
// Set the length so the browser can set the download timers
header("Content-Length: " . $size);
// If it's a large file we don't want the script to timeout, so:
set_time_limit(300);
// If it's a large file, readfile might not be able to do it in one go, so:
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
if ($size > $chunksize) {
$handle = fopen($realpath, 'rb');
$buffer = '';
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
}
fclose($handle);
} else {
readfile($realpath);
}
// Exit successfully. We could just let the script exit
// normally at the bottom of the page, but then blank lines
// after the close of the script code would potentially cause
// problems after the file download.
exit;
?>
[EDIT BY danbrown AT php DOT net: An addendum to this note was submitted by the original author on 04-MAR-2011. It follows.]
I started running into trouble when I had really large files being sent to clients with really slow download speeds. In those cases, the script would time out and the download would terminate with an incomplete file. I am dead-set against disabling script timeouts - any time that is the solution to a programming problem, you are doing something wrong - so I attempted to scale the timeout based on the size of the file. That ultimately failed though because it was impossible to predict the speed at which the end user would be downloading the file at, so it was really just a best guess so inevitably we still get reports of script timeouts.
Then I stumbled across a fantastic Apache module called mod_xsendfile (https://tn123.org/mod_xsendfile/ (binaries) or https://github.com/nmaier/mod_xsendfile (source)). This module basically monitors the output buffer for the presence of special headers, and when it finds them it triggers apache to send the file on its own, almost as if the user requested the file directly. PHP processing is halted at that point, so no timeout errors regardless of the size of the file or the download speed of the client. And the end client gets the full benefits of Apache sending the file, such as an accurate file size report and download status bar.
The code I finally ended up with is too long to post here, but in general is uses the mod_xsendfile module if it is present, and if not the script falls back to using the code I originally posted. You can find some example code at https://gist.github.com/854168
--was--
This seems to be one of those areas of programming where there is no hard and fast rule as to what works. I tried countless permutations of the tricks I found on this page and elsewhere on the web until I finally found something that works. This might be overkill, but it works so I'm leaving it as is. So far this was tested on Mac/Apache and Win32/Apache servers, using Firefox, Safari and Chrome clients. I've tested this with files up to 151MB and as small as a few Kb. As usual, YMMV.
<?php
$realpath = "/some/absolute/path.exe";
$mtime = ($mtime = filemtime($realpath)) ? $mtime : gmtime();
$size = intval(sprintf("%u", filesize($realpath)));
// Maybe the problem is we are running into PHPs own memory limit, so:
if (intval($size + 1) > return_bytes(ini_get('memory_limit')) && intval($size * 1.5) <= 1073741824) { //Not higher than 1GB
ini_set('memory_limit', intval($size * 1.5));
}
// Maybe the problem is Apache is trying to compress the output, so:
@apache_setenv('no-gzip', 1);
@ini_set('zlib.output_compression', 0);
// Maybe the client doesn't know what to do with the output so send a bunch of these headers:
header("Content-type: application/force-download");
header('Content-Type: application/octet-stream');
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != false) {
header("Content-Disposition: attachment; filename=" . urlencode(basename($F['FILE']['file_path'])) . '; modification-date="' . date('r', $mtime) . '";');
} else {
header("Content-Disposition: attachment; filename=\"" . basename($F['FILE']['file_path']) . '"; modification-date="' . date('r', $mtime) . '";');
}
// Set the length so the browser can set the download timers
header("Content-Length: " . $size);
// If it's a large file we don't want the script to timeout, so:
set_time_limit(300);
// If it's a large file, readfile might not be able to do it in one go, so:
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
if ($size > $chunksize) {
$handle = fopen($realpath, 'rb');
$buffer = '';
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
}
fclose($handle);
} else {
readfile($realpath);
}
// Exit successfully. We could just let the script exit
// normally at the bottom of the page, but then blank lines
// after the close of the script code would potentially cause
// problems after the file download.
exit;
?>
http://php.net/manual/en/function.readfile.php
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.