c.File()不能用于超大文件流解析,因其将整个文件读入内存导致OOM;应改用io.Copy+os.Open或c.DataFromReader实现恒定内存的流式传输,并手动处理Header、Range、路径校验等细节。

为什么c.File()不能用于超大文件流解析
c.File() 会把整个文件读进内存再发给客户端,1GB 文件就占 1GB 堆内存。并发 10 路就是 10GB —— 不是慢,是直接触发 runtime: out of memory 或被 Nginx/ALB 主动断连。它底层调用 http.ServeFile,不支持流控、不支持断点续传、无法提前校验权限,错误也常静默(比如 permission denied 返回 500 却没日志)。
真正流式解析必须用 io.Copy + os.Open
核心是让文件句柄直通响应体,边读边发,内存占用恒定在几 KB。但必须手动控制细节,否则极易出错:
- 先用
os.Stat()检查文件是否存在、是否可读,避免open /xxx: permission denied - 必须在
io.Copy()前调用c.Header()设置全部关键头:Content-Type、Content-Length、Content-Disposition -
Content-Length必须是os.Stat().Size(),不能估算或用 len(buf),否则 iOS Safari 拒载、断点续传失效 - 务必
defer f.Close(),否则快速触发too many open files - 中文文件名要用
url.PathEscape()编码,不是url.QueryEscape();拼接前需白名单校验路径,防 ../ 遍历
需要断点续传?得自己解析 Range 头
Gin 不自动处理 Range: bytes=1024- 这类请求头。c.File() 和裸 io.Copy() 都不支持。必须手动读取 c.Request.Header.Get("Range"),解析起始偏移、计算长度、设置 206 Partial Content 状态码,并用 io.CopyN() 或 io.Seeker 跳到对应位置读取。
注意:os.File 支持 Seek(),但 gzip.NewReader(f) 这类包装器不支持 —— 如果你打算边解压边传输,size 错误会导致截断且无提示。
c.DataFromReader 是生产环境默认做法
它比裸 io.Copy() 更可控,尤其适合从非磁盘来源(如数据库 BLOB、生成器)流式返回内容。但它强制要求传入准确的 size int64 参数,且不支持 Transfer-Encoding: chunked。如果总大小未知(比如实时日志 tail),只能退回到 io.Copy() + 手动写 header 的方式,此时要主动设 c.Writer.WriteHeader(200) 并禁用 Content-Length。
最易被忽略的一点:所有 c.Header() 必须在第一次 c.Writer.Write() 或 c.DataFromReader() 之前完成。之后再调用就完全无效,也不会报错 —— 浏览器收不到 Content-Disposition,就只显示空白页。


















