当使用readfile() - 在Apache上使用PHP时 - 文件会立即读入Apache的输出缓冲区并完成PHP脚本执行,或者PHP脚本执行是否等待客户端完成下载文件(或服务器)超时,以先发生者为准)?
较长的背景故事:
我有一个包含大量mp3文件的网站(当地教会的布道)。并非允许下载音频存档中的所有文件,因此/sermon/{filename}.mp3路径将被重写为真正执行/sermon.php?filename={filename},如果允许下载该文件,则内容类型设置为“audio / mpeg”,文件使用readfile()流出。我一直在接受投诉(几乎全部来自通过3G下载流媒体的iPhone用户)文件没有完全下载,或者他们在大约10或15分钟后切断了。当我从带有readfile()的文件流式传输切换到简单地重定向到文件 - header(“Location:$ file_url”); - 所有投诉都消失了(我甚至与一些用户进行了核实,这些用户可以根据需要可靠地重现问题)。
这让我怀疑当使用readfile()时,PHP脚本引擎正在使用,直到文件被完全下载,但我找不到任何确认或否认这个理论的引用。我承认我在ASP.NET世界中更像是家,而dotNet等效的readfile()会立即将整个文件推送到IIS输出缓冲区,因此ASP.NET执行管道可以独立于文件的传递而完成到最终客户端......是否有与PHP + Apache相同的行为?
答案 0 :(得分:7)
执行readfile()时,您可能仍然有PHP output buffering处于活动状态。检查:
if (ob_get_level()) ob_end_clean();
或
while (ob_get_level()) ob_end_clean();
这样只剩下输出缓冲区应该是apache的输出缓冲区,请参阅SendBufferSize进行apache调整。
修改强>
您还可以查看mod_xsendfile(an SO post on such usage, PHP + apache + x-sendfile),这样您只需告诉您已完成安全检查的网络服务器,现在他就可以发送文件了。
答案 1 :(得分:7)
您可以做的一些事情(我没有报告您需要发送的所有标题,这些标题可能与您目前在脚本中的标题相同):
set_time_limit(0); //as already mention
readfile($filename);
exit(0);
或
passthru('/bin/cat '.$filename);
exit(0);
或
//When you enable mod_xsendfile in Apache
header("X-Sendfile: $filename");
或
//mainly to use for remove files
$handle = fopen($filename, "rb");
echo stream_get_contents($handle);
fclose($handle);
或
$handle = fopen($filename, "rb");
while (!feof($handle)){
//I would suggest to do some checking
//to see if the user is still downloading or if they closed the connection
echo fread($handle, 8192);
}
fclose($handle);
答案 2 :(得分:4)
脚本将一直运行,直到用户完成下载文件。最简单,最有效和最可靠的解决方案是重定向用户:
header("Location: /real/path/to/file");
exit;
但这可能会揭示文件的位置。使用.htaccess文件密码保护所有人可能无法下载的文件是个好主意,但也许您使用数据库来确定访问权限,这是不可取的。
另一种可能的解决方案是将PHP的最大执行时间设置为0,从而禁用限制:
set_time_limit(0);
但是你的主人可能不允许这样做。 PHP首先将文件读入内存,然后通过Apache的输出缓冲区,最后进入网络。让用户直接下载文件效率更高,并且没有像最大执行时间那样的PHP限制。
编辑:你从iPhone用户那里得到很多投诉的原因可能是他们的连接速度较慢(例如3G)。
答案 3 :(得分:2)
答案 4 :(得分:1)
尝试使用stream_copy_to_stream()代替。我发现问题比readfile()少。
set_time_limit(0);
$stdout = fopen('php://output', 'w');
$bfname = basename($fname);
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$bfname\"");
$filein = fopen($fname, 'r');
stream_copy_to_stream($filein, $stdout);
fclose($filein);
fclose($stdout);
答案 5 :(得分:0)
在Apache下,有一个很好的解决方案,根本不涉及php:
只需将.htaccess配置文件放入包含要提供下载的文件的文件夹中,即可使用以下内容:
var result = studentList
.Join(markList, m => m.Id, s => s.Sid, (s, m) => new { s, m })
.GroupBy(t => t.s)
.Select(g => new
{
Student = g.Key,
MarksCount = g.Count(),
Marks = g.Select(x => x.m).ToList()
})
.ToList();
这告诉Apache提供此文件夹(及其所有子文件夹)中的所有文件以供下载,而不是直接在浏览器中显示它们。
答案 6 :(得分:-1)
见以下网址
http://php.net/manual/en/function.readfile.php
<?php
$file = 'monkey.gif';
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
?>