必须手动关闭 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、keep-alive 和 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 统计确认。很多问题其实不出在读响应本身,而在于连接没管好——超时、泄漏、复用失效,这些才是线上抖动的真正源头。