不能直接在路由 handler 里调用 c.FormValue() 或 c.Bind() 后再读原始 Body,因为 c.Request().Body 是单次消费的 io.ReadCloser,读取一次即 EOF;需先 io.ReadAll 捕获字节并用 io.NopCloser 重置 Body 才能复用。

不能直接在路由 handler 里调用 c.FormValue() 或 c.Bind() 后再想读原始 Body——此时 c.Request().Body 已被消耗,读出来是空的或报 http: read on closed response body。
为什么路由 handler 里第一次读 Body 就失败?
因为 c.Request().Body 是单次消费的 io.ReadCloser,底层要么是网络连接缓冲区,要么是内存 reader。一旦被框架内部(比如 c.BindJSON())或你自己调用 io.ReadAll() 读过一次,流就到 EOF,后续再读就是空字节或 io.EOF。
怎么在 handler 开头安全读取原始 Body 字节
必须手动捕获并重置,让后续逻辑(比如结构体绑定)还能用:
- 先调用
bodyBytes, err := io.ReadAll(c.Request().Body)获取原始字节 - 检查
err,非nil说明读取异常(如超时、断连),应直接返回错误 - 调用
c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))把字节重新包装成可读流 - 之后才能放心调用
c.BindJSON(&v)或json.Unmarshal(bodyBytes, &v)
Content-Type 不匹配时容易 panic
不同支付回调对原始 Body 格式要求极严:支付宝是 application/x-www-form-urlencoded,微信是 application/xml,PayJS 是 application/json。如果没校验 header 就硬读,可能:
- 用
xml.Unmarshal()解析 JSON 字符串 → panic: invalid character - 用
url.ParseQuery()解析 XML → 返回空 map - 伪造
Content-Type: application/json绕过 XML 验签逻辑
务必在读 Body 前加判断:if c.Request().Header.Get("Content-Type") != "application/json" { return c.JSON(400, "invalid content type") }
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
大文件上传场景下别全量读 Body
如果路由处理的是 multipart/form-data(含文件上传),io.ReadAll(c.Request().Body) 会把整个请求体(可能上百 MB)读进内存,OOM 风险极高,且后续 mime/multipart.NewReader() 无法再用这个已耗尽的 Body。
正确做法是跳过中间件/路由层的全量读取,直接用:
mr, err := mime/multipart.NewReader(c.Request().Body, boundary)- 循环
mr.NextPart(),对每个part边读边存(如写入磁盘或 S3) - 用
http.MaxBytesReader包装原始 Body,防恶意超大文件
真正难的不是“怎么读”,而是“读完要不要还、还给谁、还成什么样”——漏掉 io.NopCloser 包装,下游就拿不到数据;对 multipart 类型硬读,服务就卡死。这两个点,90% 的线上问题都出在这儿。










