当使用readfile() – 在Apache上使用PHP时,立即读入Apache的输出缓冲区并执行PHP脚本,或者执行PHP脚本,直到客户端完成下载文件(或服务器超时,以先发生者为准)?
logging器后台:
我有一个网站有大量的MP3文件(为当地教堂布道)。 并非audio档案中的所有文件都可以下载,所以/sermon/{filename}.mp3path被重写为真正执行/sermon.php?filename={filename},如果文件被允许下载,那么内容type设置为“audio / mpeg”,文件使用readfile()stream出。 我一直在接受投诉(几乎完全是通过3G下载stream式传输的iPhone用户)文件没有完全下载,或者大约10或15分钟后中断。 当我从一个readfile()stream出文件切换到简单的redirect到文件 – 头(“Location:$ file_url”); – 所有的投诉都已经消失了(我甚至查询了几个可以按需求重现问题的用户)。
这使我怀疑,当使用readfile()PHP脚本引擎正在使用,直到文件被完全下载,但我找不到任何参考证实或否认这个理论。 我承认在ASP.NET世界里我更像家,而readfile()的dotNet相当于将整个文件立即推送到IIS输出缓冲区,所以ASP.NET执行pipe道可以独立于文件的传递完成到最终客户端…是否有这样的行为等同于PHP + Apache?
你可以做一些事情(我不报告你需要发送的所有头文件可能与你目前在脚本中使用的相同):
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);
该脚本将一直运行,直到用户完成下载文件。 最简单,最有效,最可行的解决方案是重定向用户:
header("Location: /real/path/to/file"); exit;
但是这可能会显示文件的位置。 用一个.htaccess文件密码保护可能不会被所有人下载的文件是一个好主意,但也许你使用数据库来确定访问权限,这是不行的。
另一个可能的解决方案是将PHP的最大执行时间设置为0,从而禁用该限制:
set_time_limit(0);
不过,你的主人可能会拒绝这个。 另外PHP首先将文件读入内存,然后通过Apache的输出缓冲区,最后到达网络。 使用户直接下载文件效率更高,并没有PHP的限制,如最大的执行时间。
编辑:你从iPhone用户得到这个投诉很多的原因可能是他们有一个较慢的连接(如3G)。
执行readfile()时,您可能仍然有PHP输出缓冲处于活动状态。 检查:
if (ob_get_level()) ob_end_clean();
要么
while (ob_get_level()) ob_end_clean();
这种方式只剩下输出缓冲区应该是apache的输出缓冲区,请参阅Apache的调整SendBufferSize。
编辑
你也可以看一下mod_xsendfile ( 这个用法的SO帖子,PHP + apache + x-sendfile ),这样你只需告诉web服务器你已经完成了安全检查,现在他可以发送文件了。
通过PHP下载文件不是非常有效的,使用重定向是要走的路。 如果你不想暴露文件的位置,或文件不是在公共场所,然后看看内部重定向,这里是一个职位,谈论了一下, 我可以告诉Apache做一个PHP的内部重定向?
尝试使用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);
在Apache下,有一个不涉及php的优雅的解决方案:
只需将一个.htaccess配置文件放入包含要下载文件的文件夹中,其内容如下:
<Files *.*> ForceType applicaton/octet-stream </Files>
这告诉Apache提供该文件夹(及其所有子文件夹)中的所有文件供下载,而不是直接在浏览器中显示它们。
看到下面的网址
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; } ?>