net/http 库的客户端实现(下)

前言

上一篇文章我们讲了 net/http 库客户端 request 的构建,接下来继续讲构建HTTP请求之后的处理操作

net/http 库的客户端实现(上)

net/http 库的客户端实现(下)

net/http 库的服务端实现

启动事务

构建 HTTP 请求后,接着需要开启HTTP事务进行请求并且等待远程响应,以net/http.Client.Do()方法为例子,理一下它的调用链路:

  • net/http.Client.Do()
  • net/http.Client.do()
  • net/http.Client.send()
  • net/http.Send()
  • net/http.Transport.RoundTrip()

RoundTrip()RoundTripper类型中的一个的方法,net/http.Transport是其中的一个实现,可以在net/http/transport.go文件中找到这个方法,看一下源码。

func (t *Transport) roundTrip(req *Request) (*Response, error) {
	// 省略

	for {
		// 检测 ctx 退出信号
		select {
		case <-ctx.Done():
			req.closeBody()
			return nil, ctx.Err()
		default:
		}

		// 获取连接,通过使用连接池对资源进行了复用;
		pconn, err := t.getConn(treq, cm)
		if err != nil {
			t.setReqCanceler(cancelKey, nil)
			req.closeBody()
			return nil, err
		}

		var resp *Response
		if pconn.alt != nil {
			// HTTP/2 path.
			t.setReqCanceler(cancelKey, nil) // not cancelable with CancelRequest
			resp, err = pconn.alt.RoundTrip(req)
		} else {
			// 处理响应
			resp, err = pconn.roundTrip(treq)
		}
		if err == nil {
			resp.Request = origReq
			return resp, nil
		}

		// Rewind the body if we're able to.
		req, err = rewindBody(req)
		if err != nil {
			return nil, err
		}
	}
}

重点看两部分

  • net/http.Transport.getConn()获取连接
  • net/http.persistConn.roundTrip()处理写入 HTTP 请求,并在select中等待响应的返回

获取连接(getConn)

func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
 req := treq.Request
 trace := treq.trace
 ctx := req.Context()
 if trace != nil && trace.GetConn != nil {
  trace.GetConn(cm.addr())
 }

 w := &wantConn{
  cm:         cm,
  key:        cm.key(),
  ctx:        ctx,
  ready:      make(chan struct{}, 1),
  beforeDial: testHookPrePendingDial,
  afterDial:  testHookPostPendingDial,
 }
 defer func() {
  if err != nil {
   w.cancel(t, err)
  }
 }()

 // 在队列中有闲置连接,直接返回
 if delivered := t.queueForIdleConn(w); delivered {
  pc := w.pc
  return pc, nil
 }

 cancelc := make(chan error, 1)
 t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })

 // 放到队列中等待建立新的连接
 t.queueForDial(w)

 // 阻塞等待连接
 select {
 case <-w.ready:
  return w.pc, w.err
 case <-req.Cancel:
  return nil, errRequestCanceledConn
 case <-req.Context().Done():
  return nil, req.Context().Err()
 case err := <-cancelc:
  if err == errRequestCanceled {
   err = errRequestCanceledConn
  }
  return nil, err
 }
}

image.png
因为连接的建议会消耗比较多的时间,带来较大的开下,所以Go语言使用了连接池对资源进行分配和复用,先调用 net/http.Transport.queueForIdleConn() 获取等待闲置的连接,如果没有获取到在调用net/http.Transport.queueForDial 在队列中等待建立新的连接,通过select监听连接是否建立完毕,超时未获取到连接会上剖错误,我们继续在queueForDial追踪TCP连接的建立:
image.png

启动一个goroutinetcp的建连,最终调用dialConn方法,在这个方法内做持久化连接,调用net库的dial方法进行TCP连接:
image.png
在连接建立后,代码中我们我们还看到分别启动了两个goroutine

  • readLoop用于从tcp连接中读取数据,
  • writeLoop用于从tcp连接中写入数据;

writeLoop方法监听writech通道,在循环中写入发送的数据。
image.png

net/http.Transport{}中提供了连接池配置参数,可以自定义

处理 HTTP 请求

net/http.persistConn.roundTrip() 处理HTTP请求
image.png

image.png
有两个通道:

  • pc.writech :其类型是chan writeRequest writeLoop协程会循环写入数据,net/http.Request.write会根据net/http.Request结构中的字段按照HTTP协议组成TCP数据段,TCP协议栈会负责将HTTP请求中的内容发送到目标服务器上;
  • pc.reqch:其类型是chan requestAndChanreadLoop协程会循环读取响应数据并且调用net/http.ReadResponse进行协议解析,其中包含状态码、协议版本、请求头等内容;

总结

  • net/http.Client是级别最高的抽象,其中transport用于开启HTTP事务,jar用于处理cookie
  • net/http.Transport中主要逻辑两部分:
    • 从连接池中获取持久化连接
    • 使用持久化连接处理HTTP请求

net/http库中默认有一个DefaultClient可以直接使用,DefaultClient有对应DefaultTransport,可以满足大多数场景。

  • 如果需要使用自己管理HTTP客户端的头域、重定向等策略,可以自定义Client
  • 如果需要管理代理、TLS配置、连接池、压缩等设置,可以自定义Transport

因为HTTP协议的版本是不断变化的,所以为了可扩展性,transport是一个接口类型,具体的是实现是Transporthttp2TransportfileTransport,这样实现扩展性变得很高。

HTTP在建立连接时会耗费大量的资源,需要开辟一个goroutine去创建TCP连接,连接建立后会在创建两个goroutine用于HTTP请求的写入和响应的解析,然后使用channel进行通信,所以要合理利用连接池,避免大量的TCP连接的建立可以优化性能;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值