如何在 PHP 中检测 X-Accel-Redirect(Nginx)/ X-Sendfile(Apache)支持?

12

关于应用

我正在使用PHP开发一款电子商务应用程序。为了保护URL的安全,在PHP后面保留产品下载链接。有一个名为download.php的文件,它通过GET接受几个参数并根据数据库验证它们。如果一切顺利,它将使用PHP中的readfile()函数提供文件。

问题描述

当要传递给readfile()的文件大于php.ini中设置的内存限制时,问题就出现了。由于许多用户将在共享主机上使用此应用程序,因此我们不能依靠修改php.ini设置。

在我们寻找解决方法的过程中,我首先认为我们可以使用while循环中的fread()调用,但似乎这也会带来问题,正如在在PHP中可靠地下载大文件中所述。

因此,我的最佳选择是检测/检查服务器是否支持X-Accel-Redirect(在Nginx的情况下)/ X-Sendfile(在Apache的情况下)

如果服务器支持X-Accel-Redirect / X-Sendfile,则可以使用它们,并且在else块中,我可以让系统管理员知道php.ini实施的内存限制

理想情况下,我希望在可能的情况下使用服务器端支持,例如X-Accel-Redirect / X-Sendfile,如果无法使用,则希望拥有读取文件而不使用readfile()的备用代码。

我还不确定readfile()和while循环中的fread()有何不同,但似乎while循环会产生问题,正如在PHP中可靠地下载大文件中所建议的那样。

希望得到一些帮助、建议、代码、指导。

感谢您的阅读。

3个回答

13

要检测是否安装了mod_xsendfile Apache模块,您可以尝试使用以下代码:

if function_exists('apache_get_modules') 
      && in_array('mod_xsendfile', apache_get_modules()) { 
  header("X-Sendfile"); 
}

但是这段代码只检查模块是否安装,如果安装了但配置不正确会导致错误

另一种可能的方法是通过Apache的 .htaccess 设置服务器范围的变量:

<IfModule mod_xsendfile.c>
  <Files *.php>
    XSendFile On
    XSendFileAllowAbove On
    SetEnv MOD_X_SENDFILE_ENABLED 1
  </Files>
</IfModule>

并且从 PHP 代码中检查它:

if ($_SERVER['MOD_X_SENDFILE_ENABLED']) {
  Header(...)
}

对于nginx,常见的做法是将状态变量的值通过HTTP头或CGI/FastCGI变量传递给后端。


感谢您的帮助。上面的代码在使用php作为Apache的mod_php或fastcgi/fcgi时都能正常工作吗? - rahul286
另外,我猜在Nginx上,对于X-Accel-Redirect的支持是核心的一部分,所以我需要做的就是以某种方式检测(webserver == "nginx")是否成立!对吗? - rahul286
顺便说一下,上面的代码片段帮了很大的忙。所以谢谢你。 :-) - rahul286
1
第一个代码片段仅适用于mod_php,但第二个代码片段适用于mod_php和CGI / FastCGI模式。 - Vadim
关于nginx - 看起来你是对的,但是你的cgi/fastcgi层不知道前端服务器软件的任何信息,除非你通知它(使用CGI变量或HTTP头)。 - Vadim

5
readfile函数不会占用大量内存,它打开文件,读取一小部分内容,将该部分内容写入浏览器,然后重复使用内存进行下一次读取。这与在while循环中使用fread+echo相同。您不会受到内存限制的约束,但是您将受到max_execution_time等限制。
如果您想使用Web服务器提供的X-Accel-Redirect支持(或类似支持),请发送如下标头(适用于Nginx):
header('X-Accel-Redirect: /path/to/file');

你的应用程序无法知道服务器是否支持此功能。您需要提供配置选项,以便您软件的管理员/安装程序可以手动提供这些信息。


感谢您关于readfile的见解。如果readfile()在小内存中运行,为什么当我们打开大文件时会抛出“内存不足”错误?同样的错误在我们改变php.ini设置后立即消失了。另外,readfile()是我的最后一行代码,所以我确定在readfile之后,我的应用程序不再处理数据。 - rahul286
还有一个问题 - 如果我写了 header('X-Accel-Redirect: /path/to/file'); 但底层服务器没有正确配置,会发生什么? header() 调用会被忽略吗?还是会收到一些错误消息? - rahul286
rahul286,有人在http://php.net/readfile的评论中解释了可能存在的内存问题:“这是因为您启用了输出缓冲。在调用Readfile()之前立即关闭输出缓冲。使用类似ob_end_flush()的东西。” - Emil Vikström
此外,如果您的Web服务器不支持X-Accel-Redirect(或类似功能),则标头将发送到Web浏览器,该浏览器将忽略它。您将不会收到任何错误消息。 - Emil Vikström
Emil,我注意到了output_buffering的问题。但是由于我的代码被打包成WordPress插件,并且我没有初始化输出缓冲区,所以我选择在while循环中使用flush()调用。它奏效了。 :-) - rahul286

0
你可以在服务器上设置一个环境变量来控制正确的服务器头名称(可能由运维团队或负责脚本/定义服务器环境的人设置)。这将适用于支持基于Header的文件服务的任何堆栈,因为控制该堆栈的人员可以做出决策,而你只需从环境中读取Header即可。
<?php

/**
 * Method to delegate sending file to webserver
 *
 * Uses environment variable `SERVER_ACCEL_HEADER` to define the specific
 * server header to use to send files from server layer rather than 
 * application layer
 *
 * @param  string $path path to file that should exist
 * @param  bool $die if script should terminate after setting header
 * @return void returns nothing
 */
function sendPathAccel($path, $die=true) {
    $accelHeader = getenv('SERVER_ACCEL_HEADER');
    header("{$accelHeader}: {$path}");
    if($die) { die(); }
}

警告:请注意,这不是新手操作,请小心。如果负责服务器环境的人没有正确设置,使用此方法可能会出现错误或无法正常工作,但它简单快捷,我想不到更改它的理由。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接