gin 默认不支持断点续传,因其文件上传接口(如 formfile)隐式调用 parsemultipartform,强制读取全部请求体并禁用 range 处理;且 os.o_append 与分块写入逻辑冲突,导致偏移控制失效。

Go 标准库本身不提供断点续传的封装函数,Gin 也未内置支持;能否实现,完全取决于你是否绕过框架默认行为、手动控制 HTTP Range 协议交互和文件偏移写入——90% 的失败源于 os.O_APPEND 和 r.ParseMultipartForm 的误用。
为什么 Gin 默认不支持断点续传
Gin 的 c.Request.FormFile 或 c.MultipartForm 会隐式触发 r.ParseMultipartForm(32,把整个请求体读进内存或临时文件,导致 <code>r.Body 流被提前消费。后续用 io.CopyN 读取时返回空,分片写入失败或错位。
- 必须在 handler 开头第一行调用
r.ParseMultipartForm(0),禁用自动解析 - 元信息(如
file_id、chunk_index)要从 URL 查询参数或r.FormValue()获取,别碰r.MultipartForm - 分片二进制数据直接从
r.Body读取,用io.CopyN(dst, r.Body, chunkSize)写入磁盘 - 路径建议按
./uploads/{file_id}/chunk_{index:04d}组织,避免命名冲突
服务端怎么正确响应 Range 请求(下载场景)
若走自定义 handler(非 http.FileServer),就不能依赖 http.ServeContent 自动处理——它要求你传入固定 size 和准确 modtime,且 Gin 的 c.Writer 不完全兼容底层 http.ResponseWriter。
- 手动解析:
rangeStr := r.Header.Get("Range"),再用http.ParseRange(rangeStr)得到[]http.HTTPRange - 只支持单段:检查
len(ranges) == 1,否则返回 416 - 显式设置响应头:
w.Header().Set("Accept-Ranges", "bytes")、w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, total)) - 状态码必须是
http.StatusPartialContent (206),不能是 200 - 读取时先
file.Seek(start, io.SeekStart),再用io.CopyN(w, file, int64(end-start+1)),避免超出右边界
客户端怎么安全续传(上传/下载都适用)
客户端“以为能续”不等于服务端真支持。只看 Accept-Ranges: bytes 是伪信号——Nginx、CDN 常伪造该 header,但实际对 Range 请求返回 200 或 500。
- 首次操作前,发试探请求:
curl -I -H "Range: bytes=0-1023" http://example.com/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** ——它会让Write()忽略Seek(),导致数据写到末尾而非指定 offset - 写入前务必校验
f.Seek(offset, io.SeekStart)返回值是否等于offset,不等说明文件被截断或并发修改
为什么 io.CopyN 比 io.Copy 更可靠
断点续传本质是“精确读取 N 字节”,不是“读完剩余全部”。用 file.Seek(offset, io.SeekStart) 后跟 io.Copy(w, file) 会把从 offset 到 EOF 的所有内容都发出去,违反 Range: bytes=1024-2047 的语义。
-
io.CopyN(dst, src, n)保证最多复制 n 字节,即使 src 提前 EOF 也正常返回(io.ErrUnexpectedEOF不是错误) - 响应体字节数必须与
Content-Range中声明的长度一致,或更少(HTTP 允许),但Content-Length必须匹配实际写出字节数 - 不要用
bufio.Reader包裹文件:内部 buffer 会破坏Seek的物理位置映射,导致跳转错位 - 大文件下默认 32KB 缓冲够用;如需更高吞吐,可传入自定义
make([]byte, 64*1024)作为第三个参数
最易被忽略的点:服务端禁用 ParseMultipartForm 和客户端弃用 os.O_APPEND 是两条硬性前提,其余逻辑哪怕写对了,踩中任一坑都会静默损坏文件——空白填充、数据错位、校验失败,且难以复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











