一文说透 Go 语言 HTTP 标准库

基于HTTP构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。HTTP 请求从客户端发出,服务端接受到请求后进行处理然后将响应返回给客户端。所以http服务器的工作就在于如何接受来自客户端的请求,并向客户端返回响应。

一个典型的 HTTP 服务应该如图所示:

http

HTTP client

在 Go 中可以直接通过 HTTP 包的 Get 方法来发起相关请求数据,一个简单例子:

func main() { resp, err := http.Get("http://httpbin.org/get?name=luozhiyun&age=27") if err != nil { fmt.Println(err) return } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) }

我们下面通过这个例子来进行分析。

HTTP 的 Get 方法会调用到 DefaultClient 的 Get 方法,DefaultClient 是 Client 的一个空实例,所以最后会调用到 Client 的 Get 方法:

httpclient

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

Client 结构体总共由四个字段组成:

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

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

Jar:用于管理和存储请求中的 cookie;

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

初始化请求 func (c *Client) Get(url string) (resp *Response, err error) { // 根据方法名、URL 和请求体构建请求 req, err := NewRequest("GET", url, nil) if err != nil { return nil, err } // 执行请求 return c.Do(req) }

我们要发起一个请求首先需要根据请求类型构建一个完整的请求头、请求体、请求参数。然后才是根据请求的完整结构来执行请求。

NewRequest 初始化请求

NewRequest 会调用到 NewRequestWithContext 函数上。这个函数会根据请求返回一个 Request 结构体,它里面包含了一个 HTTP 请求所有信息。

Request

Request 结构体有很多字段,我这里列举几个大家比较熟悉的字段:

httpclient2

NewRequestWithContext

func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) { ... // parse url u, err := urlpkg.Parse(url) if err != nil { return nil, err } rc, ok := body.(io.ReadCloser) if !ok && body != nil { rc = ioutil.NopCloser(body) } u.Host = removeEmptyPort(u.Host) req := &Request{ ctx: ctx, Method: method, URL: u, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: make(Header), Body: rc, Host: u.Host, } ... return req, nil }

NewRequestWithContext 函数会将请求封装成一个 Request 结构体并返回。

准备 http 发送请求

httpclientsend

如上图所示,Client 调用 Do 方法处理发送请求最后会调用到 send 函数中。

func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) { resp, didTimeout, err = send(req, c.transport(), deadline) if err != nil { return nil, didTimeout, err } ... return resp, nil, nil } Transport

Client 的 send 方法在调用 send 函数进行下一步的处理前会先调用 transport 方法获取 DefaultTransport 实例,该实例如下:

var DefaultTransport RoundTripper = &Transport{ // 定义 HTTP 代理策略 Proxy: ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, }).DialContext, ForceAttemptHTTP2: true, // 最大空闲连接数 MaxIdleConns: 100, // 空闲连接超时时间 IdleConnTimeout: 90 * time.Second, // TLS 握手超时时间 TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }

transport

Transport 实现 RoundTripper 接口,该结构体会发送 http 请求并等待响应。

type RoundTripper interface { RoundTrip(*Request) (*Response, error) }

从 RoundTripper 接口我们也可以看出,该接口定义的 RoundTrip 方法会具体的处理请求,处理完毕之后会响应 Response。

回到我们上面的 Client 的 send 方法中,它会调用 send 函数,这个函数主要逻辑都交给 Transport 的 RoundTrip 方法来执行。

transport2

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxggg.html