使用PHP强制下载大文件

3

我的网站有许多用户报告下载一个大文件(80 MB)的问题。我正在使用头信息来强制下载。如果需要,我可以提供其他的php设置。我正在使用CakePHP框架,但这段代码是普通的php。我使用的是CentOS Linux上的apache服务器和来自media temple的专用虚拟服务器,并且php版本为5.2。您是否看到以下代码存在任何问题:

        set_time_limit(1500);
        header("Content-Type: application/octet-stream");
        header("Content-Disposition: attachment; filename=\"" . basename($file_path) . "\"");
        header("Content-Length: ".$content_length);
        header("Content-Transfer-Encoding: binary");
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Cache-Control: private', false);
        header('Pragma: public');
        header('Expires: 0');

        //Change this part
        $handle = fopen($file_path, 'rb');
        while (!feof($handle))
        {
            echo fread($handle, 4096);
            ob_flush();
            flush();
        }
        fclose($handle);
        exit;

基本上,报告的问题是下载开始后中途停止。我认为这可能是时间限制的问题,所以我添加了set_time_limit代码。之前我使用的是php readfile函数,但也没有顺利工作。


请问,如果您不介意的话,循环输出文件的目的是什么? - El Yobo
1
但是类似readfile的东西完全可以避免将其放入内存中(除非启用了输出缓冲-但即使您按块读取它,也会有同样的问题)。 - El Yobo
1
我倾向于避免使用PHP来输出大文件数据,而是使用它来管理创建/删除随机命名的符号链接到“隐藏”的存储路径,除非你需要安全性 :) - Scuzzy
@ElYobo,我认为两者都没有问题。通过PHP进行直接下载的渲染文件的趋势是使用readfile(如上所述,它是对输出缓冲区的直接转储)。两者都可以避免PHP完全加载文件并将其分段拆分(一个似乎在引擎内处理文件,另一个是由编码人员管理)。我的个人意见是,任选其一即可。我可能100%错误,但我从未遇到过任何问题(除了自己fopen时的手腕隧道症候群;-)。 - Brad Christie
@jimiyash 是的,Web服务器非常适合提供静态内容。 - Scuzzy
显示剩余6条评论
2个回答

4

PHP发起的http传输的问题在于它们很少支持部分请求:

GET /yourfile HTTP/1.1
Range: bytes=31489531-79837582

每当浏览器遇到传输问题时,它会尝试恢复下载。您的php脚本没有考虑到这一点(这不是简单的问题,所以没有人这样做)。
因此,真正要避免这种情况。将用户重定向到静态文件并让您的Web服务器处理它。如果需要处理授权,请使用诸如符号链接或重写规则之类的技巧来检查会话cookie甚至是静态权限文件(./allowed/178.224.2.55-file-1)。任何所需的额外HTTP头也可以类似地注入,或使用.meta文件。

有趣的想法;你知道这方面的具体例子吗? - El Yobo
1
@ElYobo:对于.htaccess权限技巧,一个简单的RewriteCond -f ./allow-%{REMOTE_ADDR}可能就足够了。Byte-Range支持在Nanoweb和PEAR HTTP_Server中都有,如果我没记错的话。但是快速谷歌搜索可以得到:http://www.coneural.org/florian/papers/04_byteserving.php - mario
很棒,谢谢。我需要比RewriteCond示例更多的安全性(例如,在代理后面有多个用户),但这篇论文很有趣。 - El Yobo
@jimiyash:我不确定将内容追加到.htaccess文件是否可靠。在某个时候,它会变得太大,你必须重置它,这可能会导致竞争条件。理想情况下,难以猜测和随机的符号链接文件名就足够了。但是,您也可以创建临时的每个用户目录,并使用单独的.htaccess白名单。 - mario
@jimiyash:你得试一试。这完全取决于你的具体用例。不要过度设计。如果简单的解决方案有副作用,你总可以加强它。 - mario
显示剩余3条评论

1

我并没有看到任何问题,但是为了保险起见,请尝试将 set_time_limit 放在 while 循环内部。这可以确保它们不会达到硬限制,并且只要客户端获取信息,就可以扩展时间限制。


你可以使用 set_time_limit(0) 来取消时间限制。 - alex
我倾向于认为,如果一个操作手(无论出于何种原因)放弃了控制权,那么这是一个糟糕的想法。我尽量让PHP有机会切断联系,否则(无论出于何种原因)就会出现问题,你会发现有一个线程一直处于休眠状态。 - Brad Christie
你认为我应该延长多少秒?也许5-10秒? - jimiyash
你下载 4096 字节需要多长时间?;-) 你可以使用 30 秒来保险起见。这样可以允许中间出现问题,而且不会过度。 - Brad Christie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接