c.FormFile 不能用于大文件直传,因其底层调用 ParseMultipartForm 将整个请求体(含所有字段和文件)一次性加载到内存,导致 OOM;正确做法是使用 ctx.Request.MultipartReader() 流式处理。

c.FormFile 不能用于大文件直传,它会把整个文件读进内存,几百 MB 就可能触发 runtime: out of memory;真·直传必须绕过 Gin 默认的 multipart 解析,用 ctx.Request.MultipartReader() 流式消费每个 part。
为什么不能直接用 c.FormFile 做大块直传
它底层调用 ParseMultipartForm,强制将整个请求体(含所有字段 + 文件)一次性加载到内存。哪怕只传一个 500MB 文件,Gin 就会尝试分配等量内存 —— 并发稍高,OOM 是必然结果。更糟的是,你连“上传中校验权限”或“边读边转码”都做不到,因为文件内容早已全在内存里了。
- 错误现象:
panic: runtime: out of memory或 Nginx 返回502 Bad Gateway(因上游连接被强制关闭) - 即使设了
r.MaxMultipartMemory = 100 ,也只是限制大小,不改变“全量加载”行为 -
c.SaveUploadedFile同样依赖c.FormFile,本质没区别
如何用 MultipartReader 实现真正流式直传
关键不是“怎么读”,而是“别让 Gin 提前读”。必须禁用自动解析,否则 c.Request.Body 已为空,MultipartReader() 会直接返回 io.ErrUnexpectedEOF。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 不要调用
r.MaxMultipartMemory(或留默认值但不触发解析) - handler 内第一行就调用
c.Request.MultipartReader(),不要碰c.FormFile、c.PostForm等任何会触发ParseMultipartForm的方法 - 用
part.FormName()区分字段和文件:普通字段用part.Read()拿值;文件字段则用io.Copy(dst, part)直接流式写入磁盘或对象存储 - 示例片段:
reader, err := c.Request.MultipartReader() if err != nil { c.AbortWithStatusJSON(400, gin.H{"error": "invalid multipart"}) return } for { part, err := reader.NextPart() if err == io.EOF { break } if err != nil { c.AbortWithStatusJSON(400, gin.H{"error": err.Error()}) return } if part.FormName() == "file" { f, _ := os.Create("/tmp/upload-" + uuid.New().String()) defer f.Close() io.Copy(f, part) // 这里才是真正的“直传” } }
中文文件名、断点续传与安全边界怎么处理
直传不等于裸传。生产环境必须补上三件事:文件名安全解码、分片状态持久化、路径白名单校验。
立即学习“go语言免费学习笔记(深入)”;
- 客户端传的
filename必须用url.PathEscape编码(不是QueryEscape),服务端收到后用url.PathUnescape解码,再做filepath.Clean+ 白名单比对,防../etc/passwd路径遍历 - 断点续传不是靠 Gin 自带能力,得自己存分片元数据:比如用 Redis 记录
file_id → [chunk_0, chunk_2, chunk_5],上传前先查缺失索引 - 流式写入时别用
os.Create直接写用户指定路径 —— 临时文件应固定在/tmp或独立挂载卷,合并后再移到最终目录 - 如果文件需校验(如 MD5),必须在
io.Copy过程中同步计算哈希,而不是等写完再os.Open重读 —— 那又回到内存瓶颈
流式直传的复杂点不在代码行数,而在边界控制:body 是否被提前 consume、multipart boundary 是否被破坏、临时文件清理是否遗漏、并发写同一文件句柄是否冲突 —— 这些问题不会报错,但会在压测时突然暴露。

















