gin框架不支持大文件切片上传,因其默认解析整个multipart/form-data请求体,调用c.formfile或访问表单字段会触发parsemultipartform导致r.body被读空;必须首行执行c.request.parsemultipartform(0)禁用解析,再流式读取原始body,并通过query或header获取元信息。

Gin 框架本身不支持大文件切片上传——它默认按 multipart/form-data 解析整个请求体,一旦调用 c.FormFile 或隐式触发 ParseMultipartForm,r.Body 就被读空,后续分片数据全丢。必须绕过框架自动解析,手动接管 raw body 流式读取。
为什么 c.FormFile 会导致分片上传失败
这不是 Gin 的 bug,而是 multipart 解析的必然行为:c.FormFile 内部会调用 r.ParseMultipartForm,该方法会一次性把整个 r.Body 读进内存或临时磁盘(取决于 MaxMultipartMemory 设置),之后再调用 io.Copy 或 io.ReadAt 都只能得到 EOF。
- 现象:客户端发了 10 个分片,服务端只收到第 1 个,其余全为空或报
http: read on closed response body - 即使你没显式调用
c.FormFile,某些中间件(如gin-contrib/multiform)也可能偷偷触发解析 -
r.MultipartForm是惰性解析,但只要任何地方访问了c.Request.PostForm、c.Request.FormValue或c.Request.MultipartForm(),就等同于触发解析
如何禁用 multipart 自动解析并安全读取分片
核心动作只有两步:在 handler 开头立刻禁用解析,并用流式方式精确读取原始字节流。
- 必须第一行执行:
c.Request.ParseMultipartForm(0)—— 传0表示跳过解析,保留r.Body可读 - 务必在
http.MaxBytesReader包裹前做这一步,否则限流器可能提前消费 body - 分片请求的
Content-Type应为application/octet-stream,不是multipart/form-data;若客户端误设,Gin 仍可能尝试解析 - 用
io.CopyN(dst, r.Body, expectedSize)或io.LimitReader(r.Body, expectedSize)读取指定字节数,避免读超或阻塞 - 写入临时文件时建议加
os.O_EXCL标志,防止并发上传同一分片导致覆盖:os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
元信息从哪来:别碰 c.Request.Form,改用 Query 或 Header
既然不能走表单解析,所有上传上下文(如 upload_id、chunk_index、total_chunks)必须通过 URL 查询参数或 HTTP Header 传递。
- 推荐方式:
r.URL.Query().Get("upload_id")、r.URL.Query().Get("chunk_index") - 也可用 Header:
r.Header.Get("X-Upload-ID")、r.Header.Get("X-Chunk-Index"),更干净且不易被缓存干扰 - 绝对不要用
c.PostForm("upload_id")或c.DefaultPostForm("chunk_index", "0"),它们会触发ParseMultipartForm - 文件名和大小这类信息,应在首请求(如初始化上传)中由客户端通过独立接口提交,服务端存状态后供后续分片查询
分片状态怎么存:框架不提供,你得自己选存储策略
Gin 不管上传上下文,upload_id 对应的分片完成列表、总片数、文件名、MD5 等,全靠你自己存。选型关键不在“多酷”,而在“查得快、判得准、删得稳”。
- 单机轻量场景:用
sync.Map存内存,但需注意 GC 压力和重启丢失;适合开发或低并发 - 生产多实例:必须用 Redis,用
HSET upload:<code>upload_idchunk_0 1 chunk_1 1 ... 或SET存已传分片索引,配合EXPIRE自动清理 - 极端简单场景:直接写本地 JSON 文件,路径为
/tmp/uploads/<code>upload_id.json,但要注意并发写和权限问题 - 无论哪种,合并前必须校验:是否所有分片都已存在、
total_chunks是否匹配、每个分片大小是否符合预期(最后一片可略小)
真正容易被忽略的不是代码怎么写,而是「谁在什么时候触发了 ParseMultipartForm」——可能是你加的一行日志中间件、一个未注释掉的调试语句、甚至第三方库的某次反射调用。上线前务必用 curl 发 raw body 请求实测,别信文档里“没调 FormFile 就安全”的假设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











