c.data无法分块是因为它自动计算并写入content-length,强制禁用transfer-encoding: chunked;实现pdf分块需手动操作responsewriter、删除content-length、设置transfer-encoding并逐块write+flush。

为什么直接用 c.Data 发 PDF 无法分块?
因为 c.Data 内部调用的是 http.ResponseWriter.Write,但默认不触发 Transfer-Encoding: chunked。Go 标准库只有在响应头未设 Content-Length 且未显式关闭分块时,才可能启用 chunked —— 但 Gin 的 c.Data、c.File 等方法会自动计算并写入 Content-Length,直接堵死分块路径。
必须手动控制 Writer 并禁用 Content-Length
要实现 PDF 分块响应,核心是绕过 Gin 封装,直接操作底层 http.ResponseWriter,并确保:
– 不设置 Content-Length
– 显式写入 Transfer-Encoding: chunked
– 每次写入后调用 w.(http.Flusher).Flush()
- 先调用
w.Header().Set("Content-Type", "application/pdf") - 再调用
w.Header().Del("Content-Length")(必须删,否则 net/http 强制禁用 chunked) - 然后
w.Header().Set("Transfer-Encoding", "chunked") - 最后用
w.Write()+w.(http.Flusher).Flush()分段输出 PDF 数据块
PDF 文件不能整读进内存再分块
大 PDF 文件(比如 >50MB)如果用 ioutil.ReadFile 或 os.ReadFile 一次性加载,会触发高内存占用甚至 OOM。正确做法是:
– 用 os.Open 打开文件
– 用固定大小 buffer(如 make([]byte, 64*1024))循环 io.ReadFull 或 file.Read
– 每次读到数据就 w.Write + Flush
注意:io.Copy 会内部缓冲并等待 EOF,不满足“边读边发”;io.CopyN 适合断点续传,但这里不需要 Range,直接流式读更简单。
浏览器接收分块 PDF 的兼容性陷阱
不是所有客户端都支持分块传输的 PDF 渲染:
– Chrome 和 Edge 通常能边下载边预览(只要 Content-Type 正确)
– Safari 对 chunked PDF 支持不稳定,可能卡住或报“Failed to load PDF document”
– 移动端 WebView 表现差异更大,部分直接拒绝渲染
真正可靠的 fallback 是提供完整 URL 下载链接,并在响应头中加 Content-Disposition: attachment; filename="xxx.pdf"。分块只作为“可选流式预览”能力,不要当作唯一交付方式。











