$ go version
go version go1.20.5 darwin/arm64
如果一个文件一个文件挨着看未免也太枯燥了,并且没有和实践结合,感觉看了也体会不深。这里从发起一个请求到最后收到应答的整个流程,跟随一个 http.Request 的生命周期去探索相关的源码。
http.Client
开始发请求之前先看一下 http.Client 结构体
1// src/net/http/client.go#L29
2// A Client is an HTTP client. Its zero value (DefaultClient) is a
3// usable client that uses DefaultTransport.
4//
5// The Client's Transport typically has internal state (cached TCP
6// connections), so Clients should be reused instead of created as
7// needed. Clients are safe for concurrent use by multiple goroutines.
8//
9// A Client is higher-level than a RoundTripper (such as Transport)
10// and additionally handles HTTP details such as cookies and
11// redirects.
12//
13// When following redirects, the Client will forward all headers set on the
14// initial Request except:
15//
16// • when forwarding sensitive headers like "Authorization",
17// "WWW-Authenticate", and "Cookie" to untrusted targets.
18// These headers will be ignored when following a redirect to a domain
19// that is not a subdomain match or exact match of the initial domain.
20// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"
21// will forward the sensitive headers, but a redirect to "bar.com" will not.
22//
23// • when forwarding the "Cookie" header with a non-nil cookie Jar.
24// Since each redirect may mutate the state of the cookie jar,
25// a redirect may possibly alter a cookie set in the initial request.
26// When forwarding the "Cookie" header, any mutated cookies will be omitted,
27// with the expectation that the Jar will insert those mutated cookies
28// with the updated values (assuming the origin matches).
29// If Jar is nil, the initial cookies are forwarded without change.
30type Client struct {
31 // Transport specifies the mechanism by which individual
32 // HTTP requests are made.
33 // If nil, DefaultTransport is used.
34 Transport RoundTripper
35
36 // CheckRedirect specifies the policy for handling redirects.
37 // If CheckRedirect is not nil, the client calls it before
38 // following an HTTP redirect. The arguments req and via are
39 // the upcoming request and the requests made already, oldest
40 // first. If CheckRedirect returns an error, the Client's Get
41 // method returns both the previous Response (with its Body
42 // closed) and CheckRedirect's error (wrapped in a url.Error)
43 // instead of issuing the Request req.
44 // As a special case, if CheckRedirect returns ErrUseLastResponse,
45 // then the most recent response is returned with its body
46 // unclosed, along with a nil error.
47 //
48 // If CheckRedirect is nil, the Client uses its default policy,
49 // which is to stop after 10 consecutive requests.
50 CheckRedirect func(req *Request, via []*Request) error
51
52 // Jar specifies the cookie jar.
53 //
54 // The Jar is used to insert relevant cookies into every
55 // outbound Request and is updated with the cookie values
56 // of every inbound Response. The Jar is consulted for every
57 // redirect that the Client follows.
58 //
59 // If Jar is nil, cookies are only sent if they are explicitly
60 // set on the Request.
61 Jar CookieJar
62
63 // Timeout specifies a time limit for requests made by this
64 // Client. The timeout includes connection time, any
65 // redirects, and reading the response body. The timer remains
66 // running after Get, Head, Post, or Do return and will
67 // interrupt reading of the Response.Body.
68 //
69 // A Timeout of zero means no timeout.
70 //
71 // The Client cancels requests to the underlying Transport
72 // as if the Request's Context ended.
73 //
74 // For compatibility, the Client will also use the deprecated
75 // CancelRequest method on Transport if found. New
76 // RoundTripper implementations should use the Request's Context
77 // for cancellation instead of implementing CancelRequest.
78 Timeout time.Duration
79}
80
81// DefaultClient is the default Client and is used by Get, Head, and Post.
82var DefaultClient = &Client{}
83
84// RoundTripper is an interface representing the ability to execute a
85// single HTTP transaction, obtaining the Response for a given Request.
86//
87// A RoundTripper must be safe for concurrent use by multiple
88// goroutines.
89type RoundTripper interface {
90 // RoundTrip executes a single HTTP transaction, returning
91 // a Response for the provided Request.
92 //
93 // RoundTrip should not attempt to interpret the response. In
94 // particular, RoundTrip must return err == nil if it obtained
95 // a response, regardless of the response's HTTP status code.
96 // A non-nil err should be reserved for failure to obtain a
97 // response. Similarly, RoundTrip should not attempt to
98 // handle higher-level protocol details such as redirects,
99 // authentication, or cookies.
100 //
101 // RoundTrip should not modify the request, except for
102 // consuming and closing the Request's Body. RoundTrip may
103 // read fields of the request in a separate goroutine. Callers
104 // should not mutate or reuse the request until the Response's
105 // Body has been closed.
106 //
107 // RoundTrip must always close the body, including on errors,
108 // but depending on the implementation may do so in a separate
109 // goroutine even after RoundTrip returns. This means that
110 // callers wanting to reuse the body for subsequent requests
111 // must arrange to wait for the Close call before doing so.
112 //
113 // The Request's URL and Header fields must be initialized.
114 RoundTrip(*Request) (*Response, error)
115}
简单翻译一下注释。
一个 Client 就是一个 http 客户端,它的零值,或者叫默认的空值 (http.DefaultClient) 是一个可用的使用 DefaultTransport 的客户端(那 transport 是什么呢,后面会提到)。 Client 的 Transport 是有内部状态的,如缓存的 tcp 连接,所以 Clients 应该被重复使用而不是每次需要的时候都新创建,Clients 对多 goroutine 并发使用是安全的。 一个 Client 是比 RoundTripper 更高的层级(就是说 Client 封装了 RoundTripper),并且额外处理了 http 协议相关的细节如 cookies 和 重定向。 当跟随重定向时,Client 会转发原来请求上设置的所有 headers,除了以下几条
- 当转发敏感 headers 像 “Authorization”, “WWW-Authenticate” and “Cookie” 去不信任的目标时。在跟随重定向到不是现在域名也不是现在域名的子域名时,这些 headers 会被忽略。比如,从 “foo.com” 重定向到 “foo.com” 和 “sub.foo.com” 时都会带上敏感的 headers,但重定向到 “bar.com” 不会。
- 当 cookie jar 字段不为空时,转发 Cookie header。因为每次重定向都有可能会改变 cookie jar 的状态,重定向可以修改原来请求里的 cookie 值。所以重定向转发 Cookie header 时,任何改动的值都会丢掉,因为会被新设置的值覆盖。如果 cookie jar 是 nil,那么初始请求的 cookie 将会原封不动的转发。
Transport 字段,Transport 指明了每个独特的 http 请求使用的机制,如果是 nil 就使用 DefaultTransport。(看后面的 RoundTripper 会更清楚这到底是干啥的)
CheckRedirect 字段,CheckRedirect 指明了如何对待重定向,如果 CheckRedirect 不是 nil,那么客户端会在每次重定向之前先调用它,参数 req 是即将发出的这个新的重定向后的请求,参数 via 是重定向之前已经发出的一串请求,并且越早发出的在越前面。如果 CheckRedirect 返回了 error,那 Client 的 Get 方法会返回之前请求的响应,并且响应的 body 是已经关闭的,和一个包装成 url.Error 的 CheckRedirect 的 error。一个比较特殊的情况是,如果 CheckRedirect 返回了 ErrUseLastResponse,那么最近的请求的响应会被返回,并且 body 没有关闭,并且没有 error。如果 CheckRedirect 是 nil,那么 Client 会使用默认的策略,也就是最多重定向 10 次。 Jar 字段,Jar 就是 cookie jar,用于保存 cookie 的状态,每次请求都会带上响应的 cookie,每次响应也会更新状态,并且这个 Client 的每次重定向也会带上 cookie。如果 Jar 是 nil,那么只有在请求里显式设置的 cookie 会被发送。 Timeout 字段,Timeout 指明了这个 Client 发送请求的时间限制,包括连接时间,任何重定向和读取响应体的时间。并且这个定时器会在 Get, Head, Post, Do 这些方法返回后持续运行,来打断对 Response.Body 的读取(因为前面说了读取响应体的时间也算在超时内)。如果 Timeout 设置为 0 就是没有超时限制。当 Request 的 Context 结束时,Client 会取消底层的 Transport 的请求。为了兼容性,Client 也会使用 Transport 上的已经弃用的 CancelRequest 方法,如果能找到有这个方法的话,新的 RoundTripper 实现应该使用 Request 的 Context 来处理请求取消而不是去实现 CancelRequest。
RoundTripper 是一个接口,代表了执行单次 http 请求,拿到对应响应的能力。一个 RoundTripper 必须是对多 goroutine 使用是并发安全的。 RoundTrip 方法,执行一个单次的 http 请求,返回它的响应。RoundTrip 不应该去尝试解释响应,特别地,只要成功取到了对应的响应,不管这个响应的 http 状态码是多少,RoundTrip 都必须返回 err == nil,一个非空的 err 应该保留来描述获取响应的失败。相似地,RoundTrip 不应该尝试去处理高层的协议细节,比如重定向,授权和 cookie 等。 RoundTrip 不应该修改请求,除了消费和关闭请求的 body。RoundTrip 可能会在多个不同的 goroutine 里读取请求的字段,所以调用者不应该修改或者复用请求直到请求对应的响应的 body 被关闭。 RoundTrip 必须总是关闭请求的 body,就算是有错误,但是基于不同的实现,这个操作甚至可能会在 RoundTrip 返回后在不同的 goroutine 里完成,这就是说如果调用者想要复用请求的 body 来做后续的请求,应该等到 body 关闭之后。 请求的 URL 和 Header 字段必须被初始化。
算了不想翻译了,在 AI 发展得这么好的今天,对翻译完全是降维打击,特别是这种没有很深语境的技术文档,就没必要浪费人力来做这种事情了。 总的来说,RoundTripper 负责把单个 http.Request 转成 http.Response,但不能去关注上层的协议细节,Client 才应该来处理这些细节。
http.NewRequest
1// src/net/http/request.go#L837
2// NewRequest wraps NewRequestWithContext using context.Background.
3func NewRequest(method, url string, body io.Reader) (*Request, error) {
4 return NewRequestWithContext(context.Background(), method, url, body)
5}
6
7// NewRequestWithContext returns a new Request given a method, URL, and
8// optional body.
9//
10// If the provided body is also an io.Closer, the returned
11// Request.Body is set to body and will be closed by the Client
12// methods Do, Post, and PostForm, and Transport.RoundTrip.
13//
14// NewRequestWithContext returns a Request suitable for use with
15// Client.Do or Transport.RoundTrip. To create a request for use with
16// testing a Server Handler, either use the NewRequest function in the
17// net/http/httptest package, use ReadRequest, or manually update the
18// Request fields. For an outgoing client request, the context
19// controls the entire lifetime of a request and its response:
20// obtaining a connection, sending the request, and reading the
21// response headers and body. See the Request type's documentation for
22// the difference between inbound and outbound request fields.
23//
24// If body is of type *bytes.Buffer, *bytes.Reader, or
25// *strings.Reader, the returned request's ContentLength is set to its
26// exact value (instead of -1), GetBody is populated (so 307 and 308
27// redirects can replay the body), and Body is set to NoBody if the
28// ContentLength is 0.
29func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
30 if method == "" {
31 // We document that "" means "GET" for Request.Method, and people have
32 // relied on that from NewRequest, so keep that working.
33 // We still enforce validMethod for non-empty methods.
34 method = "GET"
35 }
36 if !validMethod(method) {
37 return nil, fmt.Errorf("net/http: invalid method %q", method)
38 }
39 if ctx == nil {
40 return nil, errors.New("net/http: nil Context")
41 }
42 u, err := urlpkg.Parse(url)
43 if err != nil {
44 return nil, err
45 }
46 rc, ok := body.(io.ReadCloser)
47 if !ok && body != nil {
48 rc = io.NopCloser(body)
49 }
50 // The host's colon:port should be normalized. See Issue 14836.
51 u.Host = removeEmptyPort(u.Host)
52 req := &Request{
53 ctx: ctx,
54 Method: method,
55 URL: u,
56 Proto: "HTTP/1.1",
57 ProtoMajor: 1,
58 ProtoMinor: 1,
59 Header: make(Header),
60 Body: rc,
61 Host: u.Host,
62 }
63 if body != nil {
64 switch v := body.(type) {
65 case *bytes.Buffer:
66 req.ContentLength = int64(v.Len())
67 buf := v.Bytes()
68 req.GetBody = func() (io.ReadCloser, error) {
69 r := bytes.NewReader(buf)
70 return io.NopCloser(r), nil
71 }
72 case *bytes.Reader:
73 req.ContentLength = int64(v.Len())
74 snapshot := *v
75 req.GetBody = func() (io.ReadCloser, error) {
76 r := snapshot
77 return io.NopCloser(&r), nil
78 }
79 case *strings.Reader:
80 req.ContentLength = int64(v.Len())
81 snapshot := *v
82 req.GetBody = func() (io.ReadCloser, error) {
83 r := snapshot
84 return io.NopCloser(&r), nil
85 }
86 default:
87 // This is where we'd set it to -1 (at least
88 // if body != NoBody) to mean unknown, but
89 // that broke people during the Go 1.8 testing
90 // period. People depend on it being 0 I
91 // guess. Maybe retry later. See Issue 18117.
92 }
93 // For client requests, Request.ContentLength of 0
94 // means either actually 0, or unknown. The only way
95 // to explicitly say that the ContentLength is zero is
96 // to set the Body to nil. But turns out too much code
97 // depends on NewRequest returning a non-nil Body,
98 // so we use a well-known ReadCloser variable instead
99 // and have the http package also treat that sentinel
100 // variable to mean explicitly zero.
101 if req.GetBody != nil && req.ContentLength == 0 {
102 req.Body = NoBody
103 req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
104 }
105 }
106
107 return req, nil
108}
注释就已经很清楚了,做了简单的参数检查,然后对 body 进行了封装增加 Close 方法(因为 RoundTripper 必须要关闭请求的 body,所以如果 body 没有 Close 方法也要添加一个啥都不做的 Close 方法),再根据 body 的不同类型去设置 GetBody 和 ContentLength,需要注意的是设置 GetBody 这里,把 body 备份了,在后面恢复 body 的时候会用到。
http.Request
因为这个结构体在在 client 和 server 端都会用到,字段挺多的,就不用特别看了,在后续函数里涉及到的时候再看,重点还是在后面的 RoundTripper
Client.Do
1// src/net/http/client.go#L544
2// Do sends an HTTP request and returns an HTTP response, following
3// policy (such as redirects, cookies, auth) as configured on the
4// client.
5//
6// An error is returned if caused by client policy (such as
7// CheckRedirect), or failure to speak HTTP (such as a network
8// connectivity problem). A non-2xx status code doesn't cause an
9// error.
10//
11// If the returned error is nil, the Response will contain a non-nil
12// Body which the user is expected to close. If the Body is not both
13// read to EOF and closed, the Client's underlying RoundTripper
14// (typically Transport) may not be able to re-use a persistent TCP
15// connection to the server for a subsequent "keep-alive" request.
16//
17// The request Body, if non-nil, will be closed by the underlying
18// Transport, even on errors.
19//
20// On error, any Response can be ignored. A non-nil Response with a
21// non-nil error only occurs when CheckRedirect fails, and even then
22// the returned Response.Body is already closed.
23//
24// Generally Get, Post, or PostForm will be used instead of Do.
25//
26// If the server replies with a redirect, the Client first uses the
27// CheckRedirect function to determine whether the redirect should be
28// followed. If permitted, a 301, 302, or 303 redirect causes
29// subsequent requests to use HTTP method GET
30// (or HEAD if the original request was HEAD), with no body.
31// A 307 or 308 redirect preserves the original HTTP method and body,
32// provided that the Request.GetBody function is defined.
33// The NewRequest function automatically sets GetBody for common
34// standard library body types.
35//
36// Any returned error will be of type *url.Error. The url.Error
37// value's Timeout method will report true if the request timed out.
38func (c *Client) Do(req *Request) (*Response, error) {
39 return c.do(req)
40}
41
42var testHookClientDoResult func(retres *Response, reterr error)
43
44func (c *Client) do(req *Request) (retres *Response, reterr error) {
45 if testHookClientDoResult != nil {
46 defer func() { testHookClientDoResult(retres, reterr) }()
47 }
48 if req.URL == nil {
49 req.closeBody()
50 return nil, &url.Error{
51 Op: urlErrorOp(req.Method),
52 Err: errors.New("http: nil Request.URL"),
53 }
54 }
55
56 var (
57 deadline = c.deadline()
58 reqs []*Request
59 resp *Response
60 copyHeaders = c.makeHeadersCopier(req)
61 reqBodyClosed = false // have we closed the current req.Body?
62
63 // Redirect behavior:
64 redirectMethod string
65 includeBody bool
66 )
67 uerr := func(err error) error {
68 // the body may have been closed already by c.send()
69 if !reqBodyClosed {
70 req.closeBody()
71 }
72 var urlStr string
73 if resp != nil && resp.Request != nil {
74 urlStr = stripPassword(resp.Request.URL)
75 } else {
76 urlStr = stripPassword(req.URL)
77 }
78 return &url.Error{
79 Op: urlErrorOp(reqs[0].Method),
80 URL: urlStr,
81 Err: err,
82 }
83 }
84 for {
85 // For all but the first request, create the next
86 // request hop and replace req.
87 if len(reqs) > 0 {
88 loc := resp.Header.Get("Location")
89 if loc == "" {
90 // While most 3xx responses include a Location, it is not
91 // required and 3xx responses without a Location have been
92 // observed in the wild. See issues #17773 and #49281.
93 return resp, nil
94 }
95 u, err := req.URL.Parse(loc)
96 if err != nil {
97 resp.closeBody()
98 return nil, uerr(fmt.Errorf("failed to parse Location header %q: %v", loc, err))
99 }
100 host := ""
101 if req.Host != "" && req.Host != req.URL.Host {
102 // If the caller specified a custom Host header and the
103 // redirect location is relative, preserve the Host header
104 // through the redirect. See issue #22233.
105 if u, _ := url.Parse(loc); u != nil && !u.IsAbs() {
106 host = req.Host
107 }
108 }
109 ireq := reqs[0]
110 req = &Request{
111 Method: redirectMethod,
112 Response: resp,
113 URL: u,
114 Header: make(Header),
115 Host: host,
116 Cancel: ireq.Cancel,
117 ctx: ireq.ctx,
118 }
119 if includeBody && ireq.GetBody != nil {
120 req.Body, err = ireq.GetBody()
121 if err != nil {
122 resp.closeBody()
123 return nil, uerr(err)
124 }
125 req.ContentLength = ireq.ContentLength
126 }
127
128 // Copy original headers before setting the Referer,
129 // in case the user set Referer on their first request.
130 // If they really want to override, they can do it in
131 // their CheckRedirect func.
132 copyHeaders(req)
133
134 // Add the Referer header from the most recent
135 // request URL to the new one, if it's not https->http:
136 if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL); ref != "" {
137 req.Header.Set("Referer", ref)
138 }
139 err = c.checkRedirect(req, reqs)
140
141 // Sentinel error to let users select the
142 // previous response, without closing its
143 // body. See Issue 10069.
144 if err == ErrUseLastResponse {
145 return resp, nil
146 }
147
148 // Close the previous response's body. But
149 // read at least some of the body so if it's
150 // small the underlying TCP connection will be
151 // re-used. No need to check for errors: if it
152 // fails, the Transport won't reuse it anyway.
153 const maxBodySlurpSize = 2 << 10
154 if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
155 io.CopyN(io.Discard, resp.Body, maxBodySlurpSize)
156 }
157 resp.Body.Close()
158
159 if err != nil {
160 // Special case for Go 1 compatibility: return both the response
161 // and an error if the CheckRedirect function failed.
162 // See https://golang.org/issue/3795
163 // The resp.Body has already been closed.
164 ue := uerr(err)
165 ue.(*url.Error).URL = loc
166 return resp, ue
167 }
168 }
169
170 reqs = append(reqs, req)
171 var err error
172 var didTimeout func() bool
173 if resp, didTimeout, err = c.send(req, deadline); err != nil {
174 // c.send() always closes req.Body
175 reqBodyClosed = true
176 if !deadline.IsZero() && didTimeout() {
177 err = &httpError{
178 err: err.Error() + " (Client.Timeout exceeded while awaiting headers)",
179 timeout: true,
180 }
181 }
182 return nil, uerr(err)
183 }
184
185 var shouldRedirect bool
186 redirectMethod, shouldRedirect, includeBody = redirectBehavior(req.Method, resp, reqs[0])
187 if !shouldRedirect {
188 return resp, nil
189 }
190
191 req.closeBody()
192 }
193}
也很清楚,基本都自解释了,还有注释。基本就是检查了一下字段,然后处理了重定向。定义了一个 reqs 来保存一路重定向的请求,再用做参数调用 checkRequest,再调用 c.send 取到 response,直到不用重定向,返回结果,基本都在 CheckRedirect 的注释那里解释了。再看 c.send 。
1// src/net/http/client.go#L168
2// didTimeout is non-nil only if err != nil.
3func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
4 if c.Jar != nil {
5 for _, cookie := range c.Jar.Cookies(req.URL) {
6 req.AddCookie(cookie)
7 }
8 }
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
21func (c *Client) deadline() time.Time {
22 if c.Timeout > 0 {
23 return time.Now().Add(c.Timeout)
24 }
25 return time.Time{}
26}
27
28func (c *Client) transport() RoundTripper {
29 if c.Transport != nil {
30 return c.Transport
31 }
32 return DefaultTransport
33}
34
35// send issues an HTTP request.
36// Caller should close resp.Body when done reading from it.
37func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
38 req := ireq // req is either the original request, or a modified fork
39
40 if rt == nil {
41 req.closeBody()
42 return nil, alwaysFalse, errors.New("http: no Client.Transport or DefaultTransport")
43 }
44
45 if req.URL == nil {
46 req.closeBody()
47 return nil, alwaysFalse, errors.New("http: nil Request.URL")
48 }
49
50 if req.RequestURI != "" {
51 req.closeBody()
52 return nil, alwaysFalse, errors.New("http: Request.RequestURI can't be set in client requests")
53 }
54
55 // forkReq forks req into a shallow clone of ireq the first
56 // time it's called.
57 forkReq := func() {
58 if ireq == req {
59 req = new(Request)
60 *req = *ireq // shallow clone
61 }
62 }
63
64 // Most the callers of send (Get, Post, et al) don't need
65 // Headers, leaving it uninitialized. We guarantee to the
66 // Transport that this has been initialized, though.
67 if req.Header == nil {
68 forkReq()
69 req.Header = make(Header)
70 }
71
72 if u := req.URL.User; u != nil && req.Header.Get("Authorization") == "" {
73 username := u.Username()
74 password, _ := u.Password()
75 forkReq()
76 req.Header = cloneOrMakeHeader(ireq.Header)
77 req.Header.Set("Authorization", "Basic "+basicAuth(username, password))
78 }
79
80 if !deadline.IsZero() {
81 forkReq()
82 }
83 stopTimer, didTimeout := setRequestCancel(req, rt, deadline)
84
85 resp, err = rt.RoundTrip(req)
86 if err != nil {
87 stopTimer()
88 if resp != nil {
89 log.Printf("RoundTripper returned a response & error; ignoring response")
90 }
91 if tlsErr, ok := err.(tls.RecordHeaderError); ok {
92 // If we get a bad TLS record header, check to see if the
93 // response looks like HTTP and give a more helpful error.
94 // See golang.org/issue/11111.
95 if string(tlsErr.RecordHeader[:]) == "HTTP/" {
96 err = errors.New("http: server gave HTTP response to HTTPS client")
97 }
98 }
99 return nil, didTimeout, err
100 }
101 if resp == nil {
102 return nil, didTimeout, fmt.Errorf("http: RoundTripper implementation (%T) returned a nil *Response with a nil error", rt)
103 }
104 if resp.Body == nil {
105 // The documentation on the Body field says “The http Client and Transport
106 // guarantee that Body is always non-nil, even on responses without a body
107 // or responses with a zero-length body.” Unfortunately, we didn't document
108 // that same constraint for arbitrary RoundTripper implementations, and
109 // RoundTripper implementations in the wild (mostly in tests) assume that
110 // they can use a nil Body to mean an empty one (similar to Request.Body).
111 // (See https://golang.org/issue/38095.)
112 //
113 // If the ContentLength allows the Body to be empty, fill in an empty one
114 // here to ensure that it is non-nil.
115 if resp.ContentLength > 0 && req.Method != "HEAD" {
116 return nil, didTimeout, fmt.Errorf("http: RoundTripper implementation (%T) returned a *Response with content length %d but a nil Body", rt, resp.ContentLength)
117 }
118 resp.Body = io.NopCloser(strings.NewReader(""))
119 }
120 if !deadline.IsZero() {
121 resp.Body = &cancelTimerBody{
122 stop: stopTimer,
123 rc: resp.Body,
124 reqDidTimeout: didTimeout,
125 }
126 }
127 return resp, nil, nil
128}
c.send 也是简单处理了下就调用 send,再看 send,也是检查了很多字段,如初始化 header,处理 basic auth,设置超时,最后调用 roundTripper 的 RoundTrip 方法。 需要注意,默认情况我们会使用 DefaultTransport 作为 RoundTripper。这一部分有点长,放到 transport 那边。