使用PHP提供大型文件

时间:2009-01-11 10:28:09

标签: php apache

所以我试图通过PHP脚本提供大型文件,它们不在Web可访问的目录中,所以这是我能够提供访问它们的最佳方式。

我能想到这个文件的唯一方法是将它加载到内存中(fopen,fread等),将头数据设置为正确的MIME类型,然后只回显整个内容文件。

问题是,我必须同时将这些~700MB的文件加载到内存中,并保留整个内容直到下载完成。如果我可以在他们下载的时候输入我需要的部分,那就太好了。

有什么想法吗?

9 个答案:

答案 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上找到的一些文本。

为什么不使用PHP提供文件:

  • 天真地完成了操作,该文件被读入内存,然后送达。如果 文件很大,这可能会导致服务器的内存不足。
  • 缓存标题通常设置不正确。这会导致网络浏览器 即使文件没有更改,也可以多次重新下载该文件。
  • 通常不支持HEAD请求和范围请求 自动支持。如果文件很大,则提供此类文件 将工作进程或线程捆绑在一起。如果这会导致饥饿 有有限的工人。工人人数增加 可能会导致服务器的内存不足。

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;