You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go net/http包读取res.Body后为什么需要手动调用Close方法?

为什么需要调用res.Body.Close()

http.Get方法内部发起网络请求时,会自动建立底层TCP连接来接收响应数据,res.Body本质是对这个连接对应数据流的封装,资源在你调用http.Get拿到非空res的时候就已经完成分配,所有权转移到了调用方,你虽然没有显式调用打开资源的接口,但你是资源的持有者,自然有义务完成资源释放。如果不执行关闭操作,TCP连接关联的内存、文件描述符资源会长期占用无法回收,高并发场景下很容易出现资源泄漏,最终导致程序无法创建新的连接甚至崩溃。

更优的实现方案

你当前的写法存在隐患:如果io.ReadAll执行报错,后续逻辑会直接触发log.Fatal退出,res.Body.Close()就不会被执行,更稳妥的写法是在拿到非空res后立刻用defer注册关闭逻辑:

func main() {
    res, err := http.Get("http://www.google.com/robots.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close() // 确保无论后续逻辑是否报错,资源都会被释放
    body, err := io.ReadAll(res.Body)
    if res.StatusCode > 299 {
        log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body)
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", body)
}

如果想要简化调用,也可以自行封装工具函数,把读取和关闭的逻辑整合在一起:

func ReadAllAndClose(r io.ReadCloser) ([]byte, error) {
    defer r.Close()
    return io.ReadAll(r)
}

后续直接调用该工具函数读取响应体即可,不需要单独处理关闭逻辑。

关于设计合理性的说明

这是Go语言非常典型的设计范式,核心逻辑是谁持有资源所有权,谁负责资源释放:

  • io.ReadAll的入参是io.Reader接口,该接口本身没有定义关闭方法,它的职责仅为读取流中全部内容,既没有义务也没有能力去关闭流;如果强行要求io.ReadAll负责关闭,反而会大幅缩小它的适用范围,比如你读取本地文件时如果需要读完一部分后继续操作后续内容,调用io.ReadAll直接把流关了反而会违背需求。
  • 这个设计并不违背开闭原则:io.Reader作为抽象层对所有可读资源开放适配,io.ReadAll不需要做任何修改就能支持所有实现了该接口的资源类型,关闭逻辑由持有具体资源(实现了io.Closer接口)的调用方处理,权责划分非常清晰。

内容的提问来源于stack exchange,提问作者demee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 21:57:02