简体   繁体   English

使用 PHP 提供大文件

[英]Serving large files with PHP

So I am trying to serve large files via a PHP script, they are not in a web accessible directory, so this is the best way I can figure to provide access to them.所以我试图通过 PHP 脚本提供大文件,它们不在网络可访问目录中,所以这是我能想到的最好的方法来提供对它们的访问。

The only way I could think of off the bat to serve this file is by loading it into memory (fopen, fread, ect.), setting the header data to the proper MIME type, and then just echoing the entire contents of the file.我能立即想到为该文件提供服务的唯一方法是将其加载到内存中(fopen、fread 等),将标头数据设置为正确的 MIME 类型,然后只回显文件的全部内容。

The problem with this is, I have to load these ~700MB files into memory all at once, and keep the entire thing there till the download is finished.问题是,我必须一次将这些 ~700MB 的文件全部加载到内存中,并将整个文件保存在那里,直到下载完成。 It would be nice if I could stream in the parts that I need as they are downloading.如果我可以在下载时流式传输我需要的部分,那就太好了。

Any ideas?有任何想法吗?

You don't need to read the whole thing - just enter a loop reading it in, say, 32Kb chunks and sending it as output.您不需要阅读整个内容 - 只需输入一个循环读取它,例如,32Kb 块并将其作为输出发送。 Better yet, use fpassthru which does much the same thing for you....更好的是,使用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;

even less lines if you use readfile , which doesn't need the fopen call...如果您使用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;

If you want to get even cuter, you can support theContent-Range header which lets clients request a particular byte range of your file.如果你想变得更可爱,你可以支持Content-Range标头,它允许客户端请求文件的特定字节范围。 This is particularly useful for serving PDF files to Adobe Acrobat, which just requests the chunks of the file it needs to render the current page.这对于向 Adob​​e Acrobat 提供 PDF 文件特别有用,它只请求呈现当前页面所需的文件块。 It's a bit involved, but see this for an example .这有点复杂,但请参阅此示例

The best way to send big files with php is the X-Sendfile header.使用 php 发送大文件的最佳方式是X-Sendfile标头。 It allows the webserver to serve files much faster through zero-copy mechanisms like sendfile(2) .它允许网络服务器通过零复制机制(如sendfile(2)更快地提供文件。 It is supported by lighttpd and apache with a plugin . lighttpd 和 apache 通过插件支持它。

Example:例子:

$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;

The server reads the X-Sendfile header and sends out the file.服务器读取X-Sendfile标头并发送文件。

While fpassthru() has been my first choice in the past, the PHP manual actually recommends* using readfile() instead, if you are just dumping the file as-is to the client.虽然fpassthru()过去一直是我的首选,但 PHP 手册实际上建议*使用readfile()代替,如果您只是将文件按原样转储到客户端。

* "If you just want to dump the contents of a file to the output buffer, without first modifying it or seeking to a particular offset, you may want to use the readfile(), which saves you the fopen() call." * “如果您只想将文件的内容转储到输出缓冲区,而不先修改它或寻找特定的偏移量,您可能需要使用 readfile(),它可以节省 fopen() 调用。” PHP manual PHP 手册

If your files are not accessible by the web server because the path is not in your web serving directory (htdocs) then you can make a symbolic link (symlink) to that folder in your web serving directory to avoid passing all traffic trough php.如果您的文件无法被 Web 服务器访问,因为该路径不在您的 Web 服务目录 (htdocs) 中,那么您可以创建指向 Web 服务目录中该文件夹的符号链接 (symlink),以避免通过 php.ini 传递所有流量。

You can do something like this你可以做这样的事情

ln -s /home/files/big_files_folder /home/www/htdocs

Using php for serving static files is a lot slower, if you have high traffic, memory consumption will be very large and it may not handle a big number of requests.使用php服务静态文件会慢很多,如果流量大,内存消耗会非常大,可能无法处理大量请求。

Have a look at fpassthru() .看看fpassthru() In more recent versions of PHP this should serve the files without keeping them in memory, as this comment states.在较新版本的 PHP 中,这应该为文件提供服务,而不会将它们保留在内存中,如该注释所述。

Strange, neither fpassthru() nor readfile() did it for me, always had a memory error.奇怪的是,fpassthru() 和 readfile() 都没有为我做,总是有内存错误。 I resorted to use passthru() without the 'f':我使用 passthru() 而不带 'f':

$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;

this execs 'cat' Unix command and send its output to the browser.这会执行 'cat' Unix 命令并将其输出发送到浏览器。

comment for slim: the reason you just don't put a symlink to somewhere is webspace is SECURITY.对 slim 的评论:您不将符号链接放置到某处的原因是网络空间是安全性。

One of benefits of fpassthru() is that this function can work not only with files but any valid handle. fpassthru() 的好处之一是该函数不仅可以处理文件,还可以处理任何有效的句柄。 Socket for example.以插座为例。

And readfile() must be a little faster, cause of using OS caching mechanism, if possible (as like as file_get_contents()).并且 readfile() 必须快一点,这是使用操作系统缓存机制的原因,如果可能的话(就像 file_get_contents())。

One more tip.还有一个技巧。 fpassthru() hold handle open, until client gets content (which may require quite a long time on slow connect), and so you must use some locking mechanism if parallel writes to this file possible. fpassthru() 保持句柄打开,直到客户端获取内容(这在慢速连接时可能需要很长时间),因此如果可以并行写入此文件,则必须使用某种锁定机制。

The Python answers are all good. Python 的答案都很好。 But is there any reason you can't make a web accessible directory containing symbolic links to the actual files?但是有什么理由不能创建一个包含指向实际文件的符号链接的 Web 可访问目录吗? It may take some extra server configuration, but it ought to work.它可能需要一些额外的服务器配置,但它应该可以工作。

If you want to do it right, PHP alone can't do it.如果你想做得对,单靠PHP是做不到的。 You would want to serve the file by using Nginx's X-Accel-Redirect (Recommended) or Apache's X-Sendfile , which are built exactly for this purpose.您可能希望使用 Nginx 的X-Accel-Redirect (推荐)或 Apache 的X-Sendfile来提供文件,它们正是为此目的而构建的。

I will include in this answer some text found on this article .我会包括这回答这个问题上的一些文本中找到的物品

Why not serve the files with PHP:为什么不使用 PHP 提供文件:

  • Done naively, the file is read into memory and then served.天真地完成,文件被读入内存然后提供。 If the files are large, this could cause your server to run out of memory.如果文件很大,这可能会导致您的服务器内存不足。
  • Caching headers are often not set correctly.缓存标头通常设置不正确。 This causes web browsers to re-download the file multiple times even if it hasn't changed.这会导致 Web 浏览器多次重新下载文件,即使它没有更改。
  • Support for HEAD requests and range requests is typically not automatically supported.通常不会自动支持对 HEAD 请求和范围请求的支持。 If the files are large, serving such files ties up a worker process or thread.如果文件很大,提供这些文件会占用工作进程或线程。 This can lead to starvation if there are limited workers available.如果可用的工人有限,这可能会导致饥饿。 Increasing the number of workers can cause your server to run out of memory.增加工作线程的数量可能会导致您的服务器内存不足。

NGINX handles all of these things properly. NGINX 可以正确处理所有这些事情。 So let's handle permission checks in the application and let NGINX serve the actual file.因此,让我们在应用程序中处理权限检查,并让 NGINX 为实际文件提供服务。 This is where internal redirects come in. The idea is simple: you can configure a location entry as usual when serving regular files.这就是内部重定向的用武之地。想法很简单:在提供常规文件时,您可以像往常一样配置位置条目。

Add this to your nginx server block:将此添加到您的 nginx 服务器块:

location /protected_files/ {
    internal;
    alias /var/www/my_folder_with_protected_files/;
}

In your project, require the HTTP Foundation package:在您的项目中,需要 HTTP Foundation 包:

composer require symfony/http-foundation

Serve the files in PHP using Nginx:使用 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;

This should be the basic you need to get started.这应该是您入门所需的基础知识。

Here's a more complete example serving an Inline PDF:这是一个更完整的示例,用于提供内联 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM