我正在尝试通过PHP脚本提供大文件的服务,这些文件不在Web可访问目录中,所以这是我能想到的最佳方式。
我一开始能够想到的唯一方法是将其加载到内存中(fopen、fread等),将头数据设置为正确的MIME类型,然后只需回显整个文件的内容。
问题在于,我必须一次性将这些大约700MB的文件全部加载到内存中,并保持其在下载完成之前一直存在。如果我可以在下载时按需流式传输需要的部分,那就太好了。
有什么好主意吗?
我正在尝试通过PHP脚本提供大文件的服务,这些文件不在Web可访问目录中,所以这是我能想到的最佳方式。
我一开始能够想到的唯一方法是将其加载到内存中(fopen、fread等),将头数据设置为正确的MIME类型,然后只需回显整个文件的内容。
问题在于,我必须一次性将这些大约700MB的文件全部加载到内存中,并保持其在下载完成之前一直存在。如果我可以在下载时按需流式传输需要的部分,那就太好了。
有什么好主意吗?
你不需要阅读整个内容 - 只需进入循环读取它,比如每次读取32Kb并将其发送为输出。更好的选择是使用fpassthru,它可以为您完成类似的操作....
$name = 'mybigfile.zip';
$fp = fopen($name, 'rb');
// send the right headers
header("Content-Type: application/zip");
header("Content-Length: " . filesize($name));
// dump the file and stop the script
fpassthru($fp);
exit;
$name = 'mybigfile.zip';
// send the right headers
header("Content-Type: application/zip");
header("Content-Length: " . filesize($name));
// dump the file and stop the script
readfile($name);
exit;
readfile
还是fpassthru
都会出现内存相关错误:Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 1883504640 bytes)
... - Ionică Bizău使用PHP发送大文件的最佳方法是使用X-Sendfile
头。它允许Web服务器通过零拷贝机制(如sendfile(2)
)更快地提供文件。它受到了lighttpd和apache的支持,需要安装插件。
示例:
$file = "/absolute/path/to/file"; // can be protected by .htaccess
header('X-Sendfile: '.$file);
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
// other headers ...
exit;
服务器读取 X-Sendfile
标头并发送文件。虽然在过去,fpassthru()
一直是我的首选,但实际上PHP手册推荐*使用readfile()
,如果你只是将文件原样转储到客户端。
*
"如果您只想将文件内容转储到输出缓冲区,而不是先修改它或寻找特定的偏移量,则可能希望使用readfile(),这样可以避免fopen()调用。" ——PHP手册
ln -s /home/files/big_files_folder /home/www/htdocs
如果您有高流量,使用PHP来提供静态文件会慢得多,内存消耗将非常大,并且可能无法处理大量的请求。
奇怪的是,无论是fpassthru()还是readfile()都不能满足我的需求,总是出现内存错误。 我只好使用没有'f'的passthru()函数:
$name = 'mybigfile.zip';
// send the right headers
header("Content-Type: application/zip");
header("Content-Length: " . filesize($name));
// dump the file and stop the script
passthru('/bin/cat '.$filename);
exit;
这个程序执行Unix命令'cat'并将其输出发送到浏览器。
给slim的评论:你不能只是在webspace上放置一个符号链接,原因是安全性问题。
如果你想做得正确,仅仅使用PHP是不够的。你应该使用Nginx的X-Accel-Redirect (推荐) 或者Apache的X-Sendfile来提供文件服务,这些功能正是为此目的而设计的。
我会在这个回答中包含一些在这篇文章中找到的文本。
NGINX可以正确处理所有这些问题。因此,让我们在应用程序中处理权限检查,并让NGINX服务实际文件。这就是内部重定向的作用。其思想很简单:您可以像通常一样配置位置条目来服务常规文件。
将以下内容添加到nginx服务器块:
location /protected_files/ {
internal;
alias /var/www/my_folder_with_protected_files/;
}
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$real_path = '/var/www/my_folder_with_protected_files/foo.pdf';
$x_accel_redirect_path = '/protected_files/foo.pdf';
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse( $real_path );
$response->headers->set( 'X-Accel-Redirect', $accel_file );
$response->sendHeaders();
exit;
这应该是你开始的基础。
以下是一个更完整的示例,用于服务内联PDF:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$real_path = '/var/www/my_folder_with_protected_files/foo.pdf';
$x_accel_redirect_path = '/protected_files/foo.pdf';
$file = new File( $file_path );
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse( $file_path );
$response->setImmutable( true );
$response->setPublic();
$response->setAutoEtag();
$response->setAutoLastModified();
$response->headers->set( 'Content-Type', 'application/pdf' );
$response->headers->set( 'Content-Length', $file->getSize() );
$response->headers->set( 'X-Sendfile-Type', 'X-Accel-Redirect' );
$response->headers->set( 'X-Accel-Redirect', $accel_file );
$response->headers->set( 'X-Accel-Expires', 60 * 60 * 24 * 90 ); // 90 days
$response->headers->set( 'X-Accel-Limit-Rate', 10485760 ); // 10mb/s
$response->headers->set( 'X-Accel-Buffering', 'yes' );
$response->setContentDisposition( ResponseHeaderBag::DISPOSITION_INLINE, basename( $file_path ) ); // view in browser. Change to DISPOSITION_ATTACHMENT to download
$response->sendHeaders();
exit;
fpassthru() 的一个好处是,该函数不仅可以与文件一起使用,还可以与任何有效的句柄一起使用。例如套接字。
如果可能的话(就像 file_get_contents() 一样),readfile() 必须更快,因为它使用了操作系统缓存机制。
还有一个提示。fpassthru() 保持句柄打开,直到客户端获取内容(在慢速连接上可能需要相当长的时间),因此如果对该文件进行并行写入,则必须使用某些锁定机制。
Python的答案都很好。但是你不能制作一个可通过网络访问的目录,其中包含指向实际文件的符号链接吗?这可能需要一些额外的服务器配置,但应该可以工作。