【问题标题】:Downloading files with download.php使用 download.php 下载文件
【发布时间】:2023-03-23 04:00:02
【问题描述】:

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

    下载时测得的 CPU 使用率:13.6%。

  • 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(可以编写多种方法)。在性能方面,类似于让客户下载example.com/file.zip,由Apache服务器提供服务。

  • 3:另一种 PHP 解决方案。这可能会起作用:

    $myfile = file_get_contents("file.zip");
    echo $myfile;
    

    但这不会要求 PHP 将整个内容加载到内存中吗? (这在性能方面会很糟糕!)

  • 4:只需按照File with a short URL downloaded with original filename 中的说明进行header("Location: /abcd/file.zip"); 重定向。

    此解决方案的问题:这会泄露文件的实际位置

     example.com/abcd/file.zip
    

    发给不想要的最终用户(然后他们可以使用或共享此 URL 而无需经过身份验证)...

    但另一方面,它对 CPU 来说要轻得多,因为 PHP 只是重定向请求而不是传递文件本身。

    下载时测得的 CPU 使用率:10.6%。


注意:readfile 文档说:

readfile() 不会出现任何内存问题,即使在发送大文件时也是如此。如果遇到内存不足错误,请确保使用 ob_get_level() 关闭输出缓冲。

但我想 100% 确定它不会比纯 Apache 解决方案更慢/更占用 CPU/RAM。

【问题讨论】:

  • 为什么不对这两种解决方案进行基准测试?
  • 如果你想确定,就测试一下。
  • 我认为这可能是众所周知的@akond,并且会成为将来参考的有用答案。而且,我还不够 linux-benchmarking-tools-connoisseur 做一个精确的有意义的测试。
  • 您能否具体说明您考虑使用 PHP 来处理您的问题中的文件下载的原因?实际上,header 调用对于服务器来说是肤浅的,因为它们是发送给客户端的指令,用于描述客户端应如何处理响应。假设readfile 是使用的方法,那么导致“缓慢”的唯一header 将是content-length,因为使用filesize 需要系统调用,而不是必需的header。其余的标头理论上可以在.htaccess 中定义,并且与 PHP 中的效果相同。
  • @fyrye:我正在使用 PHP 1)登录我自己的数据库,令牌 ID f6zDaq(与用户关联)的文件已被很好地下载 2)检查令牌 ID在提供文件之前匹配数据库中的用户...如果除了 PHP 之外还有其他方法可以做到这一点(直接使用 Apache / .htaccess),我也很感兴趣。

标签: php apache .htaccess download


【解决方案1】:

您可以使用.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: /');
}

当用户访问一个有效的urlhttp://example.com/download/f6zDaq/file.zip时,将开始下载并关闭连接。

如果用户访问了一个无效的url,他们将被重定向到主页。

【讨论】:

  • @Basj 可以看看第二条限制id的规则。
  • @Basj 公平地说,您的问题实际上仅限于“我想 100% 确定它不会比纯 Apache 解决方案更慢/更多 CPU/RAM 消耗”。你没有提到安全问题。
  • @Basj 你是什么意思?它在download.php 中。标头正在传递文件,它是强制下载的内容类型。
  • Content-Disposition: attachment; 的标头告诉浏览器应该下载它,而浏览器只是读取您传递的文件名的数据。
  • @ChinLeung:我清理了旧的过时 cmets。最后一个问题:在所有其他变体中(请参阅更新的问题),总是有:1)标题,然后是 2)readfileecho file_get_contents("file.zip")。 2)是否有实际交付文件。您的解决方案如何在没有readfileecho file_get_contents(...) 的情况下交付文件?提前谢谢你。
【解决方案2】:

对于这些大小的文件,您将面临的最大问题如下:

  • 人们使用下载管理器下载它
  • 连接中断

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

因此,在您提供的选项中,我推荐

1ter

但是,与此处的其他人一样,我仍然建议您测试您的解决方案,并且最好从与您提供文件的位置不同的位置。

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

改为创建基于 Apache 的受保护下载文件夹:

Options +FollowSymLinks
RewriteEngine On
RewriteRule ^/user/files/folder1.*$ http://example.com/userfiles/ [R=301,L]

然后,如果您需要对其进行密码保护,而不是使用 PHP,请使用 Apache(大多数 PHP 安装已经安装了它)。为此,您可以在目标文件夹中包含一个 .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> 

[注意:如果密码不是每个文件随机分配的,请勿使用直接密码链接方法。]

或者,如果您基于根 apache 密码管理进行填充,并且您的站点正在使用 apache 进行登录过程,那么他们可能根本不需要 user:pass 部分链接,已经使用 Apache 登录。

注意:

现在,也就是说,共享完整链接(带有用户名/密码)的人可以访问这些文件。因此,它们将与您服务器的 https(或 http,如果您允许)协议以及您的用户共享或不共享链接一样安全(或不安全)。

这样做,文件将向其目标用户开放,并具有可供他们访问的网络的全部功能,这意味着下载帮助程序、提供帮助的浏览器插件、REST 调用等,具体取决于您的用户用例。这可能会降低安全性,这可能会或可能不会很重要,具体取决于您托管的内容。如果您要托管私人医疗数据(用户少、安全性高、速度要求低),我不会这样做。如果您要托管音乐专辑,我完全会这样做(用户多、安全性低、速度要求高)。

【讨论】:

  • 感谢您的回答@liljoshu。可以肯定的是,您是否建议使用header('Connection: Keep-Alive');?如果是这样,您的答案可能是 1ter,而不是 1bis?
  • 是的,我会解决的。我用代码块误读了标题,道歉。
  • 谢谢@liljoshu。关于你的附录,你能发布一个没有 PHP 的解决方案,而只是 .htaccess 吗? (注意:仅当用户使用特定令牌时,我才需要授予对文件的访问权限,并且我需要在自定义数据库中登录,如果文件已为每个用户下载或未下载;这就是我使用 PHP 的原因)
  • @Basj 希望对您有所帮助。
【解决方案3】:

我会选择readfile。我使用它多年,从未遇到过内存问题,即使在 128MB VPS 上运行也是如此。

使用 PHP 意味着您可以轻松处理身份验证、授权、日志记录、添加和删除用户、过期 URL 等。您可以使用.htaccess 来执行此操作,但您必须编写一个相当大的结构来处理此问题。

【讨论】:

  • 感谢您的回答。我编辑了问题,您将使用哪种标题变体? (或者你可以在答案中包含你的标题吗?)
【解决方案4】:

当您的网络服务器是 Nginx 时,您可以使用 X-Accel-Redirect。对于 Apache,它是带有 X-Sendfile 标头的 mod_xsendfile。

<?php
header('X-Accel-Redirect: /download/f6zDaq/file.zip');

成本更低,性能也更好,因为网络服务器处理文件。

【讨论】:

  • 为什么成本更低/性能更好?由于您的服务器正在处理下载,这不会花费更多吗?
【解决方案5】:

在内存和 CPU 方面,您可能应该使用 readfile() 或使用自定义缓冲区大小的 fopen()fread() 编写一些自定义代码。

关于您发送的标头,它们不会影响脚本的性能,它们只会指示客户端如何处理服务器响应(在您的情况下是文件)。你可以谷歌每个标题,看看它到底做了什么。

您可能应该看看这个:Is there a good implementation of partial file downloading in PHP?。您可能会感兴趣的内容:下载范围和下载恢复支持、使用 Web 服务器插件的方法、提供您需要的功能的 PEAR 包或库。

【讨论】:

    【解决方案6】:

    正如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 . '"');
    

    注意:奇怪的是,即使我在apache2.conf VirtualHost 中启用了AllowOverride All,这样做:

    XSendFile on
    XSendFilePath /home/www/example.com/files/
    

    只是在/home/www/example.com/.htaccess/home/www/example.com/files/.htaccess 文件中不起作用(它会因xsendFilePath not allowed here 而失败)。

    基准测试:

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

    【讨论】:

      猜你喜欢
      • 2015-10-18
      • 2015-04-08
      • 2010-12-20
      • 2016-01-06
      • 2018-04-08
      • 2020-07-09
      • 2019-02-05
      • 2014-04-13
      • 2014-04-06
      相关资源
      最近更新 更多