基于HTTP构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。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 方法:
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 结构体有很多字段,我这里列举几个大家比较熟悉的字段:
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 发送请求如上图所示,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 } TransportClient 的 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 实现 RoundTripper 接口,该结构体会发送 http 请求并等待响应。
type RoundTripper interface { RoundTrip(*Request) (*Response, error) }从 RoundTripper 接口我们也可以看出,该接口定义的 RoundTrip 方法会具体的处理请求,处理完毕之后会响应 Response。
回到我们上面的 Client 的 send 方法中,它会调用 send 函数,这个函数主要逻辑都交给 Transport 的 RoundTrip 方法来执行。