PHP readfile()和大文件

时间:2012-08-02 22:25:16

标签: php apache

当使用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相同的行为?

7 个答案:

答案 0 :(得分:7)

执行readfile()时,您可能仍然有PHP output buffering处于活动状态。检查:

if (ob_get_level()) ob_end_clean();

while (ob_get_level()) ob_end_clean();

这样只剩下输出缓冲区应该是apache的输出缓冲区,请参阅SendBufferSize进行apache调整。

修改

您还可以查看mod_xsendfilean 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)

通过php下载文件非常有效,使用重定向是可行的方法。如果你不想暴露文件的位置,或者文件不在公共场所,那么请查看内部重定向,这里有一篇文章,稍微讨论一下,Can I tell Apache to do an internal redirect from PHP?

答案 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;
}
?>