使用 Nginx 的 X-Sendfile (X-Accel-Redirect) 机制提升 PHP 文件下载性能

使用场景

此处说的是静态文件的下载,有时候我们可以直接放在七牛云这样的云平台上,或者直接交由nginx作为静态文件处理,但是对于有权限的文件,或者增加统计的话就需要经过后端服务器。类似于这样:

<?php

$file = "/tmp/中文名.tar.gz";
$filename = basename($file);
header("Content-type: application/octet-stream");
//处理中文文件名乱码的问题
$ua = $_SERVER["HTTP_USER_AGENT"];
$encoded_filename = rawurlencode($filename);
if (preg_match("/MSIE/", $ua)) {
    header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
} else if (preg_match("/Firefox/", $ua)) {
    header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"');
} else {
    header('Content-Disposition: attachment; filename="' . $filename . '"');
}
header("Content-Length: ". filesize($file));

readfile($file);

虽然PHP的 readfile 尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用 MMAP(如果支持), 或者是一个固定的 buffer 去循环读取文件, 然后发送给nginx,最后才发送给用户,而对于 Nginx + fpm 如果他们分开部署的话, 那还会带来额外的网络IO。

一个理想的解决方式应该是,由 php 程序进行权限检查等逻辑判断,一切通过后,让前台的 web 服务器直接将文件发送给用户——像 Nginx 这样的前台更善于处理静态文件。这样一来 php 脚本就不会被 I/O 阻塞了。

什么是 X-Sendfile

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

X-Sendfile 通过一个特定的 HTTP header 来实现:在 X-Sendfile 头中指定一个文件的地址来通告前端 web 服务器。当 web 服务器检测到后端发送的这个 header 后,它将忽略后端的其他输出,而使用自身的组件(包括 缓存头 和 断点重连 等优化)机制将文件发送给用户。

不过,在使用 X-Sendfile 之前,我们必须明白这并不是一个标准特性,在默认情况下它是被大多数 web 服务器禁用的。而不同的 web 服务器的实现也不一样,包括规定了不同的 X-Sendfile 头格式。如果配置失当,用户可能下载到 0 字节的文件。

使用 X-Sendfile 将允许下载非 web 目录中的文件(例如/root/),即使文件在 .htaccess 保护下禁止访问,也会被下载。

SENDFILE 头使用的 WEB 服务器
X-SendfileApache, Lighttpd v1.5, Cherokee
X-LIGHTTPD-send-fileLighttpd v1.4
X-Accel-RedirectNginx, Cherokee

使用 X-SendFile 的缺点是你失去了对文件传输机制的控制。例如如果你希望在完成文件下载后执行某些操作,比如只允许用户下载文件一次,这个 X-Sendfile 是没法做到的,因为后台的 php 脚本并不知道下载是否成功。

怎么使用

Apache 请参考 mod_xsendfile 模块。下面我介绍 Nginx 的用法。

Nginx 默认支持该特性,不需要加载额外的模块。只是实现有些不同,需要发送的 HTTP 头为 X-Accel-Redirect。另外,需要在配置文件中做以下设定

location /download/ {
  internal;
  root   /some/path;
}

internal 表示这个路径只能在 Nginx 内部访问,不能用浏览器直接访问防止未授权的下载。

<?php

$filePath = "/download/a.jpg";

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

header('X-Accel-Redirect: ' . $filePath);

这样用户就会下载到 /some/path/download/a.jpg 这个路径下的文件。

如果你想发送的是/some/path/a.jpg 文件,那么 Nginx 配置应该是

location /download/ {
  internal;
  alias   /some/path/; # 注意最后的斜杠
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值