gin 的 c.file 和 c.data 无法支持断点续传,因为 c.file 调用 http.servefile 不解析 range 头、始终返回 200,c.data 则强制设状态码为 200 并清空 header,覆盖 accept-ranges 和 content-range 等关键头。

直接用 http.ServeContent 在 Gin 里行不通——Gin 的 c.Data、c.File 会覆盖状态码和 header,无法触发 206 Partial Content 或正确返回 Content-Range。必须绕过 Gin 封装,直接操作底层 http.ResponseWriter。
为什么不能用 Gin 的 c.File 或 c.Data 处理断点续传
Gin 的 c.File 内部调用的是 http.ServeFile,它不解析 Range 请求头;c.Data 则强制设置状态码为 200 并清空已有 header,导致 Accept-Ranges: bytes 和 Content-Range 无法生效。即使你手动设置了这些 header,Gin 也会在写响应体前覆盖掉。
-
c.File返回的是 200 OK,永远不返回 206 -
c.Data会重置w.Header(),Accept-Ranges白设 - 所有
c.*方法都隐式调用w.WriteHeader(200),一旦调用就不可逆
正确做法:用原生 http.ResponseWriter + http.ServeContent
在 Gin handler 中,把 c.Writer 类型断言为 http.ResponseWriter,再传给 http.ServeContent。但必须满足三个硬性条件,否则仍会返回 416 或 412:
- 文件
modtime必须稳定:别直接用os.Stat().ModTime(),应缓存或从文件元数据中读取固定值,避免因文件被其他进程修改导致412 Precondition Failed -
size必须等于文件真实字节长度:用f.Stat().Size()前确保f已关闭或未被写入;若文件可能被并发修改,需加锁或用os.Open后立刻Stat - 必须显式设置:
w.Header().Set("Accept-Ranges", "bytes"),且不能晚于http.ServeContent调用
示例关键片段:
w := c.Writer
f, _ := os.Open(filepath)
defer f.Close()
fi, _ := f.Stat()
w.Header().Set("Accept-Ranges", "bytes")
http.ServeContent(w, c.Request, filename, fi.ModTime(), f)
自定义 Range 解析时,io.CopyN 是唯一安全选择
如果不想依赖 http.ServeContent(比如要加权限校验、审计日志或加密传输),就得自己解析 Range 头并流式读取。此时最容易踩的坑是:file.Seek() 后直接 io.Copy() —— 它会把剩余全部内容发出去,不尊重右边界。
- 必须用
http.ParseRange(r.Header.Get("Range")),它返回[]http.HTTPRange,只支持单段(len(ranges) == 1) - 计算
length := ranges[0].Length(),然后file.Seek(ranges[0].Start, io.SeekStart) - 用
io.CopyN(w, file, length),不是io.Copy;io.CopyN返回io.ErrUnexpectedEOF是正常现象(文件尾提前到达),按实际字节数设Content-Length即可 - 绝对不要用
bufio.Reader包裹文件:内部 buffer 会破坏Seek的准确性
客户端续传前必须做 Range 支持探测,别信 Accept-Ranges
Nginx、CDN 经常伪造 Accept-Ranges: bytes 响应头,但实际收到 Range: bytes=0-1023 时却返回 200 或 500。客户端必须主动探测:
- 发一个
HEAD /file.zip或带Range: bytes=0-1023的GET请求 - 仅当响应码是
206 Partial Content且Content-Range字段匹配(如bytes 0-1023/12345678)才启用续传 - 若返回
200,说明服务端不支持,应清空本地文件重下;若返回416,说明服务端知道长度但 range 超限,需先用HEAD获取真实Content-Length - 每次请求的
Range头格式必须严格为bytes={offset}-,结尾不能多横杠、不能有空格
服务端逻辑越“薄”,客户端越容易对齐;任何中间件(包括 Gin 自身)插入的 header 修改或状态码覆盖,都会让这个探测失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











