echo框架默认调用parsemultipartform会提前读空request.body,导致后续无法读取分片原始字节;需在handler开头显式调用c.request().parsemultipartform(0)禁用自动解析,再手动流式处理分片。

为什么 Echo 默认会吃掉分片数据
因为 echo.Context.FormFile 和自动调用的 c.Request().ParseMultipartForm 会提前读空 request.Body,导致后续手动读取分片原始字节时返回空。这不是 Bug,而是框架为兼容普通表单设计的默认行为。
必须显式禁用:在接收分片的 handler 开头加 c.Request().ParseMultipartForm(0),其中 0 表示不限制内存缓冲,让框架跳过解析,把原始 body 流留给开发者自己处理。
常见错误现象:
- 前端明明传了文件块,服务端
c.FormFile("file")返回nil或报错http: no such file - 用
io.ReadAll(c.Request().Body)读到空字节,但c.Request().ContentLength显示有数据
如何安全接收单个分片
绕过框架自动解析后,需手动提取元信息(如 upload_id、index)并流式保存分片内容,避免内存暴涨。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
实操要点:
- 元信息统一从 URL query 或 form 字段获取,不要依赖
multipart解析 —— 例如用c.FormValue("upload_id")而非从fileHeader里抠 - 用
io.Copy直接将c.Request().Body写入临时文件,不经过bytes.Buffer或io.ReadAll - 临时路径按
upload_id/index组织,比如/tmp/uploads/{upload_id}/000001,便于后续合并时排序 - 务必校验
Content-Length是否与预期分片大小匹配(允许最后片略小),防止截断或注入
怎么判断分片是否收全并触发合并
不能只靠客户端传来的 total_chunks 值做判断——它可能被篡改,或因并发上传顺序混乱导致误判。真实可靠的状态必须由服务端主动维护。
推荐做法:
- 用 Redis 存储每个
upload_id的已接收分片索引集合,命令如SADD upload:{id} 000001 - 合并前执行
SCARD upload:{id}并比对客户端声称的总数;同时检查是否存在索引跳跃(如收到 000001 和 000003 却缺 000002) - 合并操作必须原子化:先重命名临时目录为
.merging后缀,再逐片os.Open+io.Copy到目标文件,最后os.Rename完成落盘 - 合并失败时保留
.merging目录,供下次重试识别状态,避免重复合并
哪些地方最容易被忽略
真正上线后出问题的,往往不是主流程,而是边界细节:
-
upload_id如果只靠客户端生成,攻击者可伪造已存在 ID 绕过校验 —— 必须服务端签发,并绑定用户 session 或 token - 临时分片文件没设过期策略,磁盘悄悄被占满;建议用
filepath.WalkDir配合time.Since(fi.ModTime()) > 24*time.Hour定期清理 - 合并时若直接
os.Create最终文件,旧文件可能被覆盖一半就中断,导致损坏;应先写到{file}.tmp,成功后再os.Rename - HTTP/2 下并发上传多个分片时,Nginx 默认
client_max_body_size是针对单请求的,不是总和 —— 要确认网关层没拦截单个分片










