让 PHP 更快的提供文件下载

虫虫 发布于 2012/05/02 18:00
阅读 2K+
收藏 70
PHP

一般来说, 我们可以通过直接让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 = urlencode($filename);
        $encoded_filename = str_replace("+", "%20", $encoded_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-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 = urlencode($filename);
        $encoded_filename = str_replace("+", "%20", $encoded_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-Disposition: attachment; filename="' . basename($file) . '"');
     
        //让Xsendfile发送文件
        header("X-Sendfile: $file");

X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.

Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看 :)

 

加载中
0
主编
主编
可以防到连码
Raynor1
Raynor1
楼主写的没有。要自己写。
0
该用户已被和谐
该用户已被和谐
我觉得可以把文章出处的引用放到文章的开始的地方。
该用户已被和谐
该用户已被和谐
回复 @毛毛哦 : 既然已经做了,那么就应该做的更好。呵呵。
毛毛哦
毛毛哦
有原文链接已经很厚道了,大部分网站都是没有原文链接的
0
Raynor1
Raynor1
可以的喔。。不错。顶一个好东西呀。
0
蟋蟀哥哥
蟋蟀哥哥
原来来自鸟哥blog啊。我以为又是翻译或者自己写的。鸟哥的blog真不错
0
BossKiller
BossKiller
现学现用,下周一系统正常负载的时候就知道提升明不明显了。
0
旁边白
旁边白
嘿嘿,鸟哥写的。鸟哥的英文名好像lucene哟
旁边白
旁边白
回复 @hevin : 嘿嘿,我说鸟哥的英文名好像全文索引Lucene哟。。。
盘先海
盘先海
名字写对也是对作者的一种尊重:Laruence
0
hokim
hokim
需要服务器支持哦    
0
兔bug
兔bug
不错,刚好用上!
0
rickyleo
rickyleo

看了鸟哥的blog后 给phpwind87加了这个功能扩展 哈哈

http://blog.phpwind.me/?p=378

0
lusi
lusi
这个不错,收藏了
返回顶部
顶部