【问题标题】:What could happen if I don't close response.Body?如果我不关闭 response.Body 会发生什么?
【发布时间】:2021-05-22 05:27:07
【问题描述】:

在 Go 中,我有一些 http 响应,但有时会忘记调用:

resp.Body.Close()

在这种情况下会发生什么?会不会有内存泄漏?获取响应对象后立即输入defer resp.Body.Close() 是否安全?

client := http.DefaultClient
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
    return nil, err
}

如果出现错误怎么办,respresp.Body 可以为零吗?

【问题讨论】:

  • 当存在 return 时,可以将 defer resp.Body.Close() 放在 err != nil 之后,因为当 err 不是 nil 时,它已经关闭。另一方面,当请求成功时,主体需要显式关闭。

标签: go


【解决方案1】:

在这种情况下会发生什么?会不会有内存泄漏?

这是资源泄漏。连接不会被重复使用,并且可以保持打开状态,在这种情况下文件描述符不会被释放。

获取响应对象后立即放入 defer resp.Body.Close() 是否安全?

不,按照文档中提供的示例,检查错误后立即关闭它。

client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

来自http.Client 文档:

如果返回的错误为 nil,则 Response 将包含一个非 nil 正文,用户应关闭该正文。如果 Body 没有被读取到 EOF 并被关闭,则客户端的底层 RoundTripper(通常是 Transport)可能无法重新使用与服务器的持久 TCP 连接来进行后续的“keep-alive”请求。

【讨论】:

  • 根据link,仍然有可能泄漏与您的代码的连接。在某些情况下,响应为非零且错误为非零。
  • @mmcdole:那个帖子是错误的,并且不能保证它不会恐慌,因为任何返回的错误响应都没有定义的状态。如果 Body 没有因错误而关闭,那么这是一个错误,需要报告。你应该去official client documentation,它声明“出错时,任何响应都可以被忽略”,而不是随机的博客文章。
  • @del-boy:如果您希望该客户端发出更多请求,那么您应该尝试读取正文,以便可以重用连接。如果您不需要连接,请不要费心阅读正文。如果您阅读正文,请使用io.LimitReader 将其包裹起来。我通常使用一个相当小的限制,因为如果请求太大,建立新连接会更快。
  • 值得指出的是,执行_, err := client.Do(req) 也会导致文件描述符保持打开状态。因此即使不关心响应是什么,仍然需要将其分配给一个变量并关闭主体。
  • 对于任何感兴趣的人,完整的文档是(强调添加):“出错时,任何响应都可以被忽略。只有当 CheckRedirect 失败时,才会发生具有非零错误的非零响应,并且 即便如此返回的 Response.Body 已经关闭。”
【解决方案2】:

如果Response.Body 不会被Close() 方法关闭,那么与fd 关联的资源将不会被释放。这是资源泄漏。

关闭Response.Body

来自response source

关闭 Body 是调用者的责任。

所以没有绑定到对象的终结器,它必须显式关闭。

错误处理和延迟清理

发生错误时,可以忽略任何响应。仅当 CheckRedirect 失败时才会发生具有非 nil 错误的非 nil Response,即使这样返回的 Response.Body 已经关闭。

resp, err := http.Get("http://example.com/")
if err != nil {
    // Handle error if error is non-nil
}
defer resp.Body.Close() // Close body only if response non-nil

【讨论】:

  • 您应该注意它们应该在您的错误处理条件内返回。如果用户在错误处理中没有返回,这将导致恐慌。
【解决方案3】:

一开始描述符永远不会关闭,如上所述。

更重要的是,如果DisableKeepAlives 为假,golang 将缓存连接(使用persistConn 结构来包装)以供重用。

在golang中使用client.Do方法后,go会运行goroutinereadLoop方法作为步骤之一。

所以在 golang http transport.go 中,pconn(persistConn struct) 不会被放入 idleConn 通道,直到在 readLoop 方法中取消请求,并且这个 goroutine(readLoop 方法)将被阻塞,直到请求已取消。

Here is the code 显示它。

想了解更多,需要看readLoop方法。

【讨论】:

    【解决方案4】:

    https://golang.org/src/net/http/client.go
    “当 err 为 nil 时,resp 总是包含一个非 nil 的 resp.Body。”

    但他们没有说当 err != nil 时,resp 总是为零。他们接着说:
    “如果 resp.Body 没有关闭,客户端的底层 RoundTripper(通常是传输)可能无法重新使用与服务器的持久 TCP 连接来进行后续的“保持活动”请求。”

    所以我通常会这样解决问题:

    client := http.DefaultClient
    resp, err := client.Do(req)
    if resp != nil {
       defer resp.Body.Close()
    }
    if err != nil {
        return nil, err 
    }
    

    【讨论】:

    • 这是不正确的,并且不能保证出现错误时resp.Body是nit nil。
    • 谢谢@JimB。文档中的措辞是“出错时,可以忽略任何响应”。说“出错时,响应正文总是关闭”会更准确。
    • 不,因为通常没有要关闭的响应正文。如果您继续阅读文档中的那一段 - “只有在 CheckRedirect 失败时才会发生带有非 nil 错误的非 nil 响应,即使这样,返回的 Response.Body 也已经关闭。”
    【解决方案5】:

    一种选择是将子请求请求放入一个新的上下文中,这样您就可以 如果需要,可以使用相同的变量名,而不必担心破坏任何 现有变量,但仍然关闭所有内容:

    package main
    
    import (
       "bytes"
       "net/http"
    )
    
    func main() {
       b := new(bytes.Buffer)
       // request one
       r, e := http.Get("http://speedtest.atl.hivelocity.net")
       if e != nil {
          panic(e)
       }
       defer r.Body.Close()
       b.ReadFrom(r.Body)
       // request two
       {
          r, e := http.Get("http://speedtest.lax.hivelocity.net")
          if e != nil {
             panic(e)
          }
          defer r.Body.Close()
          b.ReadFrom(r.Body)
       }
       // print
       print(b.String())
    }
    

    【讨论】:

      猜你喜欢
      • 2016-09-14
      • 2017-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多