c.file()强制下载pdf因自动设置content-disposition: attachment;预览须用c.data()手动输出流并仅设content-type: application/pdf,禁用attachment头。

直接用 c.File() 或 c.FileAttachment() 无法让浏览器内联预览 PDF —— 它们默认设了 Content-Disposition: attachment,强制下载;想预览,必须绕过文件路径直输流,并手动控制响应头。
为什么 c.File() 会导致 PDF 下载而非预览
这是 Gin 的设计使然:c.File() 内部调用 http.ServeFile(),自动设置 Content-Disposition: attachment; filename=xxx;c.FileAttachment() 更进一步强化该行为。哪怕你传的是 PDF 路径,浏览器也只认头,不看后缀。
- 现象:点击链接弹下载框、地址栏显示
%PDF-1.7乱码、iframe 白屏 - 根本原因:浏览器收到
attachment头,直接放弃内联渲染逻辑 - 误区:以为改
Content-Type就够 —— 不,Content-Disposition的优先级更高
正确做法:用 c.Data() 手动输出 Blob 流
核心是跳过 Gin 的文件封装逻辑,自己构造响应体 + 响应头。后端不写磁盘、不走静态服务,直接把 PDF 二进制数据吐给浏览器。
- 必须设置
Content-Type: application/pdf(不能是octet-stream) - 绝对不要设
Content-Disposition,或显式设为inline(但现代浏览器其实只认Content-Type) - 推荐用
c.Data(),传入原始字节切片:c.Data(http.StatusOK, "application/pdf", pdfBytes) - 若 PDF 来自磁盘大文件,用
ioutil.ReadFile()风险高;应改用os.Open()+io.Copy(c.Writer, file),避免内存爆掉
路径安全与 MIME 类型校验不能省
用户传来的 PDF 路径若未经清洗,可能触发路径遍历(如 ../../../etc/passwd),且错误的 MIME 类型会让预览彻底失效。
- 路径校验必须白名单:只允许访问
/var/www/pdfs/或./storage/pdfs/下的文件,拒绝任何..和绝对路径 - 读取前先
os.Stat()确认是常规文件,且后缀为.pdf(仅作辅助,不可替代 MIME 检查) - 输出前用
mime.TypeByExtension(".pdf")获取标准类型,比硬编码更可靠 - 若 PDF 来自第三方存储(如 OSS),务必用
bytes.NewReader(data)构造流,别拼接本地路径
前端 iframe src 必须指向该 API,不能指向静态路径
很多人把 PDF 放进 Static() 目录,然后 <iframe src="/pdfs/xxx.pdf"></iframe> —— 这看似简单,但实际依赖 Nginx 配置是否正确返回 application/pdf。Gin 自带的 Static() 不控制 MIME,容易踩坑。
- 正确链路:前端请求
/api/pdf/123→ Gin 后端查 DB 获取 PDF 二进制 →c.Data()输出 - iframe 的
src必须是这个 API 地址,不是文件系统路径 - 加
#toolbar=0&view=fitH可隐藏工具栏、适配高度,但非必需;Safari 对此支持差,别强依赖 - 移动端 fallback:iOS Safari 常直接跳转全屏阅读器,无法拦截,只能接受
最易被忽略的是响应头顺序和空字符:Gin 中若在 c.Data() 前有 fmt.Println() 或日志中间件输出了换行,就会污染响应头,导致 PDF 解析失败。所有调试输出必须关掉,或确保发生在 c.Data() 之后。











