不能用c.FormFile接收大文件,因为它会将整个文件读入内存再返回*multipart.FileHeader,导致OOM、超时502或静默失败;应改用c.Request.MultipartReader()实现真流式接收,边读边处理,避免内存溢出。

为什么不能用 c.FormFile 接收大文件
它会把整个文件读进内存再返回 *multipart.FileHeader,几百 MB 的文件直接触发 OOM;而且你无法在读取中途做校验、压缩或转存——所有数据已加载完毕,控制权丢失。
常见错误现象:runtime: out of memory、请求超时后 Nginx 返回 502、c.FormFile 返回 nil 但没报错,日志里也看不到原因。
- 别在上传接口里调用
r.MaxMultipartMemory()来“加大内存限制”,这只是延缓爆炸,不是解决流式问题 - 禁用 Gin 默认的 multipart 解析:不调用
c.Request.FormFile(),也不依赖c.PostForm解析文件字段 - 确保前端 curl 或 JS
FormData没手动设置Content-Type: application/json,否则 boundary 丢失,MultipartReader会直接报invalid multipart
用 c.Request.MultipartReader() 实现真流式接收
这是 Gin 中唯一能边读边处理大文件的方式。它返回标准 multipart.Reader,你可以按 part 迭代,拿到每个字段的 io.Reader,直接 io.Copy 到磁盘、对象存储或解压器,全程零内存缓冲。
实操要点:
- 必须在 handler 开头就调用
c.Request.MultipartReader(),否则c.Request.Body可能已被 Gin 内部提前读空 - 每个
part要检查part.FormName(),只对目标字段(如"file")做后续处理 - 对文件 part,建议用
io.CopyN或带 buffer 的io.Copy控制单次写入量,避免阻塞太长 - 务必用
defer f.Close()关闭目标文件句柄,否则很快出现too many open files
示例关键片段:
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" {
dst, _ := os.Create("/tmp/uploaded-" + uuid.New().String())
defer dst.Close()
io.Copy(dst, part) // 真正流式:边读 part 边写磁盘
}
}
文件名和路径安全必须手动校验
Gin 不会帮你清理 part.FileName(),攻击者可传 ../../../etc/passwd 触发路径遍历。也不能直接拼接用户输入生成保存路径。
正确做法:
- 用
filepath.Base(part.FileName())提取原始文件名,丢弃所有路径成分 - 用
securejoin.SecureJoin("uploads/", cleanName)或手写白名单校验(如只允许[a-zA-Z0-9._-]+) - 强制添加唯一前缀或哈希(如
sha256(part.Header.Get("Content-Transfer-Encoding"))[:8]),避免同名覆盖 - 保存前检查目标目录是否存在,用
os.MkdirAll(dir, 0755)创建,不要假设 uploads/ 目录一定存在
上传进度和中断恢复几乎不可靠
HTTP/1.1 本身不支持上传断点续传。所谓“分片上传”是业务层协议,需前端配合生成唯一 uploadId、分片序号、MD5 校验,并由服务端维护状态(如 Redis 存分片列表)。Gin 原生不提供任何支持。
容易被忽略的关键点:
- 别指望
Content-Range头在上传时自动生效——那是下载用的,上传时浏览器根本不发这个头 - 如果用 Nginx 做反向代理,要显式配置
client_max_body_size和proxy_buffering off,否则 Nginx 会先缓存整个 body 再转发,失去流式意义 - 超时设置要分层:Gin 的
ReadTimeout、Nginx 的client_header_timeout和client_body_timeout都得调大,否则大文件还没传完连接就被关了


















