Go中的HTTP请求之——HTTP1.1请求流程分析

来自公众号:新世界杂货铺

前言

http是目前应用最为广泛, 也是程序员接触最多的协议之一。今天笔者站在GoPher的角度对http1.1的请求流程进行全面的分析。希望读者读完此文后, 能够有以下几个收获:

  1. 对http1.1的请求流程有一个大概的了解
  2. 在平时的开发中能够更好地重用底层TCP连接
  3. 对http1.1的线头阻塞能有一个更清楚的认识

HTTP1.1流程

今天内容较多, 废话不多说, 直接上干货。

在这里插入图片描述

接下来, 笔者将根据流程图,对除了NewRequest以外的函数进行逐步的展开和分析

(*Client).do

(*Client).do方法的核心代码是一个没有结束条件的for循环。

for {
   
	// For all but the first request, create the next
	// request hop and replace req.
	if len(reqs) > 0 {
   
		loc := resp.Header.Get("Location")
		// ...此处省略代码...
		err = c.checkRedirect(req, reqs)
		// ...此处省略很多代码...
	}

	reqs = append(reqs, req)
	var err error
	var didTimeout func() bool
	if resp, didTimeout, err = c.send(req, deadline); err != nil {
   
		// c.send() always closes req.Body
		reqBodyClosed = true
		// ...此处省略代码...
		return nil, uerr(err)
	}

	var shouldRedirect bool
	redirectMethod, shouldRedirect, includeBody = redirectBehavior(req.Method, resp, reqs[0])
	if !shouldRedirect {
   
		return resp, nil
	}

	req.closeBody()
}

上面的代码中, 请求第一次进入会调用c.send, 得到响应后会判断请求是否需要重定向, 如果需要重定向则继续循环, 否则返回响应。

进入重定向流程后, 这里笔者简单介绍一下checkRedirect函数:

func defaultCheckRedirect(req *Request, via []*Request) error {
   
	if len(via) >= 10 {
   
		return errors.New("stopped after 10 redirects")
	}
	return nil
}
// ...
func (c *Client) checkRedirect(req *Request, via []*Request) error {
   
	fn := c.CheckRedirect
	if fn == nil {
   
		fn = defaultCheckRedirect
	}
	return fn(req, via)
}

由上可知, 用户可以自己定义重定向的检查规则。如果用户没有自定义检查规则, 则重定向次数不能超过10次

(*Client).send

(*Client).send方法逻辑较为简单, 主要看用户有没有为http.Client的Jar字段实现CookieJar接口。主要流程如下:

  1. 如果实现了CookieJar接口, 为Request添加保存的cookie信息。
  2. 调用send函数。
  3. 如果实现了CookieJar接口, 将Response中的cookie信息保存下来。
// didTimeout is non-nil only if err != nil.
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
   
	if c.Jar != nil {
   
		for _, cookie := range c.Jar.Cookies(req.URL) {
   
			req.AddCookie(cookie)
		}
	}
	resp, didTimeout, err = send(req, c.transport(), deadline)
	if err != nil {
   
		return nil, didTimeout, err
	}
	if c.Jar != nil {
   
		if rc := resp.Cookies(); len(rc) > 0 {
   
			c.Jar.SetCookies(req.URL, rc)
		}
	}
	return resp, nil, nil
}

另外, 我们还需要关注c.transport()的调用。如果用户未对http.Client指定Transport则会使用go默认的DefaultTransport。

该Transport实现RoundTripper接口。在go中RoundTripper的定义为“执行单个HTTP事务的能力,获取给定请求的响应”。

func (c *Client) transport() RoundTripper {
   
	if c.Transport != nil {
   
		return c.Transport
	}
	return DefaultTransport
}

send

send函数会检查request的URL,以及参数的rt, 和header值。如果URL和rt为nil则直接返回错误。同时, 如果请求中设置了用户信息, 还会检查并设置basic的验证头信息,最后调用rt.RoundTrip得到请求的响应。

func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
   
	req := ireq // req is either the original request, or a modified fork
	// ...此处省略代码...
	if u := req.URL.User; u != nil && req.Header.Get("Authorization") == "" {
   
		username := u.Username()
		password, _ := u.Password()
		forkReq()
		req.Header = cloneOrMakeHeader(ireq.Header)
		req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
	}

	if !deadline.IsZero() {
   
		forkReq()
	}
	stopTimer, didTimeout := setRequestCancel(req, rt, deadline)

	resp, err = rt.RoundTrip(req)
	if err != nil {
   
        // ...此处省略代码...
		return nil, didTimeout, err
	}
	// ...此处省略代码...
	return resp, nil, nil
}

(*Transport).RoundTrip

(*Transport).RoundTrip的逻辑很简单,它会调用(*Transport).roundTrip方法,因此本节实际上是对(*Transport).roundTrip方法的分析。

func (t *Transport) RoundTrip(req *Request) (*Response, error) {
   
	return t.roundTrip(req)
}
func (t *Transport) roundTrip(req *Request) (*Response, error) {
   
	// ...此处省略校验header头和headervalue的代码以及其他代码...

	for {
   
		select {
   
		case <-ctx.Done():
			req.closeBody()
			return nil, ctx.Err()
		default:
		}

		// treq gets modified by roundTrip, so we need to recreate for each retry.
		treq := &transportRequest{
   Request: req, trace: trace}
		cm, err := t.connectMethodForRequest(treq)
		// ...此处省略代码...
		pconn, err := t.getConn(treq, cm)
		if err != nil {
   
			t.setReqCanceler(req, nil)
			req.closeBody()
			return nil, err
		}

		var resp *Response
		if pconn.alt != nil {
   
			// HTTP/2 path.
			t.setReqCanceler(req, nil) // not cancelable with CancelRequest
			resp, err = pconn.alt.RoundTrip(req)
		} else {
   
			resp, err = pconn.roundTrip(treq)
		}
		if err == nil {
   
			return resp, ni
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值