golang HTTP Response Body的内存泄漏问题

在爬虫类的场景中, 我们需要做大量的并发http请求,所以经常需要打开和关闭大量http连接。 如果没有正确处理http连接, 很容易导致内存的泄漏。

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    defer resp.Body.Close()//not ok
    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

这么写肯定是不对的, 如果请求失败的话, resp就可能为nil值, 此时就会panic。

通常大家会这么写:

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    if err != nil {
        fmt.Println(err)
        return
    }

    defer resp.Body.Close()//ok, most of the time :-)
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

大多数情况下,当你的http请求失败, resp变量会为nil, err变量会为非nil值。 然而, 当你得到的是一个重定向错误时, resp和err变量都会变为非nil值。 这意味着此时你无法关闭resp.Body,就会导致内存的泄漏。

Most of the time when your http request fails the resp variable will be nil and the err variable will be non-nil. However, when you get a redirection failure both variables will be non-nil. This means you can still end up with a leak.

你可以通过以下方式修复这个内存泄漏问题, 即在结果处理的时候, 判断resp变量如果为非nil值, 就调用close。 另一个方式是, 不论成功失败,退出前都使用defer 调用close去关闭response.Body。

You can fix this leak by adding a call to close non-nil response bodies in the http response error handling block. Another option is to use one defer call to close response bodies for all failed and successful requests.

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    resp, err := http.Get("https://api.ipify.org?format=json")
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

原来的resp.Body.Close()实现中, 会替你读取和丢弃body中的残留数据。 这样可以确保这个http连接可以被其他请求重用, 比如在一些使能了http长连接的场景中。 最新的Body.Close函数实现是不一样的。现在,需要你自己去读取和丢弃残留的数据。 如果你不这么做, http会被关闭,而无法被重用。

The orignal implementation for resp.Body.Close() also reads and discards the remaining response body data. This ensured that the http connection could be reused for another request if the keepalive http connection behavior is enabled. The latest http client behavior is different. Now it’s your responsibility to read and discard the remaining response data. If you don’t do it the http connection might be closed instead of being reused.

如果你需要重用http连接, 那你需要在你的代码底部添加如下代码。

If reusing the http connection is important for your application you might need to add something like this at the end of your response processing logic:

_, err = io.Copy(ioutil.Discard, resp.Body)  

假设你不需要立刻读取整个响应body, 比如在你在流式处理json响应的时候,这么做就很有必要。

It will be necessary if you don’t read the entire response body right away, which might happen if you are processing json API responses with code like this:

json.NewDecoder(resp.Body).Decode(&data)   

以上基本翻译自这里: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#close_http_resp_body

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值