大文件下载应避免c.file,因其将整个文件读入内存;正确做法是用io.copy配合os.open实现流式传输,并手动设置header、处理断点续传及资源释放。

大文件下载别用 c.File,它根本不是流式
直接调用 c.File("big.zip") 看似方便,但 Gin 会用 os.ReadFile 把整个文件读进内存再发出去。100MB 文件就占 100MB 堆内存,多个并发请求一上来,GC 频繁或直接 OOM。这不是流式,是“假流”。
实操建议:
- 只对 ≤ 1MB 的小资源(如图标、配置文件)用
c.File - 路径必须是绝对路径,或相对于可执行文件的正确相对路径,否则报
open xxx: no such file or directory - 它自动设
Content-Type和Content-Disposition,但不支持断点续传,也不控制超时
真正流式下载:用 io.Copy + os.Open 手动控制
核心是把文件句柄直接交给 io.Copy,边读边发,内存恒定在几 KB。Gin 的 c.Writer 实现了 io.Writer 接口,完全兼容。
实操建议:
- 先用
os.Stat检查文件是否存在、是否可读,避免运行时报permission denied - 手动设置
c.Header("Content-Length", ...)和c.Header("Content-Disposition", ...),否则浏览器可能不触发下载 - 务必
defer f.Close(),否则文件句柄泄漏,很快出现too many open files -
Content-Type别硬写application/octet-stream,应根据后缀用mime.TypeByExtension推断
示例片段:
f, err := os.Open(filepath.Clean("/data/report.pdf"))
if err != nil {
c.AbortWithStatus(404)
return
}
defer f.Close()
size := f.Size()
c.Header("Content-Type", mime.TypeByExtension(".pdf"))
c.Header("Content-Length", strconv.FormatInt(size, 10))
c.Header("Content-Disposition", "attachment; filename=report.pdf")
io.Copy(c.Writer, f)
需要断点续传?必须自己解析 Range 头
浏览器暂停后继续下载时,会带 Range: bytes=1024- 或 Range: bytes=1024-2047。Gin 默认不处理,c.File 和裸 io.Copy 都不支持。
实操建议:
- 用
c.Request.Header.Get("Range")获取原始头值 - 手动解析字节范围,校验合法性(如起始不能超文件大小)
- 用
f.Seek(offset, 0)定位,再用io.CopyN或带限长的io.Copy发送指定段 - 返回状态码必须是
206 Partial Content,并设置Content-Range头 - 注意:如果客户端发的是多段 Range(如
bytes=0-100,200-300),标准 HTTP 不鼓励,可直接忽略或返回416 Range Not Satisfiable
流式写入本地文件:优先用 io.Copy,不是 bufio.Writer
从 HTTP 响应体(res.Body)、管道或其它 io.Reader 写入磁盘时,io.Copy 是首选。它内部用 32KB 缓冲区循环读写,零额外内存分配,性能和可读性都最优。
实操建议:
- 用
os.Create创建文件,记得defer outFile.Close() - 必须显式调用
res.Body.Close()(或确保被 defer),否则连接无法复用,引发资源泄漏 - 若路径含多级目录(如
"logs/2026/08/app.log"),先调os.MkdirAll(filepath.Dir(path), 0755) - 高可靠性场景(如金融日志),
io.Copy后加outFile.Sync()强制落盘,但会增加延迟 - 不要自己写 for 循环 +
make([]byte, 1 —— 这容易误用缓冲区,且不如 <code>io.Copy经过生产验证
流式处理的关键不在“分块”动作本身,而在是否真正控制住了内存增长和资源生命周期。很多问题不是逻辑写错,而是 Close 忘了 defer、Seek 没检查错误、或把 *os.File 当成唯一选择而忽略了 io.ReadSeeker 接口的通用性。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











