
PHP用header()强制下载文件时,Content-Disposition不生效
浏览器直接打开文件而不是下载,通常是因为Content-Disposition头没写对,或者前面有输出干扰了header发送。
- 必须在任何输出(包括空格、BOM、
echo、var_dump)之前调用header(),否则会报“headers already sent”错误 -
Content-Disposition的filename参数不能含中文,否则部分浏览器(如旧版IE)会乱码或截断;建议用filename*=UTF-8''xxx格式,例如:header('Content-Disposition: attachment; filename*=UTF-8\'\''.rawurlencode($filename)); - 务必搭配
Content-Transfer-Encoding: binary和Content-Length(可用filesize()获取),否则大文件可能中断或被截断
PHP读取大文件时内存爆满或超时
用readfile()直接输出没问题,但若先file_get_contents()再echo,整个文件会加载进内存——100MB文件就占100MB内存,极易OOM。
- 优先用
readfile($filepath),它边读边输出,内存占用恒定在几KB - 对超大文件(如>500MB),需配合
set_time_limit(0)和ignore_user_abort(true)防超时中断 - 避免用
fopen()+fread()手动循环读取,除非要加进度或校验——readfile()底层已优化,更稳更快
下载路径含中文或特殊字符时404或找不到文件
用户传来的$_GET['file']如果没过滤,可能被构造成../../etc/passwd,或者带URL编码混乱导致file_exists()返回false。
- 绝不直接拼接用户输入到
file_get_contents()或readfile()中 - 用
basename()剥离路径,再限定允许的目录前缀,例如:$safe_path = __DIR__ . '/downloads/' . basename($_GET['file']);,然后检查$safe_path是否仍在该目录下(用realpath()比对) - URL中传中文名时,前端应
encodeURIComponent(),后端用urldecode()还原,再mb_convert_encoding()转为系统编码(如UTF-8 → GBK仅Windows需)
PHP下载功能在Nginx/Apache下失效或被缓存
Web服务器可能拦截readfile()响应,或把PHP脚本当成静态资源走缓存逻辑,导致下载内容错乱或返回500。
立即学习“PHP免费学习笔记(深入)”;
- Nginx需确认没配
try_files把下载请求转给静态服务;Apache注意.htaccess里别有RedirectMatch误匹配download.php - 禁用代理/CDN缓存:加
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');和header('Expires: 0'); - 如果用
fastcgi_buffering off;(Nginx),可避免缓冲区截断大文件,但要确保PHP-FPM配置允许长响应
最常被绕过的点是BOM头和调试语句——哪怕文件开头有个看不见的UTF-8 BOM,header()就会失败;另外error_reporting(E_ALL)开着时,未定义变量警告也会提前输出,破坏header流程。上线前务必关掉所有调试输出,用headers_sent()检查一遍。











