
在 Go 中,若未调用 resp.Body.Close(),将导致 TCP 连接无法复用、文件描述符泄漏,进而引发资源耗尽;defer 应置于错误检查之后,否则可能因 resp 为 nil 引发 panic。
在 go 中,若未调用 `resp.body.close()`,将导致 tcp 连接无法复用、文件描述符泄漏,进而引发资源耗尽;`defer` 应置于错误检查之后,否则可能因 `resp` 为 `nil` 引发 panic。
HTTP 客户端请求完成后,http.Response.Body 是一个实现了 io.ReadCloser 的流式接口,底层通常绑定一个活跃的网络连接(如 keep-alive 的 TCP 连接)。不关闭它,不是内存泄漏,而是资源泄漏:操作系统级别的文件描述符不会被释放,连接无法归还至 http.Transport 的连接池,后续相同目标的请求将被迫新建连接,最终可能导致“too many open files”错误或服务端连接拒绝。
❌ 错误写法(panic 风险 + 资源泄漏)
client := http.DefaultClient
resp, err := client.Do(req)
defer resp.Body.Close() // 危险!若 err != nil,resp 可能为 nil
if err != nil {
return nil, err
}
当 client.Do() 返回非 nil 错误时(例如 DNS 失败、连接超时、TLS 握手失败),resp 保证为 nil(见 Go 源码 及文档)。此时执行 resp.Body.Close() 会触发 panic:panic: runtime error: invalid memory address or nil pointer dereference。
✅ 正确写法(安全 + 资源可控)
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
return nil, err // 提前返回,避免操作 nil resp
}
defer resp.Body.Close() // 此时 resp 必然非 nil,Body 可安全关闭
// 读取响应体(必须读完或显式丢弃,否则连接仍可能无法复用)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// 处理 body...
⚠️ 注意:仅调用 Close() 不足以确保连接复用。http.Transport 要求响应体必须被读取到 EOF(即完整消费)或显式调用 io.Copy(io.Discard, resp.Body) 丢弃,再 Close(),才能将连接放回空闲池。否则即使关闭了 Body,连接也可能被直接关闭(尤其当 Content-Length 或 Transfer-Encoding 不明确时)。
最佳实践总结
- ✅ 永远在 err == nil 检查之后再 defer resp.Body.Close();
- ✅ 务必消费整个 resp.Body(io.ReadAll、io.Copy、循环 Read 至 io.EOF,或使用 io.Discard 显式丢弃);
- ✅ 若只需响应头(如检查状态码),仍需关闭 Body:
if resp.StatusCode != http.StatusOK { resp.Body.Close() // 立即关闭,无需读取 return errors.New("bad status") } defer resp.Body.Close() - ? 避免在 resp 作用域外延迟关闭(如返回 resp.Body 给调用方),责任边界必须清晰。
遵循以上原则,可确保 HTTP 客户端高效、稳定、无资源泄漏地运行。











