https://gocn.vip/topics/10626
读取完 http.Response 的 Body并关闭它,否则不会重用底层的 TCP 连接。我想了想为什么它这里一定要特别提出来呢?关闭 http.Response 不是一个常识性动作么?比如一般写代码我们都会遵循下面的模式:
resp, err := http.Get(“http://www.example.com”)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
// …
在结合实际的场景之后,我发现其实有的时候问题出在我们并不总是会去读取完整个http.Response 的 Body。为什么这么说呢?
在常见的 API 开发的业务逻辑中,我们会定义一个 JSON 的对象来反序列化 http.Response 的 Body,但是通常在反序列化这个回复之前,我们会做一些 http 的 StatusCode 检查,比如当 StatusCode 为 200 的时候,我们才去读取 http.Response 的 Body,如果不是 200,我们就直接返回一个包装好的错误。比如下面的模式:
resp, err := http.Get(“http://www.example.com”)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
var apiRet APIRet
decoder := json.NewDecoder(resp.Body)
err := decoder.Decode(&apiRet)
// …
}
如果代码是按照上面的这种方式写的话,那么在请求异常的时候,会导致大量的底层 TCP 无法重用,所以我们稍微改进下就可以了。
resp, err := http.Get(“http://www.example.com”)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
var apiRet APIRet
decoder := json.NewDecoder(resp.Body)
err := decoder.Decode(&apiRet)
// …
}else{
io.Copy(ioutil.Discard, resp.Body)
// …
}
我们通过直接将 http.Response 的 Body 丢弃掉就可以了。
原因
在 Go 的源码中,关于这个问题有特别的注释。
// Body represents the response body.
//
// The response body is streamed on demand as the Body field
// is read. If the network connection fails or the server
// terminates the response, Body.Read calls return an error.
//
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
// a zero-length body. It is the caller’s responsibility to
// close Body. The default HTTP client’s Transport may not
// reuse HTTP/1.x “keep-alive” TCP connections if the Body is
// not read to completion and closed.
//
// The Body is automatically dechunked if the server replied
// with a “chunked” Transfer-Encoding.
//
// As of Go 1.12, the Body will also implement io.Writer
// on a successful “101 Switching Protocols” response,
// as used by WebSockets and HTTP/2’s “h2c” mode.
Body io.ReadCloser
其中提到了必须将 http.Response 的 Body 读取完毕并且关闭后,才会重用底层的 TCP 连接。
https://github.com/webtao520/golang_project/tree/master/%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81context%E5%8C%85
https://www.digitalocean.com/community/books/how-to-code-in-go-ebook
https://mp.weixin.qq.com/s/BkVEuPGDPxYd1xQ_2YrHxQ
https://go.googlesource.com/proposal/+/master/design/draft-gobuild.md