Go HTTP中间件使用http.Client导致原HttpRequest.Body关闭的问题排查
问题原因分析与解决方案
问题核心
你遇到的http: invalid Read on closed Body错误,本质是原请求的r.Body在中间件执行c.Do(hReq)后被意外关闭,导致后续Handler无法读取请求体。结合代码和场景,可能的原因及解决方式如下:
可能原因
- 调试器干扰:GoLand调试时,查看
r.Body的状态(如在Watch面板中观察)可能会触发隐式的读取或关闭操作,导致后续无法正常读取。 - 未正确处理HTTP客户端响应:虽然看似和原请求Body无关,但
c.Do(hReq)后未关闭响应Body会导致连接池异常,间接影响当前请求的资源状态(概率较低,但属于最佳实践缺失)。 - 请求体的一次性读取特性:
http.Request.Body是io.ReadCloser,本身只能被读取一次,若中间件或调试过程中存在隐式读取操作,会导致后续Handler无法再次读取(但你的代码中中间件未显式读取Body,更可能是调试器或库的隐式行为)。
解决方案
最可靠的解决方式是提前保存原请求体内容,重新创建可重复读取的Body对象,确保后续Handler能正常解析。同时修复HTTP客户端的响应处理逻辑,避免连接泄漏:
import ( "bytes" "io" "net/http" ) func (m *AuthorizeHandlerMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if token == "" { res := types.Base{ Code: 401, Message: "error", } logc.Error(r.Context(), "token is empty") httpx.OkJsonCtx(r.Context(), w, res) return } // 1. 保存原请求Body内容,重新创建可读取的Body var bodyBytes []byte if r.Body != nil { bodyBytes, _ = io.ReadAll(r.Body) r.Body.Close() // 用保存的字节重新生成一个可重复读取的ReadCloser r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) } hReq, err := http.NewRequest("GET", "http://localhost:801/admin/member/info", nil) if err != nil { logc.Error(r.Context(), err) return } // 2. 正确处理HTTP客户端响应,避免连接泄漏 resp, err := http.DefaultClient.Do(hReq) if err != nil { logc.Error(r.Context(), err) return } defer resp.Body.Close() // 必须读取响应Body,否则连接无法被连接池复用 _, _ = io.ReadAll(resp.Body) next(w, r) } }
额外验证步骤
- 关闭GoLand调试,直接运行程序,若错误消失,说明是调试器的隐式操作导致Body被关闭。
- 检查
httpx.Parse的实现,确认它不会在未读取的情况下提前关闭请求Body。
内容的提问来源于stack exchange,提问作者nil
相关产品推荐
相关产品推荐

