使用download.php

时间:2017-08-31 19:45:58

标签: php apache .htaccess download

我需要向客户提供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

    的PHP解决方案
    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饥饿。

6 个答案:

答案 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)

使用这些大小的文件要面对的最大问题如下:

  • 人们通过下载管理器进行下载
  • 连接中断

通常,保持活动状态不是一个好主意,因为它专用于与下载的连接,这可能会使您的网络连接陷入瘫痪,而不是让它们轻松释放。但是,如果您希望所有文件都很大,那就是您的朋友,因为您不希望其他人重新开始下载这些文件。这些下载将使连接保持可靠连接,并使客户端更容易恢复,这有助于减少尝试重新下载大量文件的人。

因此,我建议您选择

1ter

但是,与这里的其他人一样,我仍然建议您测试解决方案,最好在与您要提供文件的位置不同的位置进行测试。

附录: 这就是说,用PHP服务不是最好的主意,除非您必须获得标头控件功能和.htaccess控件,因为这只会增加更多的处理能力。到目前为止,更好的方法是将文件放在可访问的目录中。 .htaccess可以重写对文件和文件夹的访问,而不仅仅是PHP脚本。

相反,要创建基于Apache的受保护下载文件夹:

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)。

基准:

  • 下载时为10.6%的CPU,就像我直接用Apache直接下载文件(完全没有PHP)一样,所以一切都很好!