Golang http之client源码详解

转自 Golang http之server源码详解-蒲公英云

仅做个人备份

http客户端介绍

 
  1. http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。本文章主要介绍Golang中http客户端的相关源码,源码主要在net/http/client.go中。源码版本号为1.10.3

一个简单的http 客户端请求例子:

 
  1. package main
  2. import (
  3. "log"
  4. "fmt"
  5. "net/http"
  6. "io/ioutil"
  7. )
  8. func main() {
  9. resp,err := http.Get("http://www.baidu.com")
  10. if err != nil {
  11. log.Fatal(err)
  12. }
  13. d,err := ioutil.ReadAll(resp.Body)
  14. if err != nil {
  15. log.Fatal(err)
  16. }
  17. resp.Body.Close()
  18. fmt.Println(string(d))
  19. }
  20. http客户端直接调用http包的Get获取相关请求数据。如果客户端在请求时需要设置header,则需要使用NewRequest和 DefaultClient.Do。我们看一下设置Header的操作例子
  21. package main
  22. import (
  23. "fmt"
  24. "net/http"
  25. "log"
  26. "io/ioutil"
  27. )
  28. func main() {
  29. req,err := http.NewRequest("GET","http://www.baidu.com",nil)
  30. if err != nil {
  31. log.Fatal(err)
  32. }
  33. req.Header.Set("key","value")
  34. resp ,err := http.DefaultClient.Do(req)
  35. if err != nil {
  36. log.Fatal(err)
  37. }
  38. byts,err := ioutil.ReadAll(resp.Body)
  39. defer resp.Body.Close()
  40. if err != nil {
  41. log.Fatal(err)
  42. }
  43. fmt.Println(string(byts))
  44. }

源码分析

 
  1. **Client结构体**
  2. type Client struct {
  3. Transport RoundTripper
  4. CheckRedirect func(req *Request, via []*Request) error
  5. Jar CookieJar
  6. Timeout time.Duration
  7. }

我们看到Client的结构体非常简单,只有几个字段。Client表示一个HTTP客户端,它的默认值是DefaultClient会默认使用DefaultTransport的可用客户端。

Transport表示HTTP事务,用于处理客户端的请求并等待服务端的响应

CheckRedirect 用于指定处理重定向的策略

Jar指定cookie的jar

Timeout 指定客户端请求的最大超时时间,该超时时间包括连接,任何的重定向以及读取响

应体的时间。如果服务器已经返回响应信息该计时器仍然运行,并将终端Response.Body的读取。如果Timeout为0则意味着没有超时

RoundTripper接口

 
  1. type RoundTripper interface {
  2. RoundTrip(*Request) (*Response, error)
  3. }

RoundTripper表示执行单个HTTP事务的接口,必须是并发安全的。它的相关源码我们在前面已经介绍过了,可以参考以下地址

https://blog.csdn.net/skh2015java/article/details/89340215

我们通过一些常用的http包对外提供的方法来窥探http客户端的请求流程和机制。

Get方法

 
  1. func Get(url string) (resp *Response, err error) {
  2. return DefaultClient.Get(url)
  3. }
  4. var DefaultClient = &Client{}

Get方法使用的是http包对外提供的默认客户端DefaultClient

DefaultClient的Get方法如下

 
  1. func (c *Client) Get(url string) (resp *Response, err error) {
  2. req, err := NewRequest("GET", url, nil)
  3. if err != nil {
  4. return nil, err
  5. }
  6. return c.Do(req)
  7. }

看到首先创建了Request结构体req,指定了请求的Method和url。

 
  1. func (c *Client) Do(req *Request) (*Response, error) {
  2. //如果请求的URL为nil,则关闭请求体,并且返回error
  3. if req.URL == nil {
  4. req.closeBody()
  5. return nil, errors.New("http: nil Request.URL")
  6. }
  7. var (
  8. deadline = c.deadline()
  9. reqs []*Request
  10. resp *Response
  11. copyHeaders = c.makeHeadersCopier(req) //拷贝一份headers
  12. reqBodyClosed = false // have we closed the current req.Body?
  13. // Redirect behavior:
  14. redirectMethod string
  15. includeBody bool
  16. )
  17. //错误处理函数
  18. uerr := func(err error) error {
  19. // the body may have been closed already by c.send()
  20. if !reqBodyClosed {
  21. req.closeBody()
  22. }
  23. method := valueOrDefault(reqs[0].Method, "GET")
  24. var urlStr string
  25. if resp != nil && resp.Request != nil {
  26. urlStr = resp.Request.URL.String()
  27. } else {
  28. urlStr = req.URL.String()
  29. }
  30. return &url.Error{
  31. Op: method[:1] + strings.ToLower(method[1:]),
  32. URL: urlStr,
  33. Err: err,
  34. }
  35. }
  36. for {
  37. // For all but the first request, create the next
  38. // request hop and replace req.
  39. /*
  40. 对于非第一个请求 ,创建下一个hop并且替换req
  41. */
  42. if len(reqs) > 0 { //有重定向的情况
  43. loc := resp.Header.Get("Location") //获取响应resp的Header中的Location对应的值
  44. if loc == "" {
  45. resp.closeBody()
  46. return nil, uerr(fmt.Errorf("%d response missing Location header", resp.StatusCode))
  47. }
  48. u, err := req.URL.Parse(loc) //将loc解析成URL
  49. if err != nil {
  50. resp.closeBody()
  51. return nil, uerr(fmt.Errorf("failed to parse Location header %q: %v", loc, err))
  52. }
  53. host := ""
  54. if req.Host != "" && req.Host != req.URL.Host { //解析host
  55. // If the caller specified a custom Host header and the
  56. // redirect location is relative, preserve the Host header
  57. // through the redirect. See issue #22233.
  58. if u, _ := url.Parse(loc); u != nil && !u.IsAbs() {
  59. host = req.Host
  60. }
  61. }
  62. ireq := reqs[0]
  63. //获取重定向的Request req
  64. req = &Request{
  65. Method: redirectMethod,
  66. Response: resp,
  67. URL: u,
  68. Header: make(Header),
  69. Host: host,
  70. Cancel: ireq.Cancel,
  71. ctx: ireq.ctx,
  72. }
  73. if includeBody && ireq.GetBody != nil {
  74. req.Body, err = ireq.GetBody()
  75. if err != nil {
  76. resp.closeBody()
  77. return nil, uerr(err)
  78. }
  79. req.ContentLength = ireq.ContentLength
  80. }
  81. // Copy original headers before setting the Referer,
  82. // in case the user set Referer on their first request.
  83. // If they really want to override, they can do it in
  84. // their CheckRedirect func.
  85. copyHeaders(req)
  86. // Add the Referer header from the most recent
  87. // request URL to the new one, if it's not https->http:
  88. /*
  89. 如果不是https-> http,请将最新请求URL中的Referer标头添加到新标头中:
  90. */
  91. if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL); ref != "" {
  92. req.Header.Set("Referer", ref)
  93. }
  94. err = c.checkRedirect(req, reqs)
  95. // Sentinel error to let users select the
  96. // previous response, without closing its
  97. // body. See Issue 10069.
  98. if err == ErrUseLastResponse {
  99. return resp, nil
  100. }
  101. // Close the previous response's body. But
  102. // read at least some of the body so if it's
  103. // small the underlying TCP connection will be
  104. // re-used. No need to check for errors: if it
  105. // fails, the Transport won't reuse it anyway.
  106. const maxBodySlurpSize = 2 << 10
  107. if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
  108. io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize)
  109. }
  110. resp.Body.Close()
  111. if err != nil {
  112. // Special case for Go 1 compatibility: return both the response
  113. // and an error if the CheckRedirect function failed.
  114. // See https://golang.org/issue/3795
  115. // The resp.Body has already been closed.
  116. ue := uerr(err)
  117. ue.(*url.Error).URL = loc
  118. return resp, ue
  119. }
  120. }
  121. reqs = append(reqs, req) //将req写入到reps中
  122. var err error
  123. var didTimeout func() bool
  124. //发送请求到服务端,并获取响应信息resp
  125. if resp, didTimeout, err = c.send(req, deadline); err != nil {
  126. // c.send() always closes req.Body
  127. reqBodyClosed = true
  128. if !deadline.IsZero() && didTimeout() { //已超时
  129. err = &httpError{
  130. err: err.Error() + " (Client.Timeout exceeded while awaiting headers)",
  131. timeout: true,
  132. }
  133. }
  134. return nil, uerr(err)
  135. }
  136. var shouldRedirect bool
  137. //根据请求的Method,响应的消息resp和 第一次请求req获取是否需要重定向,重定向的方法,重定向时是否包含body
  138. redirectMethod, shouldRedirect, includeBody = redirectBehavior(req.Method, resp, reqs[0])
  139. if !shouldRedirect { //不用重发,则返回
  140. return resp, nil
  141. }
  142. req.closeBody()
  143. }
  144. }

Do发送一个HTTP请求并且返回一个客户端响应,遵循客户端上配置的策略(例如redirects,cookie,auth),如果服务端回复的Body非空,则该Body需要客户端关闭,否则会对”keep-alive”请求中的持久化TCP连接可能不会被复用。

如果服务端回复一个重定向,客户端首先会调用CheckRedirect函数来确定是否遵循重定向。

如果允许,一个301,302或者303重定向会导致后续请求使用HTTP的GET方法。

该方法的大致流程如下:

1.首先会进行相关参数的校验

2.参数校验通过后,会调用send方法来发送客户端请求,并获取服务端的响应信息。

3.如果服务端回复的不需要重定向,则将该响应resp返回

4.如果服务端回复的需要重定向,则获取重定向的Request,并进行重定向校验

5.重定向校验通过后,会继续调用send方法来发送重定向的请求。

6.不需要重定向时返回从服务端响应的结果resp。

send方法

 
  1. func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
  2. //如果Jar不为nil,则将Jar中的Cookie添加到请求中
  3. if c.Jar != nil {
  4. for _, cookie := range c.Jar.Cookies(req.URL) {
  5. req.AddCookie(cookie)
  6. }
  7. }
  8. //发送请求到服务端,并返回从服务端读取到的response信息resp
  9. resp, didTimeout, err = send(req, c.transport(), deadline)
  10. if err != nil {
  11. return nil, didTimeout, err
  12. }
  13. if c.Jar != nil {
  14. if rc := resp.Cookies(); len(rc) > 0 {
  15. c.Jar.SetCookies(req.URL, rc)
  16. }
  17. }
  18. return resp, nil, nil
  19. }
  20. send方法主要调用send函数将请求发送到Transport中,并返回response。
  21. 如果Jar不为nil,则将Jar中的Cookie添加到请求中,并调用send函数将请求发送到服务端,并返回从服务端读取到的response信息resp。需要注意的是如果用户自定义了Transport则用用户自定义的,如果没有则用默认的DefaultTransport。

send函数

 
  1. func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
  2. req := ireq // req is either the original request, or a modified fork req是原始的请求或者一个拷贝
  3. /*
  4. 条件判断:
  5. 如果RoundTripper为nil 或者请求的URL为nil 或者 请求的RequestURI为空,则关闭请求体,返回error
  6. */
  7. .......
  8. // forkReq forks req into a shallow clone of ireq the first
  9. // time it's called.
  10. /*
  11. 第一次调用时,将req转换为ireq的拷贝
  12. */
  13. forkReq := func() {
  14. if ireq == req {
  15. req = new(Request)
  16. *req = *ireq // shallow clone
  17. }
  18. }
  19. // Most the callers of send (Get, Post, et al) don't need
  20. // Headers, leaving it uninitialized. We guarantee to the
  21. // Transport that this has been initialized, though.
  22. /*
  23. 由于大多数的调用(Get,Post等)都不需要Headers,而是将其保留成为初始化状态。不过我们传输到Transport需要保证其被初始化,所以这里将
  24. 没有Header为nil的进行初始化
  25. 如果请求头为nil
  26. */
  27. if req.Header == nil {
  28. forkReq()
  29. req.Header = make(Header)
  30. }
  31. /*
  32. 如果URL中协议用户和密码信息,并且请求头的Authorization为空,我们需要设置Header的Authorization
  33. */
  34. if u := req.URL.User; u != nil && req.Header.Get("Authorization") == "" {
  35. username := u.Username()
  36. password, _ := u.Password()
  37. forkReq()
  38. req.Header = cloneHeader(ireq.Header)
  39. req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
  40. }
  41. //如果设置了超时时间,则需要调用forkReq,来确保req是ireq的拷贝,而不是执行同一地址的指针
  42. if !deadline.IsZero() {
  43. forkReq()
  44. }
  45. //根据deadline设置超时
  46. stopTimer, didTimeout := setRequestCancel(req, rt, deadline)
  47. //调用RoundTrip完成一个HTTP事务,并返回一个resp
  48. resp, err = rt.RoundTrip(req)
  49. if err != nil { //如果err不为nil
  50. stopTimer() //取消监听超时
  51. if resp != nil {
  52. log.Printf("RoundTripper returned a response & error; ignoring response")
  53. }
  54. if tlsErr, ok := err.(tls.RecordHeaderError); ok {
  55. // If we get a bad TLS record header, check to see if the
  56. // response looks like HTTP and give a more helpful error.
  57. // See golang.org/issue/11111.
  58. if string(tlsErr.RecordHeader[:]) == "HTTP/" {
  59. err = errors.New("http: server gave HTTP response to HTTPS client")
  60. }
  61. }
  62. return nil, didTimeout, err
  63. }
  64. if !deadline.IsZero() { //如果设置了超时,则将Body转成cancelTimerBody
  65. resp.Body = &cancelTimerBody{
  66. stop: stopTimer,
  67. rc: resp.Body,
  68. reqDidTimeout: didTimeout,
  69. }
  70. }
  71. return resp, nil, nil
  72. }

该函数的主要作用是调用RoundTrip完成一个HTTP事务,并返回一个resp。

Post和PostForm方法

 
  1. func Post(url string, contentType string, body io.Reader) (resp *Response, err error) {
  2. return DefaultClient.Post(url, contentType, body)
  3. }
  4. func (c *Client) Post(url string, contentType string, body io.Reader) (resp *Response, err error) {
  5. req, err := NewRequest("POST", url, body)
  6. if err != nil {
  7. return nil, err
  8. }
  9. req.Header.Set("Content-Type", contentType)
  10. return c.Do(req)
  11. }
  12. func PostForm(url string, data url.Values) (resp *Response, err error) {
  13. return DefaultClient.PostForm(url, data)
  14. }
  15. func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
  16. return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  17. }

看到Post和PostForm最终都是调用默认客户端DefaultClient的Post方法,然后调用Do来处理请求并获得相应信息。

#

Request结构体和如何设置Header

 
  1. type Request struct {
  2. //HTTP的方法(GET、POST、PUT等)
  3. Method string
  4. /*
  5. 请求解析后的url
  6. */
  7. URL *url.URL
  8. Proto string // "HTTP/1.0" 协议版本
  9. ProtoMajor int // 1 主版本号
  10. ProtoMinor int // 0 次版本号
  11. //请求到服务器携带的请求头信息,用map存储
  12. Header Header
  13. //请求的消息体,HTTP客户端的Transport负责调用Close方法关闭
  14. Body io.ReadCloser
  15. /*
  16. GetBody 定义一个可选的方法用来返回Body的副本
  17. 当客户端的请求被多次重定向的时候,会用到该函数
  18. */
  19. GetBody func() (io.ReadCloser, error)
  20. /*
  21. ContentLength 存储消息体的字节长度
  22. 如果为 -1 则表示消息长度未知
  23. */
  24. ContentLength int64
  25. /*
  26. TransferEncoding列出从最外层到最内层的传输编码。
  27. 空列表表示“identity”编码。 TransferEncoding通常可以忽略; 发送和接收请求时,会根据需要自动添加和删除分块编码。
  28. */
  29. TransferEncoding []string
  30. /*
  31. Close对于服务端是 回复此请求后是否关闭连接
  32. 对于客户端发送此请求并读取服务端的响应后是否关闭连接
  33. 对于服务器请求,HTTP服务器自动处理此请求,处理程序不需要此字段。
  34. 对于客户端请求,设置此字段可防止在对相同主机的请求之间重复使用TCP连接,就像设置了Transport.DisableKeepAlives一样。
  35. */
  36. Close bool
  37. /*
  38. 主机地址
  39. 对于服务器请求,Host指定要在其上查找URL的主机
  40. 对于客户端请求,Host可以选择覆盖要发送的Host头。 如果为空,则Request.Write方法使用URL.Host的值。 主机可能包含国际域名。
  41. */
  42. Host string
  43. /*
  44. 储存解析后的表单数据,包括URL字段查询的参数和 POST或PUT表单数据
  45. 该字段仅在调用ParseForm后可用。 HTTP客户端忽略Form并使用Body。
  46. */
  47. Form url.Values
  48. /*
  49. PostForm储存了 从POST,PATCH,PUT解析后表单数据
  50. 该字段仅在调用ParseForm后可用,HTTP客户端会忽略PostForm而是使用Body
  51. */
  52. PostForm url.Values
  53. /*
  54. MultipartForm是解析的多部分表单,包括文件上载。 该字段仅在调用ParseMultipartForm后可用。
  55. HTTP客户端忽略MultipartForm而使用Body
  56. */
  57. MultipartForm *multipart.Form
  58. /*
  59. 指定请求体发送之后发送的额外请求头
  60. */
  61. Trailer Header
  62. /*
  63. RemoteAddr允许HTTP服务器和其他软件记录发送请求的网络地址,通常用于记录。 ReadRequest未填写此字段,并且没有已定义的格式。
  64. 在调用处理程序之前,此程序包中的HTTP服务器将RemoteAddr设置为“IP:port”地址。 HTTP客户端忽略此字段。
  65. */
  66. RemoteAddr string
  67. /*
  68. RequestURI是客户端发送到服务端的未经解析的Request-URI
  69. */
  70. RequestURI string
  71. /*
  72. TLS允许HTTP服务器和其他软件记录有关收到请求的TLS连接的信息。 ReadRequest未填写此字段。
  73. 此包中的HTTP服务器在调用处理程序之前为启用TLS的连接设置字段; 否则它会离开现场零。 HTTP客户端忽略此字段。
  74. */
  75. TLS *tls.ConnectionState
  76. /*
  77. 用于通知客户端的请求应该被取消.不是所有的RoundTripper都支持取消
  78. 此字段不使用服务端的请求
  79. */
  80. Cancel <-chan struct{}
  81. /*
  82. 重定向时使用该字段
  83. */
  84. Response *Response
  85. /*
  86. 客户端或者服务端的上下文。只有通过WithContext来改变该上下文
  87. */
  88. ctx context.Context //请求的上下文
  89. }

Request表示一个HTTP的请求,用于客户端发送,服务端接收。

Method表示HTTP的方法(常用的如:GET、POST、PUT、DELETE等)

Header请求到服务器时携带的请求头信息,用map存储。

Header的格式如下:

type Header map[string][]string

Header用 key-value键值对 表示HTTP header

操作Header的常用方法如下:

 
  1. //添加value到指定key,如果value已存在,则追加,因为value是[]string的格式。即一个// key可以对应多个value
  2. func (h Header) Add(key, value string) {
  3. textproto.MIMEHeader(h).Add(key, value)
  4. }
  5. //设置key的value,value会替换key对应的已存在的value
  6. func (h Header) Set(key, value string) {
  7. textproto.MIMEHeader(h).Set(key, value)
  8. }
  9. //获取与key关联的第一个value值,如果没有则返回""(空字符串),
  10. func (h Header) Get(key string) string {
  11. return textproto.MIMEHeader(h).Get(key)
  12. }
  13. //删除Header中指定的key
  14. func (h Header) Del(key string) {
  15. textproto.MIMEHeader(h).Del(key)
  16. }

以上就是http客户端请求的大致流程及相关方法和结构体的介绍,如有问题还请指正

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值