13

使用 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 世界中更加自如,与 readfile() 等效的 dotNet 立即将整个文件推送到 IIS 输出缓冲区,因此 ASP.NET 执行管道可以独立于文件的传递完成到最终客户端... PHP + Apache 是否有与此行为等效的方法?

4

7 回答 7

9

在执行 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 服务器您已经完成了安全检查,现在他可以发送文件了。

于 2012-08-03T11:59:09.207 回答
9

您可以做一些事情(我没有报告您需要发送的所有标头,这些标头可能与您当前在脚本中拥有的标头相同):

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);
于 2012-10-19T13:52:01.890 回答
6

该脚本将一直运行,直到用户完成下载文件。最简单、最有效、最有效的解决方案是重定向用户:

header("Location: /real/path/to/file");
exit;

但这可能会揭示文件的位置。使用 .htaccess 文件对可能不会被所有人下载的文件进行密码保护是个好主意,但也许您使用数据库来确定访问权限,这是没有选择的。

另一种可能的解决方案是将 PHP 的最大执行时间设置为 0,从而禁用限制:

set_time_limit(0);

不过,您的主机可能不允许这样做。PHP 也先将文件读入内存,然后通过 Apache 的输出缓冲区,最后到达网络。让用户直接下载文件效率更高,并且没有 PHP 的最大执行时间等限制。

编辑:你从 iPhone 用户那里收到很多抱怨的原因可能是他们的连接速度较慢(例如 3G)。

于 2012-08-02T22:33:41.640 回答
3

通过php下载文件效率不高,使用重定向是要走的路。如果您不想公开文件的位置,或者文件不在公共位置,那么请查看内部重定向,这里有一篇关于它的帖子,我可以告诉 Apache 从 PHP 进行内部重定向吗?

于 2012-08-02T22:32:48.087 回答
3

尝试使用 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);
于 2017-02-21T16:29:34.287 回答
0

在 Apache 下,有一个很好的 elgant 解决方案,根本不涉及 php:

只需将 .htaccess 配置文件放入包含要提供下载的文件的文件夹中,其中包含以下内容:

<Files *.*>
ForceType applicaton/octet-stream
</Files>

这告诉 Apache 提供此文件夹(及其所有子文件夹)中的所有文件以供下载,而不是直接在浏览器中显示它们。

于 2016-04-22T11:53:30.973 回答
-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;
}
?>
于 2012-08-02T23:15:36.740 回答