必须手动关闭 response.Body,否则会导致连接泄漏和文件描述符耗尽;正确做法是在检查 err 为 nil 后用 defer resp.Body.Close() 确保关闭,并配合 io.LimitReader 防 OOM,同时配置 http.Client 超时与连接复用参数。

为什么 response.Body 必须手动关闭
Go 的 http.Client 不会自动关闭响应体,不调用 resp.Body.Close() 会导致连接泄漏、文件描述符耗尽,尤其在高频请求或长连接场景下很快触发 too many open files 错误。
常见错误写法是只读取内容就结束,忽略关闭:
resp, err := http.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
body, _ := io.ReadAll(resp.Body)
// ❌ 忘记 resp.Body.Close()
正确做法始终用 defer 关闭(注意:必须在检查 err 之后,否则 resp 可能为 nil):
- 先判断
err是否非空,再操作resp -
defer resp.Body.Close()放在if err == nil分支内最开头 - 即使后续读取失败,也要确保关闭已打开的
Body
如何安全读取 resp.Body 并避免阻塞
resp.Body 是一个 io.ReadCloser,直接用 io.ReadAll 适合小响应;但对大响应或流式接口(如 SSE、长 JSON 数组),应配合 io.LimitReader 或分块读取防止 OOM。
立即学习“go语言免费学习笔记(深入)”;
典型安全读取模式:
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close() // ✅ 此处确保关闭
// 设置最大读取长度,防恶意大响应
limitedBody := io.LimitReader(resp.Body, 1010241024) // 10MB
body, err := io.ReadAll(limitedBody)
if err != nil {
return fmt.Errorf("read body failed: %w", err)
}
-
Content-Length头不可信,不能仅靠它做限制 - 使用
io.LimitReader比在ReadAll后校验字节数更早中断读取 - 若需解析 JSON,建议用
json.NewDecoder(limitedBody)直接解码,避免内存拷贝
resp.StatusCode 和重定向行为怎么控制
默认 http.Client 会自动跟随最多 10 次重定向(301/302/307/308),但有时你需要拦截重定向、检查跳转链或处理 304 Not Modified。
关键点:
- 总是先检查
resp.StatusCode,不要假设是200 - 用自定义
Client禁用重定向:&http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }} -
304响应的Body为空,但可能含Last-Modified或ETag,需单独处理缓存逻辑 - 某些 API(如 GitHub)返回
403时带X-RateLimit-Remaining,应解析该头而非直接报错
如何复用连接并设置超时
默认 http.DefaultClient 复用 TCP 连接,但没设超时,容易卡死。生产环境必须显式配置 Timeout、KeepAlive 和 MaxIdleConns。
推荐客户端初始化方式:
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
ForceAttemptHTTP2: true,
},
}
-
Timeout控制整个请求生命周期(DNS + 连接 + 写请求 + 读响应),不是单个阶段超时 -
IdleConnTimeout影响连接池中空闲连接存活时间,太短会频繁建连,太长可能被服务端主动断开 - 如果服务端支持 HTTP/2,
ForceAttemptHTTP2可提升多路复用效率
连接复用是否生效,可通过 resp.Header.Get("Connection") 是否为 keep-alive(HTTP/1.1)或观察 Transport 的 IdleConn 统计确认。很多问题其实不出在读响应本身,而在于连接没管好——超时、泄漏、复用失效,这些才是线上抖动的真正源头。










