在Nginx中使用X-Sendfile头提升PHP文件下载的性能(针对大文件下载)
很多时候用户需要从网站下载文件,如果文件是可以通过一个固定链接公开获取的,那么我们只需将文件存放到webroot下的目录里就好。但大多数情况下,我们需要做权限控制,例如下载PDF账单,又例如下载网盘里的档案。这时,我们通常借助于脚本代码来实现,而这无疑会增加服务器的负担。
例如下面的代码:
<?php //用户身份认证,若验证失败跳转 authenticate(); //获取需要下载的文件,若文件不存在跳转 $file=determine_file(); //读取文件内容 $content=file_get_contents($file); //发送合适的HTTP头 header("Content-type:application/octet-stream"); header('Content-Disposition:attachment;filename="'.basename($file).'"'); header("Content-Length:".filesize($file)); echo$content;//或者readfile($file); ?>
一、这样做有什么问题?
这样做意味着我们的程序需要将文件内容从磁盘经过一个固定的buffer去循环读取到内存,再发送给前端web服务器,最后才到达用户。当需要下载的文件很大的时候,这种方式将消耗大量内存,甚至引发php进程超时或崩溃。Cache也很头疼,更不用说中断重连的情况了。
一个理想的解决方式应该是,由php程序进行权限检查等逻辑判断,一切通过后,让前台的web服务器直接将文件发送给用户——像Nginx这样的前台更善于处理静态文件。这样一来php脚本就不会被I/O阻塞了。
二、什么是X-Sendfile?
X-Sendfile是一种将文件下载请求由后端应用转交给前端web服务器处理的机制,它可以消除后端程序既要读文件又要处理发送的压力,从而显著提高服务器效率,特别是处理大文件下载的情形下。
X-Sendfile通过一个特定的HTTPheader来实现:在X-Sendfile头中指定一个文件的地址来通告前端web服务器。当web服务器检测到后端发送的这个header后,它将忽略后端的其他输出,而使用自身的组件(包括缓存头和断点重连等优化)机制将文件发送给用户。
不过,在使用X-Sendfile之前,我们必须明白这并不是一个标准特性,在默认情况下它是被大多数web服务器禁用的。而不同的web服务器的实现也不一样,包括规定了不同的X-Sendfile头格式。如果配置失当,用户可能下载到0字节的文件。
使用X-Sendfile将允许下载非web目录中的文件(例如/root/),即使文件在.htaccess保护下禁止访问,也会被下载。
不同的web服务器实现了不同的HTTP头
SENDFILE头
使用的WEB器
X-Sendfile
Apache,Lighttpdv1.5,Cherokee
X-LIGHTTPD-send-file
Lighttpdv1.4
X-Accel-Redirect
Nginx,Cherokee
使用X-SendFile的缺点是你失去了对文件传输机制的控制。例如如果你希望在完成文件下载后执行某些操作,比如只允许用户下载文件一次,这个X-Sendfile是没法做到的,因为后台的php脚本并不知道下载是否成功。
三、怎样使用?
Apache请参考mod_xsendfile模块。下面我介绍Nginx的用法。
Nginx默认支持该特性,不需要加载额外的模块。只是实现有些不同,需要发送的HTTP头为X-Accel-Redirect。另外,需要在配置文件中做以下设定
location/protected/{ internal; root/some/path; }
表示这个路径只能在Nginx内部访问,不能用浏览器直接访问防止未授权的下载。
于是PHP发送X-Accel-Redirect给Nginx:
]<?php $filePath='/protected/iso.img'; header('Content-type:application/octet-stream'); header('Content-Disposition:attachment;filename="'.basename($file).'"'); //让Xsendfile发送文件 header('X-Accel-Redirect:'.$filePath); ?>
这样用户就会下载到/some/path/protected/iso.img这个路径下的文件。
如果你想发送的是/some/path/iso.img文件,那么Nginx配置应该是
location/protected/{ internal; alias/some/path/;#注意最後的斜杠 }