使用x-sendfile提升PHP文件下载效率

最近做的项目有个工具库模块,需要实现工具的上传与下载。上传与下载都使用的是Laravel内置的方法,本来都没什么问题,今天无意上传了一个两百多M的文件,上传很顺利,但当点击下载时,发现会出现下载回来的数据不全的情况。暂时没找到是什么原因,估计是内存方面的问题。

修改了几次代码后都无法解决,那就只能换一种方式了。多次搜索后,发现了一种直接返回 x-sendfile 头实现文件下载方式,完美的解决了我的问题。

Nginx

Nginx默认支持x-sendfile模式,所以使用起来十分方便。

配置虚拟域名的配置文件,添加如下内容
1
2
3
4
5
#伪造下载路径
location /down/{
internal; #只允许内部访问,防止盗链
alias /home/www/myweb/public/uploads/utool/;
}
PHP返回header头
1
2
3
4
5
$filename = 'test.zip';
$file_path = '/down/test.zip';//物理真实路径是 /home/www/myweb/public/uploads/utool/test.zip ,至于为什么请自行脑补
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('X-Accel-Redirect:'.$file_path);

就这么轻松的两步就实现了大文件的下载,是不是很简单。

Apache

Apache需要加载 module mod_xsendfile 模块来实现相对应的功能,所以稍微复杂一点

加载 module mod_xsendfile

项目地址上下载对应的 mod_xsendfile.so,放到apache的modules目录下.然后修改 http.conf ,添加如下内容

1
LoadModule xsendfile_module modules/mod_xsendfile.so
开启 xsendfile

修改虚拟域名的配置文件,添加如下内容

1
2
3
4
5
6
7
8
# 开启XSendFile
XSendFile On
<Directory "D:/wamp/www/myweb/public/uploads/utool/">
# 只允许本地链接,防止盗链
<IfDefine APACHE24>
Require local
</IfDefine>
</Directory>
PHP返回header头
1
2
3
4
5
$filename = 'test.zip';
$file_path = 'D:/wamp/www/myweb/public/uploads/utool/test.zip';
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('X-Sendfile:'.$file_path);

使用 x-sendfile 模式后,PHP只需返回header头,后续就不需要处理,所以效率有了明显提升。

如果在下载过程中,出现中文文件名乱码的情况,可以使用函数 rawurlencode 对文件名做转码,这样就能解决乱码问题。