Echo框架不支持上传进度监控,因c.MultipartForm()和c.FormFile()会一次性读取完整请求体,无法插入统计逻辑;必须手动用ProgressReader包装Request.Body并调用multipart.NewReader()逐part解析,同时通过轮询或WebSocket推送进度。

Echo 框架本身不支持上传进度实时监控,必须手动接管 Request.Body 并配合客户端轮询或 WebSocket 推送——没有中间路径,也不存在“启用某个选项就自动有进度”的配置。
为什么 c.MultipartForm() 和 c.FormFile() 拿不到进度
Echo 在调用 c.MultipartForm() 或 c.FormFile() 时,会直接调用底层 http.Request.Body.Read() 一次性读完整个请求体(包括所有文件数据),中间不暴露读取过程。这意味着:
- 你无法在解析 multipart 的过程中插入统计逻辑
- 等你拿到
*multipart.FileHeader时,文件早已完整写入内存或临时磁盘,上传早已结束 - 若用户上传大文件且未发
Content-Length,c.MultipartForm()可能直接 panic 或超时,连错误都来不及捕获
必须用 ProgressReader 包装 Request.Body 并手动解析 multipart
核心动作不是改 Echo 配置,而是替换请求体的读取源头。你需要自己初始化 multipart.Reader,并让它的数据来自一个带进度统计的包装器:
- 提前读取
c.Request.Header.Get("Content-Length")获取总大小(注意:客户端必须发送该 header,否则 fallback 到 -1) - 定义一个
ProgressReader类型,嵌入原始io.Reader,并在Read(p []byte)中累加已读字节数、更新sync.Map中以uploadID为 key 的状态 - 用这个
ProgressReader初始化multipart.NewReader(),再逐个调用NextPart()解析每个 part - 绝对不要调用
c.ParseMultipartForm()或c.MultipartForm(),它们会绕过你的包装器,直接读原始 Body
示例关键片段:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
pr := &ProgressReader{
Reader: c.Request.Body,
total: totalSize,
uploaded: 0,
status: statusMap,
uploadID: uploadID,
}
mr := multipart.NewReader(pr, boundary)
for {
part, err := mr.NextPart()
if err == io.EOF {
break
}
// 处理 part.Header 和 part.Body,例如保存到磁盘
}
客户端怎么获取进度:轮询 vs WebSocket
服务端把进度存进 sync.Map 或 Redis 后,客户端需主动拉取或被动接收:
- 轮询最简单:前端定时发 GET 请求到
/api/progress?upload_id=xxx,后端返回当前uploaded和total - WebSocket 更实时但复杂:需要额外维护连接生命周期,上传开始时绑定
uploadID到 conn,每次进度更新时conn.WriteJSON() - 别依赖
session.upload_progress:那是 PHP 特性,Echo 没有等价机制
轮询接口示例:
e.GET("/api/progress", func(c echo.Context) error {
id := c.QueryParam("upload_id")
if stat, ok := statusMap.Load(id); ok {
return c.JSON(http.StatusOK, stat)
}
return c.JSON(http.StatusNotFound, map[string]string{"error": "not found"})
})
真正容易被忽略的是边界条件:客户端没发 Content-Length 时 total 为 -1,进度只能按百分比估算;多个并发上传共用同一个 sync.Map 时 key 冲突;ProgressReader.Read() 被多次调用但未做并发保护——这些地方一漏,进度就跳变或卡死。

















