http.Client
连接的建立和获取
// 基于 go1.14
res, err := client.Do(req)
// 调用 c.do
func (c *Client) Do(req *Request) (*Response, error) {
return c.do(req)
}
// 调用 c.send
func (c *Client) do(req *Request) {
if resp, didTimeout, err = c.send(req, deadline); err != nil {
// ...
}
}
// 调用 send
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
resp, didTimeout, err = send(req, c.transport(), deadline)
}
// 调用 rt.RoundTrip
func send(ireq *Request, rt RoundTripper, deadline time.Time) {
resp, err = rt.RoundTrip(req)
}
// 从这里进入 RoundTrip 逻辑
// src/net/http/roundtrip.go: 16
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
return t.roundTrip(req)
}
// 调用 t.GetConn
func (t *Transport) roundTrip(req *Request) (*Response, error) {
// 尝试去获取一个空闲连接,用于发起 http 连接
pconn, err := t.getConn(treq, cm)
}
// 实现 t.getConn
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
w := &wantConn{
key: cm.key(), // 这个key就是域名
}
// 获取空闲连接,或者新建一个连接
t.queueForDial(w)
}
func (t *Transport) queueForDial(w *wantConn) {
// 空闲连接以 map 的形式存储,key 为域名。HTTP2里一个域名下可以有多个连接
n := t.connsPerHost[w.key];
// 如果未获取到,则新建连接
if n < t.MaxConnsPerHost {
go t.dialConnFor(w)
}
}
// 没有空闲连接,就创建连接
func (t *Transport) dialConnFor(w *wantConn) {
// 新建连接
pc, err := t.dialConn(w.ctx, w.cm)
// 并将连接放入连接池
delivered := w.tryDeliver(pc, err)
if err == nil && (!delivered || pc.alt != nil) {
t.putOrCloseIdleConn(pc)
}
}
// 就创建连接
// src/net/http/tansport.go
func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
pconn = &persistConn{}
conn, err := t.dial(ctx, "tcp", cm.addr())
pconn.conn = conn
// 读、写共起了2个新协程
// 如果收到响应后没有执行 resp.Body.Close(), 这里就会泄露2个 goroutine
go pconn.readLoop()
go pconn.writeLoop()
}为什么不执行 resp.Body.Close 会内存泄漏
参考
Last updated

