Golang http之client源码详解

  • Post author:
  • Post category:golang


转自

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客户端请求的大致流程及相关方法和结构体的介绍,如有问题还请指正