核心原因是Content-Type和Content-Disposition响应头设置错误:必须设为application/pdf且不能含attachment,否则浏览器误判为下载文件或显示%PDF乱码。

PHP直接输出PDF流给浏览器,为什么点开是乱码或下载而不是预览?
核心原因就一个:Content-Type 和 Content-Disposition 响应头没设对。浏览器靠这两个头判断“这是个PDF,允许内嵌预览”,而不是当成未知二进制文件强制下载。
常见错误现象:
– PDF在Chrome里自动下载,不走PDF阅读器插件
– 页面显示一堆%PDF-1.7开头的乱码字符
– Firefox提示“无法加载此PDF文档”
- 必须设置
header('Content-Type: application/pdf'),不能用application/octet-stream或漏掉 - 必须**不设置**
Content-Disposition: attachment;如果写了这句,浏览器铁定弹下载 - 如果用了
Content-Disposition: inline,可加可不加——现代浏览器基本靠Content-Type就能识别,加了反而在某些旧IE下出问题 - 确保输出前没有空格、BOM、
echo或var_dump干扰——PDF流对头部空白极其敏感
从文件路径读取PDF并输出,怎么避免内存溢出?
用 readfile() 是最安全的选择。它不把整个PDF载入PHP内存,而是边读边吐给浏览器,适合几十MB的大文件。
别用 file_get_contents() + echo,尤其当PDF来自用户上传或第三方存储时,容易触发 memory_limit 报错。
立即学习“PHP免费学习笔记(深入)”;
-
readfile('/path/to/doc.pdf')之前,必须先header()设好类型和长度(可选) - 建议加上
header('Content-Length: ' . filesize($pdf_path)),帮助浏览器显示加载进度 - 如果PDF路径不可信(比如含用户输入),务必做白名单校验或重定向到固定目录,防止路径穿越(
../../../etc/passwd) - 不要用
fpassthru()配合fopen(),除非你真需要中间处理流——多一步就多一个出错点
动态生成PDF(如用TCPDF/FPDF/MPDF)后如何直接预览?
关键不是“生成”,而是“别写到磁盘”。所有主流PDF库都支持输出到 stdout 或返回字符串,但返回字符串会吃内存,优先选直接输出。
以 MPDF 为例:
– 错误做法:$mpdf->Output('file.pdf', 'F') 写磁盘再 readfile() —— 多一次IO,还留临时文件
– 正确做法:$mpdf->Output('report.pdf', 'I') —— I 表示 inline,它内部已帮你设好响应头并输出流
- FPDF:用
$pdf->Output('D') → 'I'(I= inline,D= download) - TCPDF:
$pdf->Output('doc.pdf', 'I'),同理 - 如果框架(如Laravel)拦截了原始响应头,得手动关掉输出缓冲:
ob_end_clean()再调库的Output() - 注意:这些库的
I模式默认会设Content-Disposition: inline,如果你发现它跟Nginx/Apache配置冲突(比如被强制加attachment),就改用S模式获取字符串,自己控制header和echo
为什么本地测试正常,上线后PDF预览失败?
八成是Web服务器(Nginx/Apache)或CDN干的——它们可能重写了响应头、压缩了PDF流、或拦截了 application/pdf 类型。
典型表现:
– 开发环境Chrome能预览,生产环境直接下载
– curl -I 看到 Content-Type: text/html 或 Content-Encoding: gzip(PDF被gzip压缩后无法解析)
- Nginx检查是否有
gzip_types包含application/pdf,有就删掉——PDF自身已是压缩格式,再gzip会损坏 - Apache检查
mod_deflate是否对.pdf启用,禁用它:SetEnvIfNoCase Request_URI \.pdf$ no-gzip - CDN(如Cloudflare)检查是否开启“Polish”或“Auto Minify”,关掉;这些功能会试图“优化”PDF,实则破坏二进制结构
- 用
curl -I http://yoursite.com/gen-pdf.php对比开发/生产环境的响应头,差异点就是根因
PDF流输出看着简单,真正卡住人的永远是响应头细节、服务器配置、以及“以为没问题”的中间层干扰。



















