Gin框架不自动处理Range请求,必须手动解析Range头、设置206状态码及Content-Range/Accept-Ranges响应头,并用io.Seek+io.CopyN精确输出字节范围,否则断点续传失败。

断点下载依赖的 HTTP 头必须手动解析 Range
Gin 默认不自动处理 Range 请求头,也不会设置 Content-Range 或 Accept-Ranges 响应头。这意味着即使客户端发起带 Range: bytes=100-200 的请求,Gin 仍会返回完整文件和 200 OK,导致断点续传失败。
关键动作是:显式读取 c.Request.Header.Get("Range"),解析出字节范围,再用 io.Seek 跳转到对应位置读取。不能依赖 c.File() 或 c.Data() 自动适配 —— 它们不感知 Range。
- 必须检查
Range是否存在且格式合法(如bytes=100-、bytes=-500、bytes=100-199) - 若无
Range头,按普通下载处理,返回200 OK和完整文件 - 若格式错误或范围越界,应返回
416 Range Not Satisfiable并带上Content-Range: */<code>file_size
响应状态码和头部必须严格匹配 RFC 7233
断点下载不是“多发几个头就行”,状态码和头部组合有硬性约束。用错一个,客户端(尤其是 curl、wget、浏览器下载器)就会退化为全量重下。
典型正确流程:
立即学习“go语言免费学习笔记(深入)”;
- 收到
Range: bytes=100-199→ 返回206 Partial Content - 必须设置
Content-Range: bytes 100-199/<code>file_size - 必须设置
Accept-Ranges: bytes(即使只支持单段,也得声明) - 不能遗漏
Content-Length(值为本次返回的字节数,非整个文件) - 避免设置
Content-Encoding等干扰流式传输的头
常见坑:c.Status(206) 后忘记设 Content-Range,或把 Content-Length 写成整个文件大小 —— 这会让客户端认为数据损坏而中断。
文件读取需绕过 Gin 的缓冲机制,直接操作 http.ResponseWriter
Gin 的 c.Data() 和 c.File() 会内部调用 http.ServeContent 或封装写入,无法控制 seek 和分段读。要实现断点,必须拿到原始 http.ResponseWriter,用 io.CopyN 或手动 write 控制输出流。
- 先调用
c.Writer.WriteHeader(statusCode)显式写状态码 - 再逐个调用
c.Writer.Header().Set()设置必要头 - 用
file.Seek(offset, io.SeekStart)定位,然后io.CopyN(c.Writer, file, length)精确输出指定字节数 - 注意关闭文件句柄(建议 defer
file.Close()),否则高并发下易触发 “too many open files”
示例片段:
if rangeStr != "" {
start, end, size, err := parseRange(rangeStr, fileInfo.Size())
if err != nil {
c.AbortWithStatusJSON(416)
return
}
c.Writer.WriteHeader(206)
c.Writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, size))
c.Writer.Header().Set("Accept-Ranges", "bytes")
c.Writer.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
file.Seek(start, io.SeekStart)
io.CopyN(c.Writer, file, int64(end-start+1))
return
}
大文件场景下需考虑内存与并发限制
直接 io.CopyN 不会加载整个文件进内存,但若未加限流或超时控制,一个 10GB 文件的慢速连接可能长期占用 goroutine 和文件句柄。
- 对
*os.File设置ReadDeadline(需包装为net.Conn或用http.TimeoutHandler) - 避免在 handler 中做阻塞磁盘 I/O;SSD/NVMe 下问题不大,但 HDD 或 NFS 挂载点需警惕延迟毛刺
- 若支持多段
Range(如bytes=0-99,200-299),RFC 要求返回multipart/byteranges,实际极少客户端用,多数服务只实现单段即可 - 静态文件路径务必校验,防止
../路径遍历 —— 断点下载逻辑常忽略这步,比普通File更危险
最易被忽略的是:没有对文件路径做 filepath.Clean() + 白名单校验。一旦攻击者构造 Range 请求配合恶意路径,可能泄露任意系统文件。


















