c.file()不能用于大文件下载,因其调用http.servefile将整个文件读入内存再返回200响应,不解析range头、不支持断点续传,易导致oom崩溃;必须手动解析range、设206状态及content-range头,用file.seek()+io.copyn()流式分段响应。

直接用 c.File() 下载大文件会 OOM,断点续传必须手动处理 Range 头并返回 206 Partial Content;Gin 默认不支持,得自己写 handler,不能依赖 http.ServeContent 或 StaticFS。
为什么 c.File() 不能用于大文件下载
它会把整个文件读进内存再发出去,100MB 文件就占 100MB 内存,100 并发就是 10GB —— 不是慢,是直接崩溃。底层调用 http.ServeFile,没有流控、不响应 Range 请求、无法设自定义 MIME 类型,且权限校验滞后、错误静默(比如路径错只返回 404,没日志)。
- 常见错误现象:
runtime: out of memory、浏览器进度条卡死、iOS Safari 拒绝加载 - 即使加了
Content-Length,c.File()也不会按Range切片返回,客户端发Range: bytes=0-1023仍得 200 + 全量内容 - 它不检查
Accept-Ranges,也不设置Content-Range,客户端根本无法判断是否真支持续传
怎么手动实现支持 Range 的下载 handler
核心是:解析 Range 头 → 计算起始/结束偏移 → 设置响应头 → 用 file.Seek() + io.CopyN() 精确读取并返回。
- 先调
r.Header.Get("Range"),再用http.ParseRange()解析,只接受单段(len(ranges) == 1),否则返回416 Range Not Satisfiable - 显式设置:
w.Header().Set("Accept-Ranges", "bytes")、w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, total)) - 状态码必须是
http.StatusPartialContent(206),不是 200 - 打开文件用
os.Open(),然后file.Seek(start, io.SeekStart),再io.CopyN(w, file, int64(end-start+1)),避免越界 - 别用
http.ServeContent—— 它要求传入固定modtime和size,且 Gin 的c.Writer不完全兼容底层http.ResponseWriter
客户端怎么安全判断能否续传
别信 Accept-Ranges: bytes 这个 header —— Nginx、CDN 常伪造它,但实际对 Range 请求返回 200 或 500。
- 首次下载前,发试探请求:
curl -I -H "Range: bytes=0-1023" http://your-api/file.zip - 仅当响应含
206 Partial Content且Content-Range: bytes 0-1023/12345678才启用续传逻辑 - 本地已下载长度必须来自
os.Stat(path).Size(),不能靠缓存或猜测 - 打开文件写入时用
os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644),**绝不用os.O_APPEND** —— 它和Seek冲突,会导致偏移错位
c.DataFromReader 是下载大文件的默认做法,不是“备选”
它才是真正生产可用的流式下载方式,比手写 Range handler 更轻量,但前提是 size 必须准确、header 必须提前设好。
-
size参数必须来自os.Stat().Size(),不能估算或用len(buf),否则浏览器进度条卡死、断点续传失效 - 中文文件名必须用
url.PathEscape()编码到Content-Disposition的filename=字段,url.QueryEscape()会把空格转成+,Chrome 直接解析失败 - 如果文件来自生成器(如动态 CSV),又不知道总大小,就不能用
c.DataFromReader,得切回手动Rangehandler + 分块传输 - 它不支持
Transfer-Encoding: chunked,所以底层io.Reader必须能Seek(比如os.File),否则 size 错误会导致截断且无提示
最易被忽略的一点:服务端返回的 Content-Range 必须严格匹配客户端请求的字节范围,且 end 不能超过文件总长 —— 否则 iOS Safari 会直接中断连接,连错误提示都没有。











