为什么需要关闭?
1
2
3
4
5
6
7
|
Body io.ReadCloser
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 does not attempt to
reuse HTTP/1.0 or HTTP/1.1 TCP connections ("keep-alive") unless the Body is read to
completion and is closed.
|
1
2
3
|
http客户端(Client)和传输(Transport)保证响应体总是非空的,即使响应没有响应体或0长响应
体。关闭响应体是调用者的责任。默认http客户端传输(Transport)不会尝试复用keep-alive的
http/1.0、http/1.1连接,除非请求体已被完全读出而且被关闭了。
|
以上是http包文档说明。但是为什么body需要被关闭呢,不关闭会如何?
阅读 http.Client 发送请求的源码梳理流程,整个 http 请求的读取都是通过实现了 http 协议的 Transport 来实现,Transport 又利用 persistConn 封装了普通的 connection 来达到连接服用的目的,也就是满足 http 中 keep-alive 的需求,一旦一个 http client 需要 keep-alive 那么这个 connection 就不会断开,重复利用,而 readLoop 的逻辑就是一个 for 循环里面几个小的 select,for 循环退出的条件就是 alive 变成 false:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
select {
case rc.ch <- responseAndError{res: resp}:
case <-rc.callerGone:
return
}
select {
case bodyEOF := <-waitForBodyRead:
pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
alive = alive &&
bodyEOF &&
!pc.sawEOF &&
pc.wroteRequest() &&
tryPutIdleConn(trace)
if bodyEOF {
eofc <- struct{}{}
}
case <-rc.req.Cancel:
alive = false
pc.t.CancelRequest(rc.req)
case <-rc.req.Context().Done():
alive = false
pc.t.cancelRequest(rc.req, rc.req.Context().Err())
case <-pc.closech:
alive = false
}
|
这几个 select 都是等待各种 chan 满足条件之后才能继续执行,而 goroutine 退出需要满足:
- body 读取完毕
- request 主动 cancel
- request context Done 状态 true
- 当前的 persistConn 关闭
所以上述几个条件不满足,goroutine 将一直存在,也就是如果一个 http request 的 body 没有被用到,那么这个 goroutine 也不会被关闭。
正确使用方式如下:
1
2
3
4
5
6
7
8
9
|
// The client must close the response body when finished with it:
resp, err := http.Get("http://example.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// ...
|
何时需要关闭?
作为client端处理response的时候,有一点要注意的是,body一定要手动close,否则会造成GC回收不到,继而产生内存泄露。其实在go的官方源码注释中,也明确注明了response body需要调用方法进行手动关闭【It is the caller`s responsibility to close Body:关闭是调用者的责任】,
那么作为client端生成的request body,需不需要手动关闭呢,答案是不需要的,net/http中的 func (c *Client) Do(req *Request) (*Response, error)
会调用Close()。
同样的,作为server端接收的request body,也是需要关闭,由Server自动进行关闭【The Server will close the request body. The ServeHTTP Handler does not need to:服务器将关闭请求正文。 ServeHTTP处理程序不需要。】
参考:
https://sanyuesha.com/2019/09/10/go-http-request-goroutine-leak/