本文编写于 734 天前,最后修改于 727 天前,其中某些信息可能已经过时。

关于X-Sendfile

X-Sendfile 是一种将文件下载请求由后端应用转交给前端 web 服务器处理的机制,它可以消除后端程序既要读文件又要处理发送的压力,从而显著提高服务器效率,特别是处理大文件下载的情形下。
摘自CSDN

在没有开启X-Sendfile之前,我们在可道云下载一次文件要历经:
PHP收到请求->检查权限->判断存在并找到文件->告知PHP读入内存->系统读文件到内存->系统给PHP->PHP移交给前端Web服务器->下载
这样的过程可能计算机是一瞬间,但是这样在下载大文件或者大并发下载时,会导致文件被多次/大量读入内存,不仅非常消耗内存还有一定可能导致PHP-FPM的崩溃。
如果此时我们利用X-Sendfile来下载文件,这个过程会变成:
PHP收到请求->检查权限->判断存在并找到文件->告诉前端文件服务器读取文件->系统读文件到内存->下载

开始设置

​‌‌‌​‌​​​​​‌‌​‌​‍​‌​‌‌‌​​‌‌‌‌​‌​‍​‌​​‌​​​‌​​​‌‌​‍​​‌‌​​​‌​​‌‌​‌​‍​‌‌‌‌‌‌​​​​‌​‌​‌‍‌​‌​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍‌​‌‌‌‌​‌‍‌​​‌​​‌‌‍‌​​‌​​​​‍‌​​‌‌​​​‍​​‌‌​‌​​​​​‌​​‌‍​‌​‌‌‌​‌​‌‌​‌‌​‍​‌‌‌​​​​‌​​​‌​‌‌‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌​‌‌‌‌‌‌‌‌​​​‍​‌‌​​‌‌‌​‌‌​​‌‌‌‍​‌‌​​​‌‌‌​​​‌​‌‍​​‌‌‌‌‌‌‌‌​​‌‌‍​‌​‌​​​‌‌​‌​‌‌‌‍​‌​‌‌​​​​​‌​​​​‍​‌‌​‌‌‌‌‌​‌​‌‌​​‍​‌‌​​​‌​‌‌​‌‌‌​‍​‌‌​​​‌‌‌​‌​​‌​‍​‌‌​​​​‌​​​​​​​‍​​​‌​‌​‌‌​‌​‌‌‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​​‌‌‌‌‍‌​‌​‌‌​​‍‌​​‌‌​‌​‍‌​​‌​​​‌‍‌​​‌‌​‌‌‍‌​‌‌‌​​‌‍‌​​‌​‌‌​‍‌​​‌​​‌‌‍‌​​‌‌​‌​‍​‌‌​​​​‌‌‌​​‌‌‌‍​‌​‌‌​​‌‌‌​‌​​‌‍​​‌‌​‌​​‌‌‌‌​​​‍​‌‌​​​‌​​​​‌​​‌‍​‌‌​​​‌‌‌‌‌​‌​​‍​‌‌‌​​​​‌​​​​​‌​‍​​‌‌‌‌‌‌‌‌​​‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​​​‌​‌​‌‌​​‌‌‌​‍‌​​‌​​​​‍‌​​​‌​‌​‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍​‌‌​​​‌​‌‌‌​​​‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​​‌‌‌​‍‌‌​​​‌‌​‍‌‌​‌​​‌​‍‌‌​​‌‌‌​‍‌‌​​‌‌‌​‍‌‌​‌​​‌​‍‌‌​​‌‌​‌‍‌‌​​​‌‌​‍​​‌‌‌​‌‌​‌‌‌‌‌‌‍​‌​‌‌‌​​‌‌​​‌‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌‌​‌​​‌‌‌‌​​​‍​‌​‌​​​‌‌​​‌‌‌‌‍​‌​‌​​​‌​‌‌‌‌‌‌‍​​​​​​​​‌‌‌​​‌​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​​‌‌‌‌‍‌​​​‌‌​​‍‌‌​​​‌​‌‍‌​‌​​​‌‌‍‌​‌​​​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​​‌‌​‌‍‌​​‌‌‌​‌‍‌​​​‌‌‌​‍‌‌​‌​​​‌‍‌​​‌‌‌​​‍‌​​‌​​​‌‍‌​‌​​​‌‌‍‌​​‌‌​‌​‍‌​​​​‌‌‌‍‌​​​‌‌‌‌‍‌​‌​​​‌‌‍‌​​​‌‌​​‍‌​​‌‌​‌​‍‌​​​‌​‌‌‍‌​​​‌​‌​‍‌​​​‌‌‌‌‍‌​‌​​​​​‍‌​​​‌‌​​‍‌​​‌‌​‌​‍‌​​‌​​​‌‍‌​​‌‌​‌‌‍‌​​‌‌​​‌‍‌​​‌​‌‌​‍‌​​‌​​‌‌‍‌​​‌‌​‌​‍‌‌​‌​​​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​‌​​‌​‍‌​​‌​​‌‌

参考可道云的开发文档,它其实是内置提供了X-Sendfile的支持,与此同时你的WEB服务器也要能对X-sendfile提供支持。
Nginx与Light httpd默认提供,Apache需要安装额外模块,请参考此文
本教程以Nginx为例进行设置。

一些小修改

可道云截止 version 4.39 还未对其代码内写错了X-sendfile头部的问题修复,您可能需要进行一些修改。

#KOD/app/function/file.function.phpL1042左右,关于输出X-Sendfile头部的代码:

    $server = strtolower($_SERVER['SERVER_SOFTWARE']);
    if($server && $GLOBALS['config']['settings']['httpSendFile']){
        if(strstr($server,'nginx')){//nginx
            header('X-Sendfile: '.$file);
        }else if(strstr($server,'apache')){ //apache
            header("X-Accel-Redirect: ".$file);
        }else if(strstr($server,'http')){//light http
            header( "X-LIGHTTPD-send-file: " . $file);
        }
        return;
    }
    

其中,nginxapache的头部写反了..
将他们对换位置:

    $server = strtolower($_SERVER['SERVER_SOFTWARE']);
    if($server && $GLOBALS['config']['settings']['httpSendFile']){
        if(strstr($server,'nginx')){//nginx
            header("X-Accel-Redirect: ".$file);
        }else if(strstr($server,'apache')){ //apache
            header('X-Sendfile: '.$file);
        }else if(strstr($server,'http')){//light http
            header( "X-LIGHTTPD-send-file: " . $file);
        }
        return;
    }

随后即可正常工作。

对Nginx进行配置

我们假定我们的网站目录为/www/wwwroot/web/
我们在此搭建了可道云,现在我需要下载/www/wwwroot/web/data/1.txt
或者是/www/1.txt
这时,下载文件时输出头部应该为:X-Accel-Redirect: /www/wwwroot/web/data/1.txt
那我们访问的网页路径其实是/www
但如果头部输出的是X-Accel-Redirect: /data/1.txt
那我们访问的网页路径就是/data
这样让你理解接下来的配置为什么要那样设置。

打开对应站点的配置,增添一段在合适的位置(推荐在SSL相关配置后添加)

location /www {
root /;
internal;
}

熟悉nginx配置的文件的人应该能看懂了,我们头部指示下载的文件是在www目录,而www目录是在/根目录下,internal代表该目录仅能在nginx内部处理,不允许外部直接访问。

如果我需要仅data目录能被下载

如果是情况二,也就是X-Accel-Redirect/data/1.txt但其目录其实在/www/wwwroot/web/data也就是可道云目前的结构,那么配置应该写为:

location /data {
root /www/wwwroot/web;
internal;
}

但是,需要另外对可道云进行修改,使可道云请求下载的header内的地址变为/data/1.txt否则会无法找到文件,如果按照上面的配置修改了nginx但是可道云没有做对应修改,那么nginx收到的请求下载的路径应该是:
/www/wwwroot/web/www/wwwroot/web/data/1.txt
这显然不是我们想要的。
我们需要在刚才修正header的地方加以修改:

    //调用webserver下载
    $server = strtolower($_SERVER['SERVER_SOFTWARE']);
    $x_file = str_replace(BASIC_PATH,"",$file);
    if($server && $GLOBALS['config']['settings']['httpSendFile']){
        if(strstr($server,'nginx')){//nginx
            header("X-Accel-Redirect: /".$x_file);
        }else if(strstr($server,'apache')){ //apache
            header('X-Sendfile: '.$file);
        }else if(strstr($server,'http')){//light http
            header( "X-LIGHTTPD-send-file: " . $file);
        }
        return;
    }

在此,增加了变量x_file作为处理变量,目的是将文件地址中删掉可道云目录的部分,这里利用了KOD内置定义的目录BASIC_PATH按照我们上面的配置也就是/www/wwwroot/web/然后使用了字符替换函数替换为空,并且在请求头时补全一个/使路径完整。
相关已定义的变量可以在#KOD/config/config.php找到。
这就做之后就可以只允许可道云下载data目录下的文件了,且可道云根目录及上级目录则无法下载文件。

在可道云开启httpSendFile

在可道云目录下的config创建新文件setting_user.php写入

<?php
$GLOBALS['config']['settings']['httpSendFile'] = true;

也可以附加到现有配置文件(如果有)
此时,试着下载一个文件吧。

后话

利用这个特性,php能够下载web目录之外的内容,但是nginx内配置x-sendfile的目录最多到第二层,也就是根目录的下一层而无法直接从根目录开始访问,这样可以避免关键文件被下载。
同时,不知道是不是我个人的原因,php文件有可能会被其他配置所影响,导致无法下载.php文件。
对于个人站点我推荐将配置设置到/www这样可以方便调试其他站点,公有站点请最好设置到/data以确保安全。