使用httptest.NewServer测试出现read on closed response body错误原因
错误发生原因
- 你在
GetData方法内部声明了defer resp.Body.Close(),该语句会在GetData方法执行结束、返回resp对象之前自动触发,直接关闭响应体。你后续在测试代码中尝试读取已经被关闭的resp.Body,就会抛出http: read on closed response body错误。 - 程序正常运行时无报错,是因为你之前的正常业务逻辑不会在
GetData返回后再读取resp.Body,所有对响应体的操作都收拢在GetData内部,所以不会触发该问题,本质是业务方法设计和调用逻辑不匹配导致的问题。
正确处理方案
根据你的业务场景可以选择以下三种方案,优先选择前两种符合Go开发规范的方案:
方案1:调整业务方法返回值,不对外暴露原始响应对象
如果上层不需要获取原始*http.Response对象,直接修改GetData方法的返回值,只返回业务处理后的数据和错误即可。所有对resp.Body的读取、解析、关闭逻辑都收拢在GetData方法内部,外部不需要接触原始响应体,从根源上避免该问题,这是最推荐的方案。
方案2:明确关闭责任归属,由调用方处理响应体关闭
如果业务逻辑确实需要对外返回原始*http.Response对象,遵循Go HTTP客户端的通用设计规范,移除GetData方法内部的defer resp.Body.Close()代码,将关闭响应体的责任交给调用方。无论是正常业务代码还是测试代码,在完成resp.Body的读取操作后,自行调用resp.Body.Close()释放资源即可。
方案3:在业务方法内重置响应体(临时兼容方案)
如果既需要保留GetData内部关闭响应体的逻辑,又需要外部可以读取返回的resp.Body内容,可以在GetData内部读取完响应体内容后,重置resp.Body再返回:
import ( "bytes" "io" "net/http" ) func GetData(url string) (*http.Response, error) { resp, err := http.Get(url) if err != nil { return nil, err } // 读取原始Body内容 bodyBytes, err := io.ReadAll(resp.Body) if err != nil { resp.Body.Close() return nil, err } // 关闭原始Body resp.Body.Close() // 重置Body为可重复读取的缓冲区,且自带空Close实现 resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) return resp, nil }
该方案下返回的resp.Body是新构造的缓冲区,没有被关闭,外部可以正常读取,不需要额外关闭也不会有资源泄漏问题。
内容的提问来源于stack exchange,提问作者CJW
相关产品推荐
相关产品推荐

