- 本文地址: https://www.laruence.com/2012/05/02/2613.html
- 转载请注明出处
一般来说, 我们可以通过直接让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.
Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看 🙂
大佬,请教个问题,为什么通过向360、迅雷等下载工具下载的时候,显示大小为0 ,但是并不影响下载,还是能正常下载,资料也完整的,我想请教下,文件大小怎么和下载工具检测的大小一致呢?尝试了好多方法,都没有成功!
下载下来文件为0字节
Thanks for finally writing about >让PHP更快的提供文件下载 | 风雪之隅 <Liked it!
[…] 本文地址: http://www.laruence.com/2012/05/02/2613.html […]
$file = “/tmp/中文名.tar.gz”;
$filename = basename($file);
basename获取中文的文件名获取不到
If some one desires expert view about blogging afterward i propose him/her to pay a visit this web site, Keep up the nice work.
What’s up, for all time i used to check blog posts here in the early hours in the
dawn, because i like to gain knowledge of more and more.
[…] 文章来源:http://www.laruence.com/2012/05/02/2613.html […]
[…] 文章来源:http://www.laruence.com/2012/05/02/2613.html […]
[…] http://www.laruence.com/2012/05/02/2613.html […]
[…] 让PHP更快的提供文件下载 类别 未分类 | […]
Very shortly this web site will be famous amid all blogging and site-building users,
due too it’s fastidious articles or reviews
Si estamos pensando en adoptar un Rottweiler, contamos
con que tener en consideración que es un cánido de raza
considerada peligrosa.
my web blog http://Videosflax.com/
[…] 最近有点闲暇时间了,不小心看了@风雪之隅的一篇的文章《让PHP更快的提供文件下载》后,利用实际项目中的业务场景觉得有必须要去优化附件下载功能了。鸟哥的文章里面主要介绍的基于apache来做XSendfile讲解的,我有点强迫症,我对我的生产环境《顶岗实习管理系统》进行升级改造,我的webserver是nginx,所以基于nginx官方网站的XSendfile说明特做此改造笔记。 […]
[…] Nginx 官方 XSendfile 文档 让PHP更快的提供文件下载 Yii Framework 对 XSendfile 的支持 站长推荐PHP判断字符串中是否含有中文Ubuntu […]
[…] 让PHP更快的提供文件下载 […]
见到一些网站 采用 下载 实体文件加上 token的方式 来下载 如xxxx.mp3?uijsd2342ewijl234, 感觉这种处理方法也能统计 比 X-Sendfile 更直接 些,但也说不上有什么优势。。
[…] 今天做空号检测系统的结果下载的时候碰到了这个问题。就Google了一番,发现鸟哥的这篇文章不错,果然是大神,哈哈。 […]
[…] 让PHP更快的提供文件下载 […]
[…] Nginx 官方 XSendfile 文档 让PHP更快的提供文件下载 Yii Framework 对 XSendfile […]
Hey! I’m at work surfing around your blog from my new apple iphone! Just wanted to say I love reading through your blog and look forward to all your posts! Keep up the great work!
Very nice article, just what I wanted to find.
I am greatful that you taking a moment to see my information.
You are free to consider a look at my very own web site also for
more information and facts and effective suggestions: Luis
I know this if off topic but I’m looking into starting my own weblog and was curious what all is required to get set up? I’m assuming having a blog like yours would cost a pretty
penny? I’m not very web smart so I’m not 100% sure. Any suggestions or advice would be greatly appreciated. Appreciate it
I appreciate you currently taking a time period to evaluation my account. You are free to take a glimpse at my personal webpage also for extra info and valuable ideas … Pest Control Charlotte
忽然间跑到大神这里来了。
[…] 并且把响应的文件直接发送给Client. 本文转自 风雪之隅 php 应用PHP文件下载 ← PHP生成ZIP文件 用PclZip压缩和解压缩zip文件 […]
[…] 注:http://www.laruence.com/2012/05/02/2613.html有关于下载时出现乱码情况的解决办法。 […]
[…] 注:http://www.laruence.com/2012/05/02/2613.html有关于下载时出现乱码情况的解决办法。 This entry was posted in 有用的集合 by 管理. Bookmark the permalink. […]
[…] [转]让PHP更快的提供文件下载 作者: Laruence本文地址: http://www.laruence.com/2012/05/02/2613.html转载请注明出处 […]
[…] 风雪之隅 » PHP应用 Posted in: php / Tagged: 让PHP更快的提供文件下载 […]
大家有试下nginx的X-Accel-Redirect,当文件不存的情况吗,是不是会请求很多次?
header(“Location: “.$uri);和该方法的区别何在?
[…] 让PHP更快的提供文件下载 […]
对于简单的字符串查找应避免使用正则表达式!
##################################################
<?php
$file = "/tmp/中文名.tar.gz";
$filename = basename($file);
header("Content-type: application/octet-stream");
// 处理中文文件名
$ua = $_SERVER["HTTP_USER_AGENT"];
$encoded_filename = rawurlencode($filename);
if (strpos($ua, 'MSIE') !== false) {
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
} else if (strpos($ua, 'Firefox') !== false) {
// 博主的代码有误,这里的文件名需要编码
header("Content-Disposition: attachment; filename*=\"utf8''" . $encoded_filename . '"');
} else {
// 注意: 如果文件名包含双引号, 可能会丢失文件名中的第一个双引号开始的内容
header('Content-Disposition: attachment; filename="' . $filename . '"');
}
// 博主的代码有误,这行代码已被上面那段代替了, 应该注释掉
//header('Content-Disposition: attachment; filename="' . basename($file) . '"');
…
…
##################################################
@Anonymous 已更正, thanks
$encoded_filename = rawurlencode($filename);
这段更简洁,验证过没问题。
[…] 本文地址: http://www.laruence.com/2012/05/02/2613.html […]
[…] 让PHP更快的提供文件下载 […]
$encoded_filename = urlencode($filename);
$encoded_filename = str_replace(“+”, “%20”, $encoded_filename);
===========================
可否直接用
$encoded_filename = rawurlencode($filename);
来替代呢
…不好意思哈哈。。。去掉if else 下面那条 ——–header(‘Content-Disposition: attachment; filename=”‘ . $filename . ‘”‘); —– 后就正常了。 谢谢博主了哈。
Laruence 您好,我用了你的这段代码,在IE下,中文文件名还是出问题了。IE版本是9.0。谷歌正确
以后就来这里学习了!博主很强大
[…] 来源于http://www.laruence.com/2012/05/02/2613.html […]
[…] 原文地址: http://www.laruence.com/2012/05/02/2613.html […]
好文,以后一定用的上
啊!看了回复原来还有不足的地方,前两天做excel导出的时候就遇到中文名了!
额,用上了!
请问博主为啥提交内容 “{?PHP 1 ?} {?PHP 2 ?}” (“{“、 “}” 分别为小于号、大于号) 后没显示? 诡异的被过滤了
Test
晕,代码再次被过滤了
测试代码(PHP 5.4.0-3):
header(‘Content-Type: text/plain’);
header(‘Content-Disposition: attachment; filename*=”UTF-8\’\’this%20is%20a%20filename”‘);
Retry
测试代码(PHP 5.4.0-3):
header(‘Content-Type: text/plain’);
header(‘Content-Disposition: attachment; filename*=”UTF-8\’\’this%20is%20a%20filename”‘);
怪了,测试代码发不上
测试代码(PHP 5.4.0-3):
测试代码(PHP 5.4.0-3):
Firefox 识别出的文件名(Firefox 14.0a2 for linux):
this is a filename
经测试文件名是需要经过 URL 编码
====================================================================
修正代码:
<?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);
$encoded_filename = rawurlencode($filename);
//if (preg_match("/MSIE/", $ua)) {
if (strpos($ua, 'MSIE') !== false) {
header('Content-Disposition: attachment; filename="' . $encoded_filename . '"');
//} else if (preg_match("/Firefox/", $ua)) {
} else if (strpos($ua, 'Firefox') !== false) {
//header("Content-Disposition: attachment; filename*=\"utf8''" . $filename . '"');
header("Content-Disposition: attachment; filename*=\"utf8''" . $encoded_filename . '"');
} else {
header('Content-Disposition: attachment; filename="' . $filename . '"');
}
// 这行代码已被上面的代码代替了, 应该注释掉
//header('Content-Disposition: attachment; filename="' . basename($file) . '"');
//……
大婶你好,我是一个开发新手,刚开始拜读您的文章,你能不能做一篇关于php制作验证码的深度分析?我们网站历经几次验证码修改,到现在防刷效果还算可以,但是用户肉眼识别也变得困难。我看过腾讯的验证码,识别很简单,您能分析下他们这个验证码是怎么做的吗,既保证体验性又防刷?
厉害啊 你是我的偶像
[…] 转自:http://www.laruence.com/2012/05/02/2613.html 此条目是由 admin 发表在 PHP 分类目录的。将固定链接加入收藏夹。 […]
无法判断是否支持是个问题呀.
reply 0xFP:
1.博主处理firefox部分代码没有错误,确实不要用url编码
2.文件名是不允许有引号的,所以你的else里面多虑了
我觉得
$encoded_filename = urlencode($filename);
$encoded_filename = str_replace(“+”, “%20”, $encoded_filename);
可以换为rawurlencode
这个太高深了,我点看不明白
if (preg_match(“/MSIE/”, $ua)) {
header(‘Content-Disposition: attachment; filename=”‘ .$encoded_filename . ‘”‘);
} else if (preg_match(“/Firefox/”, $ua)) {
// 博主的代码有误,这里的文件名需经过 URL 编码
header(“Content-Disposition: attachment; filename*=\”utf8”” . $encoded_filename . ‘”‘);
} else {
// 这里如何处理文件名包含双引号的情况?
header(‘Content-Disposition: attachment; filename=”‘ . $filename . ‘”‘ );
}
nginX的类似模块是:http://wiki.nginx.org/NginxXSendfile
if (preg_match(“/Firefox/”, $ua)) {
// 这里的 $filename 也需要经过 RFC1738 的 URL Encoding
header(“Content-Disposition: attachment; filename*=\”utf8”” . $filename . ‘”‘);
} else {
// 如果 $filename 包含 `”‘ 会导致文件名部分丢失
header(‘Content-Disposition: attachment; filename=”‘ . $filename . ‘”‘);
}
[…] 并且把响应的文件直接发送给Client. 本文转自 风雪之隅 标签:PHP文件下载 上一篇:PHP生成ZIP文件 […]
额.. 看到有人说通过PHP函数获取apache模块列表了.
不知道其他服务端软件是不是这种方式..
如何判断apache是否支持那个X-Sendfile…
[…] 本文地址: http://www.laruence.com/2012/05/02/2613.html […]
学习了。。
这个方案的好处是默认就支持断点续传了
另一个好处是,可以实现对静态文件的ACL访问控制
不过悲剧的是手头的虚拟主机不提供mod_xsendfile…
鸟哥
foreach($arr AS $i){
echo <<<HTML
$i
HTML;
}
syntax error, unexpected $end
这个报错是什么原因
[…] php5.4新功能Traits » 借助X-sendfile模块实现文件下载速度和性能优化方案 本文的灵感来自于:让PHP更快的提供文件下载 […]
恩,非常不错的做法,刚刚尝试可以正常运行。
请问哈鸟哥,如果这个要求支持断点续传了?怎么改进
呃,不熟悉php,但是这样不会增加产品对环境的依赖么?如果做迁移,就会有代码修改的成本吧。
Apache的Sendfile模块好像是需要另外安装的,所以可能并不是很通用,我之前的框架里边就采用了这种方法,
$contentType = $contentType ? $contentType : ‘application/octet-stream’;
header(“Pragma:public”);
header(“Expires:0”);
header(“Content-type:” . $contentType . ‘;charset=UTF-8’);
header(“Accept-Ranges:bytes”);
$charset = Config::get(‘charset’);
if ($charset != ‘UTF-8’) {
$mbEncodings = array(‘GBK’=>’CP936’, ‘GB2312’=>’CP936’);
if(isset($mbEncodings[$charset])) $charset = $mbEncodings[$charset];
$fileName = mb_convert_encoding($fileName, $charset, ‘UTF-8’);
}
if (” != $fileCfg[‘contents’]) {
ob_clean();
$fileSize = strlen($fileCfg[‘contents’]);
} else if (” != $fileCfg[‘filepath’]){
ob_clean();
$fileSize = filesize($fileCfg[‘filepath’]);
}
if($fileSize > 0)
header(“Accept-Length:”.$fileSize);
$ua = $_SERVER[‘HTTP_USER_AGENT’];
if(preg_match(‘/firefox/i’, $ua)) {
$fileName = str_replace(‘+’, ‘%20’, urlencode($fileName));
$fileName = “utf8”” . $fileName;
header(“Content-Disposition:attachment; filename*=\”{$fileName}\””);
} else if(preg_match(‘/msie/i’, $ua)){
$fileName = str_replace(‘+’, ‘%20’, urlencode($fileName));
header(“Content-Disposition:attachment; filename=\”{$fileName}\””);
} else {
header(“Content-Disposition:attachment; filename=\”{$fileName}\””);
}
if (” != $fileCfg[‘contents’]) {
echo $fileCfg[‘contents’];
} else if(” != $fileCfg[‘filepath’]) {
$serverSoft = $_SERVER[‘SERVER_SOFTWARE’];
if(preg_match(‘/apache/i’, $serverSoft)) {
readfile($fileCfg[‘filepath’]);
} else if (preg_match(‘/lighttpd/i’, $serverSoft)) {
header(‘X-LIGHTTPD-Send-file:’ . $fileCfg[‘filepath’]);
} else if (preg_match(‘/nginx/i’, $serverSoft)) {
$nginxSendfileMaps = Config::get(‘NGINX_SENDFILE_MAP’);
if(false == $nginxSendfileMaps) {
readfile($fileCfg[‘filepath’]);
} else {
$filePath = $fileCfg[‘filepath’];
foreach($nginxSendfileMaps as $map) {
if(0 === strpos($filePath, $map[0])) {
$filePath = str_replace($map[0], $map[1], $filePath);
break;
}
}
header(‘X-Accel-Redirect:’ . $filePath);
}
}
}
呃…好吧,不支持代码片段,代码也被截断了,看这里吧 http://pastie.org/3848723
鸟哥写的很好,小弟在这里也班门弄斧一下,一个从Ruby On Rails中迁移过来的send_file()方法,具体使用参见 http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file
[code=’php’]
‘application/octet-stream’,
‘disposition’ => ‘attachment’
);
$options = array_merge($defaults, $options);
foreach (array(‘type’, ‘disposition’) as $arg) {
if (is_null($options[$arg])) {
throw new InvalidArgumentException(“{$arg} option required”);
}
}
$disposition = $options[‘disposition’];
if (isset($options[‘filename’])) {
$disposition .= “; filename=\”{$options[‘filename’]}\””;
}
if (! headers_sent()) {
header(“Content-Type: {$options[‘type’]}”);
header(“Content-Disposition: {$disposition}”);
header(“Content-Transfer-Encoding: binary”);
}
$x_sendfile_supported = $options[‘x_sendfile’] && in_array(‘mod_xsendfile’, apache_get_modules());
if (! headers_sent() && $x_sendfile_supported) {
header(“X-Sendfile: {$path}”);
} else {
@readfile($path);
}
}
?>
[/code]
原来还有这么个好东西,查了下:
nginx的模块是:http://wiki.nginx.org/XSendfile
nginx的是这个吧
http://wiki.nginx.org/X-accel
我试了下,可以用
header(‘Content-Disposition: attachment; filename=”test.zip”‘);
#http://wiki.nginx.org/X-accel
header(‘X-Accel-Redirect:/test.zip’);
大牛哥, 你好
最近遇到一个mysql索引的很离奇的问题
create table t1(x char(10), y char(10), key hs using(x,y))
describe select * from t1 where x>’dfd’
显示查询使用了索引并且type为range
我不明白为什么hash索引会有这样的行为, 去百度, google, 各大论坛仔细找了都没结果
非常希望能得到你的帮助, 谢谢
android系统的浏览器上中文名的文件的下载多有问题
为啥PHP不调用系统底层sendfile调用?直接省掉open,read,write的内核上下文切换。相对会好一点, webserver就做了这些。
不错,以前readfile的时候就总害怕文件太大出问题。回家测试测试。