使用 PHP 提供文件的最快方式使用 X-SendFile 标头符号链接和位置标头通过 IP 和位置标头进行访问控制当其他一切都失败时组合解决方案

2022-08-30 07:32:19

我试图将一个函数放在一起,该函数接收文件路径,识别它是什么,设置适当的标头,并像Apache一样提供它。

我这样做的原因是因为我需要在提供文件之前使用PHP来处理有关请求的一些信息。

速度至关重要

virtual() 不是一个选项

必须在用户无法控制Web服务器(Apache / nginx等)的共享托管环境中工作

以下是我到目前为止所得到的:

File::output($path);

<?php
class File {
static function output($path) {
    // Check if the file exists
    if(!File::exists($path)) {
        header('HTTP/1.0 404 Not Found');
        exit();
    }

    // Set the content-type header
    header('Content-Type: '.File::mimeType($path));

    // Handle caching
    $fileModificationTime = gmdate('D, d M Y H:i:s', File::modificationTime($path)).' GMT';
    $headers = getallheaders();
    if(isset($headers['If-Modified-Since']) && $headers['If-Modified-Since'] == $fileModificationTime) {
        header('HTTP/1.1 304 Not Modified');
        exit();
    }
    header('Last-Modified: '.$fileModificationTime);

    // Read the file
    readfile($path);

    exit();
}

static function mimeType($path) {
    preg_match("|\.([a-z0-9]{2,4})$|i", $path, $fileSuffix);

    switch(strtolower($fileSuffix[1])) {
        case 'js' :
            return 'application/x-javascript';
        case 'json' :
            return 'application/json';
        case 'jpg' :
        case 'jpeg' :
        case 'jpe' :
            return 'image/jpg';
        case 'png' :
        case 'gif' :
        case 'bmp' :
        case 'tiff' :
            return 'image/'.strtolower($fileSuffix[1]);
        case 'css' :
            return 'text/css';
        case 'xml' :
            return 'application/xml';
        case 'doc' :
        case 'docx' :
            return 'application/msword';
        case 'xls' :
        case 'xlt' :
        case 'xlm' :
        case 'xld' :
        case 'xla' :
        case 'xlc' :
        case 'xlw' :
        case 'xll' :
            return 'application/vnd.ms-excel';
        case 'ppt' :
        case 'pps' :
            return 'application/vnd.ms-powerpoint';
        case 'rtf' :
            return 'application/rtf';
        case 'pdf' :
            return 'application/pdf';
        case 'html' :
        case 'htm' :
        case 'php' :
            return 'text/html';
        case 'txt' :
            return 'text/plain';
        case 'mpeg' :
        case 'mpg' :
        case 'mpe' :
            return 'video/mpeg';
        case 'mp3' :
            return 'audio/mpeg3';
        case 'wav' :
            return 'audio/wav';
        case 'aiff' :
        case 'aif' :
            return 'audio/aiff';
        case 'avi' :
            return 'video/msvideo';
        case 'wmv' :
            return 'video/x-ms-wmv';
        case 'mov' :
            return 'video/quicktime';
        case 'zip' :
            return 'application/zip';
        case 'tar' :
            return 'application/x-tar';
        case 'swf' :
            return 'application/x-shockwave-flash';
        default :
            if(function_exists('mime_content_type')) {
                $fileSuffix = mime_content_type($path);
            }
            return 'unknown/' . trim($fileSuffix[0], '.');
    }
}
}
?>

答案 1

我之前的回答是片面的,没有很好的记录,这里有一个更新,总结了它和讨论中其他人的解决方案。

这些解决方案是从最佳解决方案到最差解决方案的顺序,但也从需要对Web服务器进行最大控制的解决方案到需要较少的解决方案进行排序。似乎没有一种简单的方法可以拥有一种既快速又随处可见的解决方案。


使用 X-SendFile 标头

正如其他人所记录的那样,这实际上是最好的方法。其基础是您在php中进行访问控制,然后不是自己发送文件,而是告诉Web服务器执行此操作。

基本的 php 代码是:

header("X-Sendfile: $file_name");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file_name) . '"');

文件系统上的完整路径在哪里。$file_name

此解决方案的主要问题是它需要由Web服务器允许,并且默认情况下不安装(apache),默认情况下不处于活动状态(lighttpd)或需要特定配置(nginx)。

阿帕奇

在 apache 下,如果您使用mod_php则需要安装一个名为 mod_xsendfile 然后配置它的模块(如果允许,可以在 apache 配置或 .htaccess 中)

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

使用此模块时,文件路径可以是绝对路径,也可以是相对于指定的 。XSendFilePath

光度

mod_fastcgi在配置时支持此功能

"allow-x-send-file" => "enable" 

该功能的文档位于lighttpd wiki上,他们记录了标题,但名称也有效X-LIGHTTPD-send-fileX-Sendfile

恩金克斯

在Nginx上,你不能使用标题,你必须使用他们自己的标题,名为。默认情况下,它是启用的,唯一真正的区别是它的参数应该是URI而不是文件系统。结果是,您必须在配置中定义一个标记为内部的位置,以避免客户端找到真正的文件URL并直接转到它,他们的wiki包含对此的良好解释X-SendfileX-Accel-Redirect

符号链接和位置标头

您可以使用符号链接并重定向到它们,只需在用户有权访问文件时使用随机名称创建指向文件的符号链接,然后使用以下命令将用户重定向到该文件:

header("Location: " . $url_of_symlink);

显然,您需要一种方法来修剪它们,或者在调用创建它们的脚本时或通过cron(在机器上,如果您有访问权限,或者通过某些webcron服务)

在 apache 下,您需要能够在 .htaccess 或 apache 配置中启用 FollowSymLinks

通过 IP 和位置标头进行访问控制

另一个技巧是从php生成apache访问文件,允许显式用户IP。在apache下,这意味着使用mod_authz_host()命令。mod_accessAllow from

问题在于,锁定对文件的访问(因为多个用户可能希望同时执行此操作)并非易事,并且可能导致某些用户等待很长时间。无论如何,您仍然需要修剪该文件。

显然,另一个问题是同一IP背后的多个人可能会访问该文件。

当其他一切都失败时

如果你真的没有办法让你的Web服务器帮助你,剩下的唯一解决方案是readfile,它在当前使用的所有php版本中都可用,并且工作得很好(但不是很有效)。


组合解决方案

好吧,如果您希望php代码在任何地方都可用,那么快速发送文件的最佳方法是在某个地方有一个可配置的选项,其中包含有关如何根据Web服务器激活它的说明,并且可能在安装脚本中进行自动检测。

这与许多软件中所做的非常相似

  • 干净的网址(在apache上)mod_rewrite
  • 加密函数 ( php 模块)mcrypt
  • 多字节字符串支持 ( php 模块)mbstring

答案 2

最快的方法:不要。看看nginx的x-sendfile头,其他Web服务器也有类似的东西。这意味着您仍然可以在php中进行访问控制等,但将文件的实际发送委托给为此设计的Web服务器。

P.S:我只是想想与在nginx中读取和发送文件相比,在nginx上使用它要高效得多。试想一下,如果有100个人正在下载一个文件:使用php + apache,慷慨,这可能是100 * 15mb = 1.5GB(大约,射击我),那里的RAM就在那里。Nginx只会将文件发送到内核,然后将其直接从磁盘加载到网络缓冲区中。迅速!

P.P.S:而且,使用这种方法,你仍然可以做所有你想要的访问控制,数据库的东西。


推荐