readfile() 下载总是打开而非另存为,因浏览器依 Content-Type 决定行为;需清空缓冲、设 application/octet-stream 或带 attachment 的 Content-Disposition 头,并正确编码中文文件名。

为什么 readfile() 下载总是打开而不是另存为?
因为浏览器默认按 Content-Type 决定行为,不是 PHP 函数本身能强制“下载”。readfile() 只负责输出文件内容,不控制 HTTP 头;漏设或错设响应头,文件就会被浏览器直接解析(比如 PDF 打开、图片显示、文本渲染)。
关键在三步:清空缓冲区 → 设置正确头 → 调用 readfile()。缺一不可。
-
ob_end_clean()必须调用,否则前面的 echo、空白符或 BOM 会导致 headers already sent 错误 -
Content-Type: application/octet-stream最稳妥,避免浏览器猜类型;若明确类型(如image/png),需配合Content-Disposition: attachment才能强制下载 -
Content-Disposition的filename=值必须用双引号包裹,且中文名要 URL 编码(rawurlencode()),否则 Chrome/Firefox 可能乱码或截断
PHP 下载大文件时内存爆掉或超时怎么办?
readfile() 是边读边输出,不全载入内存,本身不占大量内存 —— 但前提是没其他代码把内容缓存住。常见崩盘原因其实是开发环境配置或逻辑干扰。
- Apache 的
mod_deflate或 Nginx 的gzip on会尝试压缩整个响应体,对大文件极易超时或 OOM;下载接口应单独禁用压缩(Nginx 加gzip off;,Apache 加SetEnv no-gzip 1) - PHP 的
max_execution_time默认 30 秒,下载 500MB 文件很可能超时;建议用set_time_limit(0),但仅限 CLI 安全场景;Web 环境更推荐前端加进度条 + 分块(fpassthru()配合fopen()流式读) - 不要在
readfile()前用file_get_contents()或file(),那才是真吃内存
readfile() 和 fpassthru() 到底该选哪个?
二者都流式输出,但适用阶段不同:readfile() 适合已知路径、权限可控的静态文件;fpassthru() 适合需要前置校验、动态生成或从非文件源(如 cURL stream、数据库 BLOB)输出的场景。
立即学习“PHP免费学习笔记(深入)”;
-
readfile('/path/to/file.zip')简单直接,但路径必须是服务器本地绝对路径;相对路径易出错,__DIR__ . '/files/' . $fname更安全 -
fpassthru()必须搭配fopen(),且文件指针要定位到开头(fseek($fp, 0));好处是可以先fstat()拿 size 填Content-Length,支持断点续传 - 注意:Windows 下用
fopen()打开二进制文件必须加b标志('rb'),否则换行符可能被错误转换
下载后文件名乱码或变成下划线\_?
本质是 Content-Disposition 中 filename 和 filename* 的兼容写法没处理好。RFC 5987 规定:ASCII 名用 filename=,非 ASCII 名必须用 filename*= 并带编码声明。
- 别只写
filename=中文.pdf—— Firefox 无视,Chrome 显示乱码 - 正确写法:
Content-Disposition: attachment; filename="zh.pdf"; filename*=UTF-8''%E4%B8%AD%E6%96%87.pdf - PHP 里用
rawurlencode($name)得到百分号编码,再拼进filename*=UTF-8''...;filename=部分可留英文 fallback(如filename="download.pdf") - 某些老旧 IE(≤IE8)不支持
filename*,只能靠iconv('UTF-8', 'GB2312', $name)降级,但风险高(字符丢失),不如统一用英文名 + 前端 JS 补救
Cache-Control: no-cache)、反向代理的 body 缓冲(proxy_buffering off),以及 PHP-FPM 的 request_terminate_timeout——这些不配好,readfile() 写得再规范也会在半路断掉。



















