我需要向客户提供file.zip
(~2 GB)等大文件,并为每位客户提供唯一的URL。然后,我将(.htaccess
)客户下载链接example.com/download/f6zDaq/file.zip
重定向到
example.com/download.php?id=f6zDaq&file=file.zip
但由于文件很大,我不希望PHP处理下载(而不仅仅是让Apache处理它)成为我服务器的CPU / RAM性能问题。毕竟,要求PHP执行此操作涉及一个新层,因此如果操作不正确,可能会导致此类问题。
问题:在以下解决方案中,哪一个是最佳实践?(特别是在CPU / RAM方面)?
1:使用application/download
header('Content-Type: application/download');
header('Content-Disposition: attachment; filename=file.zip');
readfile("/path/to/file.zip");
1bis:带application/octet-stream
的PHP解决方案(来自this page的示例#1)
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=file.zip');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize('file.zip'));
readfile("/path/to/file.zip");
1ter:带application/octet-stream
的PHP解决方案(来自here):
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=file.zip');
header('Content-Transfer-Encoding: binary'); // additional line
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); // additional line
header('Pragma: public');
header('Content-Length: ' . filesize('file.zip'));
readfile("/path/to/file.zip");
1quater:另一个带有application/force-download
的PHP变体(已编辑;来自here):
header("Content-Disposition: attachment; filename=file.zip");
header("Content-Type: application/force-download");
header("Content-Length: " . filesize($file));
header("Connection: close");
2:Apache解决方案,不涉及PHP:让Apache提供文件,并使用.htaccess为同一文件提供不同的URL(可以编写很多方法)。在性能方面,它类似于让客户下载由Apache服务器提供服务的example.com/file.zip
。
3:另一种PHP解决方案。这可能会奏效:
$myfile = file_get_contents("file.zip");
echo $myfile;
但是这不会要求PHP将整个内容加载到内存中吗? (这在性能上会很差)
注意:readfile doc说:
即使在发送大文件时,readfile()也不会出现任何内存问题。如果遇到内存不足错误,请确保使用ob_get_level()关闭输出缓冲。
但我希望100%确定它不会比纯Apache解决方案更慢/更多CPU / RAM饥饿。
答案 0 :(得分:5)
您可以使用.htaccess
将请求重定向到文件,同时保留永久链接结构:
RewriteEngine On
RewriteBase /
RewriteRule ^download\/([^\/]+)\/file.zip download.php?id=$1 [L,NC]
然后在download.php
中,您可以检查提供的ID是否有效:
// Path to file
$file = 'file.zip';
// If the ID is valid
if ($condition) {
header("Content-Disposition: attachment; filename=\"" . basename($file) . "\"");
header("Content-Type: application/force-download");
header("Content-Length: " . filesize($file));
header("Connection: close");
} else {
// Handle invalid ids
header('Location: /');
}
当用户访问有效网址http://example.com/download/f6zDaq/file.zip
时,下载将开始,连接将关闭。
如果用户访问了无效的网址,他们将被重定向到主页。
答案 1 :(得分:3)
我会选择readfile
。我使用它多年,从来没有出现内存问题,即使在128MB VPS上运行。
使用PHP意味着您可以轻松处理身份验证,授权,日志记录,添加和删除用户,使URL过期等。您可以使用.htaccess
来执行此操作,但您必须编写一个相当大的结构来处理此问题。
答案 2 :(得分:2)
使用这些大小的文件要面对的最大问题如下:
通常,保持活动状态不是一个好主意,因为它专用于与下载的连接,这可能会使您的网络连接陷入瘫痪,而不是让它们轻松释放。但是,如果您希望所有文件都很大,那就是您的朋友,因为您不希望其他人重新开始下载这些文件。这些下载将使连接保持可靠连接,并使客户端更容易恢复,这有助于减少尝试重新下载大量文件的人。
因此,我建议您选择
但是,与这里的其他人一样,我仍然建议您测试解决方案,最好在与您要提供文件的位置不同的位置进行测试。
附录: 这就是说,用PHP服务不是最好的主意,除非您必须获得标头控件功能和.htaccess控件,因为这只会增加更多的处理能力。到目前为止,更好的方法是将文件放在可访问的目录中。 .htaccess可以重写对文件和文件夹的访问,而不仅仅是PHP脚本。
Options +FollowSymLinks
RewriteEngine On
RewriteRule ^/user/files/folder1.*$ http://example.com/userfiles/ [R=301,L]
然后,如果您需要对其进行密码保护,而不是使用PHP,请使用Apache(大多数PHP安装中已经安装了Apache)。为此,您可以在目标文件夹中包含一个.htaccess文件(如果您是动态创建用户,则可能需要创建一个脚本来为每个新用户生成这些文件),并确保已准备好apache来处理密码:
AuthType Basic
AuthName "Authentication Required"
AuthUserFile "/user/password/.htpasswd"
Require valid-user
(有关更多详细信息,请参见此处:Setting up Apache Passwords)
此后,请确保在密码目录中具有一个.htpasswd文件,其格式为username:password / hashedpassword。
例如:
andreas:$apr1$dHjB0/..$mkTTbqwpK/0h/rz4ZeN8M0
john:$apr1$IHaD0/..$N9ne/Bqnh8.MyOtvKU56j1
现在,假设您不希望他们每次都输入密码,请在下载链接中包含访问权限
<a href="user:pass@http://example.com/userfiles/myCoolZip.zip">Link (hopefully behind a password-protected interface.)</a>
[注意:如果没有为每个文件随机分配密码,请不要使用直接密码链接方法。]
或者,如果您是基于root apache密码管理来填充的,并且您的站点正在使用apache进行登录,则他们可能根本不需要user:pass部分链接,并且已经使用Apache登录。
现在,这就是说,共享完整链接(带有用户名/密码)的人们将可以访问这些文件。因此,它们将与服务器的HTTPS(或HTTP,如果允许)协议一样安全(或不安全),并且用户共享或不共享链接。
通过这种方式,文件将向用户开放,并具有用户可以访问的全部网络功能,这意味着下载帮助程序,提供帮助的浏览器插件,REST调用等,具体取决于用户的用例。这可能会降低安全性,取决于您所托管的内容,安全性可能不重要。如果您要托管私人医疗数据(用户少,安全性高,速度要求低),我就不会这样做。如果您要托管音乐专辑,我会完全这样做(许多用户,较低的安全性,较高的速度要求)。
答案 3 :(得分:0)
明智地使用内存和CPU,您可能应该使用readfile()
或使用具有自定义缓冲区大小的fopen()
和fread()
编写一些自定义代码。
关于发送的标头不影响脚本的性能,它们仅指示客户端如何处理服务器响应(在您的情况下为文件)。您可以使用Google的每个标头查看其确切功能。
您可能应该看看以下内容:Is there a good implementation of partial file downloading in PHP?。在那里,您可能会感兴趣的事情:下载范围和下载恢复支持,使用Web服务器插件,提供所需功能的PEAR软件包或库来实现此目的。
答案 4 :(得分:0)
当您的Web服务器是Nginx时,您可以使用X-Accel-Redirect
。对于Apache,它是带有X-Sendfile
头的mod_xsendfile。
<?php
header('X-Accel-Redirect: /download/f6zDaq/file.zip');
由于Web服务器处理文件,因此成本更低,性能也更好。
答案 5 :(得分:0)
如Fastest Way to Serve a File Using PHP中所述,我终于做到了:
apt-get install libapache2-mod-xsendfile
a2enmod xsendfile # (should be already done by previous line)
然后我在apache2.conf
中添加了它:
<Directory />
AllowOverride All
Require all granted
XSendFile on
XSendFilePath /home/www/example.com/files/
</Directory>
然后我做了service apache2 restart
,并将其包含在.htaccess
中:
RewriteRule ^(.*)$ download.php?file=$1 [L,QSA]
,并在download.php
中:
header("X-Sendfile: /home/www/example.com/files/hiddenfolder_w33vbr0upk80/" . $file);
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . $file . '"');
NB:奇怪的是,即使我在AllowOverride All
VirtualHost中启用了apache2.conf
,也可以这样做:
XSendFile on
XSendFilePath /home/www/example.com/files/
仅在/home/www/example.com/.htaccess
或/home/www/example.com/files/.htaccess
文件中不起作用(它失败,并显示xsendFilePath not allowed here
)。
基准: