ctx.FormFile不能直接处理大视频文件,因其会将整个文件载入内存,500MB MP4易触发OOM或被Nginx拦截为413;应改用ctx.Request.MultipartReader流式处理,并合理设置MaxMultipartMemory与边界校验。

为什么 ctx.FormFile 不能直接处理大视频文件
因为 ctx.FormFile 默认会把整个文件读进内存,上传一个 500MB 的 MP4 就可能触发 OOM 或被中间件(如 Nginx)直接 413 Request Entity Too Large。Gin 本身不控制请求体大小,它依赖底层 HTTP server 和反向代理的配置。
真正可控的入口是 ctx.Request.MultipartReader(),配合流式写入磁盘或对象存储。但多数场景下,你其实只需要调大限制 + 正确解析 multipart 表单字段 —— 关键不是“不用 FormFile”,而是“用对参数和边界”。
- 必须在
gin.Default()前设置gin.Mode = gin.ReleaseMode(避免调试日志干扰 multipart 解析) - 调用
router.MaxMultipartMemory = 32 (32MB 内存缓冲,超过部分自动落盘) - 确保前端
<input type="file">的name属性值与后端ctx.FormFile("video")中的字符串完全一致
如何安全保存上传的视频到本地目录
别直接用 file.SaveTo(...),它不校验文件扩展名、MIME 类型,也无路径遍历防护。实际生产中应手动控制文件名生成和写入流程。
示例做法:
file, header, err := ctx.Request.FormFile("video")
if err != nil {
ctx.JSON(400, gin.H{"error": "missing video field"})
return
}
// 校验 MIME
if header.Header.Get("Content-Type") != "video/mp4" &&
header.Header.Get("Content-Type") != "video/webm" {
ctx.JSON(400, gin.H{"error": "only mp4/webm allowed"})
return
}
// 生成安全文件名:不信任原文件名
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
dst := filepath.Join("./uploads", filename)
out, err := os.Create(dst)
if err != nil {
ctx.JSON(500, gin.H{"error": "create failed"})
return
}
defer out.Close()
if _, err := io.Copy(out, file); err != nil {
ctx.JSON(500, gin.H{"error": "write failed"})
return
}
- 注意
io.Copy是流式写入,不会把整个视频加载进内存 -
os.Create前建议先os.MkdirAll("./uploads", 0755) - 若需限制最大上传体积,应在
io.Copy中用io.LimitReader(file, maxBytes)
Nginx 或 Cloudflare 下上传失败的常见原因
Gin 跑在本地没问题,一上生产就 413 或连接重置,90% 是反向代理层拦截了。Gin 日志里看不到错误,因为请求根本没到达 Gin。
- Nginx 需显式配置:
client_max_body_size 1g;(放在http、server或location块中) - Cloudflare 免费版硬性限制 100MB,且不支持分块上传(
multipart/form-data中大文件仍算单请求体) - Gin 启动时监听地址别写
127.0.0.1:8080,Nginx proxy_pass 必须能访问到,推荐:8080或0.0.0.0:8080 - 如果用了
gin.BasicAuth中间件,注意浏览器上传大文件时可能因超时导致认证头丢失,建议改用 token 或前置鉴权
前端 fetch 上传时容易忽略的细节
用 FormData 提交视频,看似简单,但默认行为会让 Gin 收不到文件:如果没显式设置 Content-Type 为 multipart/form-data,浏览器会自动设一个带 boundary 的值,而 fetch 不允许手动覆盖这个 header —— 所以必须让浏览器自动生成。
- ✅ 正确:
fetch("/upload", { method: "POST", body: formData })(不设 headers) - ❌ 错误:
headers: { "Content-Type": "multipart/form-data" }(会禁用 boundary,Gin 解析失败) - 上传进度需要监听
XMLHttpRequest.upload.onprogress,fetch原生不支持,得用ReadableStream+TransformStream手动拆包(复杂度高,一般换回 xhr) - 若后端返回 JSON,前端记得加
.then(r => r.json()),否则响应体是ReadableStream对象,不是 JS 对象
边界情况比想象中多:比如 iOS Safari 对大于 100MB 的视频会静默截断,不报错也不触发 onerror;这时候只能靠服务端校验文件长度是否匹配 Content-Length 头。


















