在微博上偶然看到一篇介绍php更快下载文件的方法,其实就是利用web服务器的xsendfile特性,鸟哥的博客中只说了apache的实现方式,我找到了介绍nginx实现方式的文章,整理一下!
let's go!
一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.
但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.
<?php $file = "/tmp/dummy.tar.gz"; header("Content-type: application/octet-stream"); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); header("Content-Length: ". filesize($file)); readfile($file);
但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.
于是, 我们做一下修改(参考: :
<?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);
恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.
输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.
那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?
今天, 我看到了一个有意思的文章: How I PHP: X-SendFile.
我们可以使用Apache的module mod_xsendfile, 让Apache直接发送这个文件给用户:
<?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 . '"'); } //让Xsendfile发送文件 header("X-Sendfile: $file");
X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.
nginx中也有对应的xsendfile模块,默认已经包含了senfile模块了,你如果安装完nginx你会在nginx.conf文件会看到“sendfile on;”
配置部分:
server { listen 80; server_name demo.markdream.com; root /var/vhost/demo; index index.php; # 这个是定义读取你的文件的目录的url开头 直接访问是不可以的 只能通过 location /protected { internal; alias /var/vhost/demo/uploadfiles; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; include fastcgi_params; } }
<?php //eg: http://demo.markdream.com/xsendfile/downloads.php?filename=hello.docx // 获取文件名 $filename = $_GET["filename"]; // 你可以在这里写下你的查询数据库等你所想的功能 …… header ( "Content-type: application/octet-stream" ); // 处理中文文件名 $ua = $_SERVER ["HTTP_USER_AGENT"]; if (preg_match ( "/MSIE/", $ua )) { $encoded_filename = rawurlencode ( $filename ); 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 . '"' ); } // 就这么简单一句话搞定 注意“protected”是和nginx配置文件的 protected要一致 header("X-Accel-Redirect: /protected/" . $filename); ?>
这里是使用的别名的方式,也可以使用直接访问的方式,具体看nginx官网的关于xsendfile的说明,一看就懂了!
参考和摘抄:
1.鸟哥:http://www.laruence.com/2012/05/02/2613.html
2.nginx 实现:https://www.markdream.com/technologies/programs/nginx-x-accel- redirect-php-practise.shtml?utm_source=tuicool&utm_medium=referral
3.nginx xsendfile官网说明:http://wiki.nginx.org/XSendfile