PHP文件下载核心是正确设置HTTP响应头以强制浏览器下载而非显示,需检查文件权限、用readfile流式输出、处理中文文件名编码、动态生成时写入php://output,并注意路径遍历防护、类型校验与访问控制。

PHP 用 header() 实现文件下载,核心在于正确设置响应头、禁用缓存、指定 MIME 类型和文件名,否则浏览器可能直接渲染内容或报错。
为什么 header("Content-Disposition: attachment") 不生效?
最常见原因是输出已提前开始——哪怕一个空格、BOM 字节或 echo 都会让 header() 失败,并触发 Warning: Cannot modify header information 错误。
- 检查 PHP 文件是否以 UTF-8 无 BOM 格式保存(尤其 Windows 编辑器易带 BOM)
- 确认文件开头没有空行、
<?php前无任何字符 - 下载逻辑前不能有任何
echo、print、var_dump或错误提示(开启display_errors也会干扰) - 可加
if (headers_sent($file, $line)) { die("Headers already sent in $file on line $line"); }快速定位
header("Content-Type") 应该填什么?
不是所有文件都适合用 application/octet-stream。它虽通用,但会丢失原始类型信息,影响某些客户端处理(如 PDF 预览、图片缩略图)。应优先使用真实 MIME 类型:
- PDF:用
application/pdf,而非application/octet-stream - Excel(.xlsx):用
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - 中文文件名需额外处理:用
mb_convert_encoding($filename, "UTF-8", "auto")确保编码一致,再通过rawurlencode()编码后拼入filename*=UTF-8''...字段 - 若不确定类型,可用
finfo_open(FILEINFO_MIME_TYPE)动态探测(比扩展名更可靠)
如何安全地读取并输出文件内容?
别用 readfile() 直接吐大文件——内存不打紧,但没流控、没进度、无法中断。小文件(
立即学习“PHP免费学习笔记(深入)”;
ob_end_clean(); // 清掉已有输出缓冲
header("Content-Length: " . filesize($filepath));
$fp = fopen($filepath, 'rb');
while (!feof($fp) && connection_status() === CONNECTION_NORMAL) {
echo fread($fp, 8192);
ob_flush();
flush();
}
fclose($fp);
- 必须加
connection_status() === CONNECTION_NORMAL检查客户端是否断开,避免无效传输 - 调用
ob_end_clean()前确保没开启其他输出缓冲(如框架自带的 buffer) - 敏感路径要校验:禁止用户传入
../../etc/passwd这类路径,用realpath()+ 白名单目录做前缀比对
真正难的不是写几行 header(),而是控制整个请求生命周期——从入口路由、路径解析、权限校验,到缓冲管理、连接状态监听、异常终止处理。漏掉任意一环,下载就可能卡住、截断或暴露路径。



















