Go: 标准库 net/http
1 · 标准库 net/http#
HTTP 是请求-响应协议,自然有 Client 和 Server。
Request/Response 是数据载体。
服务端:Server 监听连接,Handler 处理请求,通常使用ServeMux 按请求路径进行路由处理。
客户端:Client 提供 Get/Post 等http methods方法,真正发请求的是其持有的 RoundTripper(接口),默认实现是 Transport,负责 TCP 连接、连接池、HTTP/2、TLS 等。
服务端:
Server → conn → Handler(通常是 ServeMux)
↑
Request → | → ResponseWriter
客户端:
Client → Do(Request) → RoundTripper(接口)
↓
Transport(默认实现)
↓
TCP/TLS Conn → Network
↓
Response
go doc net/http.Client
go doc net/http.Server
go doc net/http.Request
go doc net/http.Response
# 接口,定义了服务端处理http请求的行为
go doc net/http.Handler
# 实现
go doc net/http.ServeMux
# 定义服务端如何构建http响应,实现为包中不可导出的response类型
go doc net/http.ResponseWriter
# 接口,定义了客户端进行一次http请求的行为
go doc net/http.RoundTripper
# 实现
go doc net/http.Transport
1.1 · 服务端
ListenAndServe -> Handler(使用ServeMux进行路由) -> ResponseWriter
// 监听 addr 产生的 tcp 连接,使用 handler 进行处理(为 nil 使用 DefaultServeMux )
// 本质:新建了一个 Server,
// 监听 tcp 端口,for 循环获取连接conn,将server包装成serverHandler,内部使用 handler 进行处理
func ListenAndServe(addr string, handler Handler) error {}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
var DefaultServeMux = &defaultServeMux // ServeMux 类型,实现了 Handler
type ServeMux struct{}
// 从请求 r 中 findHandler 获取到 Handler,进行处理
// 可以看出:ServeMux 是 Handler 的装饰器
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
// 给定 pattern,注册 handler 到 DefaultServeMux
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {}
// 非导出类型 [response] 实现了接口,在每次conn时被创建,持有请求、连接、缓存writer等
type ResponseWriter interface {
// 写响应体
// 如果`响应未开始`,也就是没有调用[WriteHeader],以默认成功状态`200 OK`开始
Write([]byte) (int, error)
// 拿到响应头:配置待发送的响应头
Header() Header
// 开始响应(显式给出状态码,提交响应头[Header])
WriteHeader(statusCode int)
// 分别对应http协议的:状态行、响应头、响应体
// ? 为什么不直接返回Response:因为服务端响应经常是`边算边写`
}
type Server struct {
Addr string
Handler Handler // handler to invoke, http.DefaultServeMux if nil
}
总结:Server负责接入连接,将请求交给Handler;Handler负责处理请求,并写响应。
1.2 · 客户端
Client -> Do -> RoundTripper -> Transport
var DefaultClient = &Client{}
type Client struct {
Transport RoundTripper
}
// 发送请求,获得响应
func (c *Client) Do(req *Request) (*Response, error)
// 获取RoundTripper:客户端自有的,或者全局的DefaultTransport(类型Transport)
func (c *Client) transport() RoundTripper {}
// 调用 RoundTrip 方法发送请求
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}
// 默认RoundTripper的具体实现,执行请求动作
// 负责:连接建立、连接池、keep-alive、TLS、HTTP/2
type Transport struct{}
总结:Client 是请求编排层(调用语义),Transport 是网络执行层(传输语义),这两层边界在RoundTripper接口。
前者,如何使用和调度 Request;后者,如何执行这个 Request。
1.3 · Request, Response#
服务端:
- ServeHTTP 的 Request 参数是如何构造的?Body 是谁提供的。
- 如何写响应的,ResponseWriter 的实现?
客户端:
- Do 的 Request 参数是如何构造的?Body 是谁提供的。
- 如何处理 Response 的。
1.4 · 超时、取消
timeout, context
客户端:
- Client.Timeout 整个请求总超时
- Transport 有哪些超时配置?
- Request.WithContext 超时/取消;更细粒度(单个请求层面),更推荐
Dial TCP -> TLS 握手 -> 发请求头/体 -> 等响应头 -> 读响应体
- DialContext 建 TCP 连接
- TLSHandshakeTimeout TLS 握手
- IdleConnTimeout 连接池里,空闲连接能活多久
- ResponseHeaderTimeout 发完请求后等响应头
- ExpectContinueTimeout 等 100 Continue
tr := &http.Transport{
DialContext: (&net.Dialer{Timeout: 3 * time.Second}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 5 * time.Second,
// 连接池
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
}
服务端:
建立连接 -> 读请求头 -> 读请求体 -> 业务处理 -> 写响应 -> 空闲等待下一次请求
- ReadHeaderTimeout 读请求头
- ReadTimeout 读请求头 + 请求体
- WriteTimeout 写响应
- IdleTimeout keep-alive 空闲期
type Server struct {
ReadHeaderTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
// ... other fields elided ...
}
客户端 HTTP/1.x
连接池空闲
<------ Transport.IdleConnTimeout ------>
开始请求
|
|---- net.Dialer.Timeout ----| TCP connect
| |
|------ Transport.TLSHandshakeTimeout -----| HTTPS 才有
| |
| 发请求头
|---- Transport.ExpectContinueTimeout ---->| 仅 Expect: 100-continue
| | 等 100 Continue
| 发请求体
|---------------- Transport.ResponseHeaderTimeout ---------------->| 等响应头
| |
|------------------------------ 读响应体 ---------------------------->|
|
|<-------------------- http.Client.Timeout / req.Context -------------------->|
服务端 HTTP/1.x
连接建立
|
|---- TLS handshake ----| HTTPS 才有
|
|---- Server.ReadHeaderTimeout ---->| 读请求头
|-------------------------------- Server.ReadTimeout ------------------------->|
| 读请求头 + 读请求体
|
| handler / 业务处理
|
|--------------------- Server.WriteTimeout --------------------->| 写响应头+体
|
请求结束,连接进入 keep-alive
|-------- Server.IdleTimeout -------->| 等下一个请求到来
1.5 · 中间件
服务端:
func(http.Handler) http.Handler
客户端:
RoundTripper
2 · httptest#
httptest分两层:NewRequest+Recorder做进程内handler测试,Server做真实loopback端到端测试,兼顾易用与贴近真实。
NewRequest: 模拟”请求进来”NewRecorder: 实现了http.ResponseWriter接口,模拟”响应写出”NewServer: 接受http.Handler接口的实现为参数,启动真实httpserver,用于测试必须经过网络栈的东西
// 构造请求
// go doc net/http/httptest.NewRequest
func NewRequest(method, target string, body io.Reader) *http.Request {}
// go doc net/http/httptest.NewRecorder
func NewRecorder() *ResponseRecorder {}
type ResponseRecorder struct{}
// go doc net/http/httptest.NewServer
func NewServer(handler http.Handler) *Server {}
3 · httptrace#
为 HTTP Client 单次请求提供阶段性埋点 httptrace.ClientTrace 是一个方法集结构体,用户自定义相关阶段的方法
通过httptrace.WithClientTrace把httptrace.ClientTrace挂到 Request.Context;
通过httptrace.ContextClientTrace从ctx中取出httptrace.ClientTrace,调用相关tracing方法
4 · 看 http 包设计#
优点:少量抽象,覆盖很大问题空间
- 统计抽象非常小:服务端只有
Handler和ResponseWriter(更贴近io.Writer) - 函数适配接口:
HandleFunc使得函数实现了Handler接口 - 组合优于集成:中间件就是
func(Handler) Handler函数组合 - 接口边界按职责切分:客户端负责高层策略(重定向、cookies等),
RoundTripper负责底层机制(一次请求,一次响应); - 零值可用、极短默认路径:
DefaultClient等默认值,http.Get等包级别函数
设计问题:
- ResponseWriter 状态机式接口, 一旦开始写,很多状态就冻结了;
- ResponseWriter 的高级能力,散落在额外接口,写中间件的时候,就需要类型断言进行透传,不然会丢失
- Request 同时表示入站和出站,请求模型偏重。