所以我试图通过PHP脚本提供大型文件,它们不在Web可访问的目录中,所以这是我能够提供访问它们的最佳方式。
我能想到这个文件的唯一方法是将它加载到内存中(fopen,fread等),将头数据设置为正确的MIME类型,然后只回显整个内容文件。
问题是,我必须同时将这些~700MB的文件加载到内存中,并保留整个内容直到下载完成。如果我可以在他们下载的时候输入我需要的部分,那就太好了。
有什么想法吗?
答案 0 :(得分:25)
您不需要阅读整个内容 - 只需输入一个循环读取它,比如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;
如果您使用readfile,甚至更少的行,这不需要fopen调用......
$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;
如果您想要更加可靠,您可以支持Content-Range标头,该标头允许客户端请求文件的特定字节范围。这对于将PDF文件提供给Adobe Acrobat特别有用,Adobe Acrobat只需要请求呈现当前页面所需文件的块。这有点牵扯,但是see this for an example。
答案 1 :(得分:10)
使用php发送大文件的最佳方法是X-Sendfile
标头。它允许Web服务器通过sendfile(2)
等零拷贝机制更快地提供文件。它由lighttpd和带有plugin的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
标题并发送文件。
答案 2 :(得分:6)
虽然fpassthru()
过去是我的第一选择,但PHP手册实际上建议*使用readfile()
代替,如果您只是将文件按原样转储到客户端。
*
“如果你只想将文件的内容转储到输出缓冲区,而不先修改它或寻找特定的偏移量,你可能想要使用readfile(),这可以节省你的fopen()来电。“ - PHP manual
答案 3 :(得分:2)
如果Web服务器无法访问您的文件,因为该路径不在您的Web服务目录(htdocs)中,那么您可以在Web服务目录中为该文件夹创建符号链接(符号链接),以避免传递所有流量槽PHP。
你可以做这样的事情
ln -s /home/files/big_files_folder /home/www/htdocs
使用php来提供静态文件的速度要慢得多,如果流量很大,内存消耗会非常大,而且可能无法处理大量请求。
答案 4 :(得分:1)
看看fpassthru()。在更新版本的PHP中,这应该为文件提供服务而不将它们保存在内存中,如this comment所述。
答案 5 :(得分:1)
奇怪,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;
这个执行'cat'Unix命令并将其输出发送到浏览器。
评论苗条:你没有把符号链接放到某处的原因是webspace是安全的。
答案 6 :(得分:0)
fpassthru()的一个好处是这个函数不仅可以用于文件,还可以用于任何有效的句柄。例如Socket。
并且readfile()必须快一点,如果可能的话,使用OS缓存机制(就像file_get_contents())。
还有一个提示。 fpassthru()保持句柄打开,直到客户端获得内容(在慢速连接上可能需要相当长的时间),因此如果可以并行写入此文件,则必须使用某种锁定机制。
答案 7 :(得分:0)
Python的答案都很好。但是有什么理由你不能创建一个包含指向实际文件的符号链接的Web可访问目录吗?它可能需要一些额外的服务器配置,但它应该工作。
答案 8 :(得分:0)
如果您想做对,仅PHP不能做。您可能希望使用专门为此目的而构建的Nginx的X-Accel-Redirect (推荐)或Apache的X-Sendfile来提供文件。
我将在此答案中包含在此article上找到的一些文本。
NGINX可以正确处理所有这些事情。因此,让我们处理应用程序中的权限检查,并让NGINX提供实际文件。这就是内部重定向的来源。这个想法很简单:在提供常规文件时,您可以照常配置位置条目。
将此添加到您的nginx服务器块:
location /protected_files/ {
internal;
alias /var/www/my_folder_with_protected_files/;
}
在您的项目中,需要HTTP Foundation包:
composer require symfony/http-foundation
使用Nginx在PHP中提供文件:
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;