如何使用Golang读取HTTP响应_Golang net/http Response处理示例

必须手动关闭 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-ModifiedETag,需单独处理缓存逻辑
  • 某些 API(如 GitHub)返回 403 时带 X-RateLimit-Remaining,应解析该头而非直接报错

如何复用连接并设置超时

默认 http.DefaultClient 复用 TCP 连接,但没设超时,容易卡死。生产环境必须显式配置 TimeoutKeepAliveMaxIdleConns

推荐客户端初始化方式:

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