c.MultipartForm()无法监控上传进度,因其会一次性读取整个Request.Body,不暴露中间状态;需用ProgressReader包装c.Request.Body并手动解析multipart。

为什么 c.MultipartForm() 无法监控上传进度
Go 标准库没有上传进度回调机制,c.MultipartForm() 会一次性读取整个 Request.Body 到内存或临时磁盘,等你拿到 *multipart.Form 时,上传早已完成。这意味着:
- 你无法在读取过程中统计已接收字节数
- 大文件上传失败(如超时、断连)时,连“当前传了 30%”都拿不到
-
c.FormFile()返回的是已落盘/内存的*multipart.FileHeader,不反映流式过程
根本原因不是 Echo 的 bug,而是 multipart 解析逻辑本身不暴露中间状态——必须绕过它。
如何用 ProgressReader 手动接管 Request.Body
核心思路:不调用 c.MultipartForm(),而是用自定义 io.Reader 包装原始 c.Request.Body,在每次 Read() 时更新进度,并将该 Reader 传给 multipart.NewReader()。
- 先读
c.Request.Header.Get("Content-Length")获取总大小(客户端必须发,否则 fallback 为 -1) - 构造
ProgressReader,内部嵌入c.Request.Body,并在Read(p []byte)中累加计数、写入sync.Map(key 为 uploadID) - 用这个 reader 初始化
multipart.NewReader(),再循环r.NextPart()解析每个字段或文件 - 禁用
c.ParseMultipartForm(),它会跳过你的包装器,直接触发底层全量读取
示例关键片段(非完整代码,仅示意流程):
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
pr := &ProgressReader{
Reader: c.Request.Body,
total: totalSize,
progress: &sync.Map{},
}
mr := multipart.NewReader(pr, boundary)
for {
part, err := mr.NextPart()
if err == io.EOF { break }
// 处理 part.Header 和 part.Body
}
上传超时与服务端配置联动要点
即使进度可控,上传仍可能被中断——常见于 Nginx、Cloudflare 或 Go HTTP Server 自身的超时限制。必须同步调整三层:
- Go HTTP Server 层:
e.Server.ReadTimeout和WriteTimeout建议设为 300s+(尤其对百 MB 文件) - 反向代理层:Nginx 需配
client_max_body_size、client_body_timeout、proxy_read_timeout - PHP 或其他前置网关(如有):确认
upload_max_filesize、post_max_size不构成瓶颈
注意:ReadTimeout 是从连接建立开始计时,不是从上传开始;若需更精细控制,应改用 http.TimeoutHandler 包裹 handler。
路径遍历与文件落地的安全校验不能省
手动解析 multipart 后,你通常会调用 os.Create() 或 os.OpenFile() 落盘。此时 filename 来自用户输入,极易触发 ../etc/passwd 类攻击:
- 禁止
strings.Contains(filename, "..")和strings.HasPrefix(filename, "/") - 推荐用
filepath.Clean()+ 白名单后缀校验(如只允许.mp4、.pdf) - 落地路径必须用
filepath.Join(uploadDir, safeFilename),且uploadDir应为绝对路径、不可被用户控制
漏掉任意一环,c.File() 或 e.Static() 服务的就不是视频,而是服务器任意文件。


















