概述
在使用 Go 开发时几乎都会用到 net/http 标准库。但是,对库的内部实现不了解,仅限于会用。遇到问题容易懵,比如:
- 长连接和短连接有什么区别?具体什么实现原理?
- net/http 如何处理并发请求?
- net/http 有用到缓存吗?缓存用来干什么?
- ......
单个问题各个击破,不如深入梳理下 net/http 标准库一网打尽。本文将深入学习 net/http 标准库,力图做到知其然知其所以然。
服务端
源码走读
先看一段服务端示例代码:- func helloHandler(w http.ResponseWriter, r *http.Request) {
- // 向客户端写入响应内容
- fmt.Fprint(w, "Hello I am server 3")
- }
-
- func main() {
- // 创建自定义的ServeMux实例
- mux := http.NewServeMux()
-
- // 在mux上注册路由和处理函数
- mux.HandleFunc("/", helloHandler)
-
- // 启动服务器并监听12302端口,使用自定义的mux作为handler
- fmt.Println("Server is listening on port 12302...")
- if err := http.ListenAndServe(":12302", mux); err != nil {
- // 错误处理
- fmt.Printf("Failed to start server: %v\n", err)
- }
- }
复制代码 示例中有几类概念需要介绍。
多路复用器 http.ServeMux
- type ServeMux struct {
- mu sync.RWMutex // 多路复用器锁
- tree routingNode // 路由节点,用来存储路由 pattern 和对应的 handler
- ...
- }
复制代码 路由处理器 handler
handler 是一个实现 func(ResponseWriter, *Request) 函数接口的函数,用来处理请求。
流程
服务端启动需要经过以下流程。
1) 创建多路复用器
创建自定义 http.ServeMux 多路复用器,如果不创建的话,则会使用默认 http.DefaultServeMux 多路复用器:- // NewServeMux allocates and returns a new [ServeMux].
- func NewServeMux() *ServeMux {
- return &ServeMux{}
- }
- var DefaultServeMux = &defaultServeMux
-
- var defaultServeMux ServeMux
复制代码 2) 注册路由处理器
调用多路复用器的 HandleFunc 方法注册路由处理器:- func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
- if use121 {
- mux.mux121.handleFunc(pattern, handler)
- } else {
- // 将 pattern 和路由处理器注册到多路复用器
- mux.register(pattern, HandlerFunc(handler))
- }
- }
复制代码 注册的过程实际是将 pattern 和 handler 的映射关系写入 ServeMux.tree 中。可以根据请求的 pattern 从 ServeMux.tree 中获取对应的 handler。
3)监听服务
调用 http.ListenAndServe 监听服务:- func ListenAndServe(addr string, handler Handler) error {
- server := &Server{Addr: addr, Handler: handler}
- return server.ListenAndServe()
- }
- func (s *Server) ListenAndServe() error {
- ...
- // 调用 net.Listen 获取 listener
- ln, err := net.Listen("tcp", addr)
- if err != nil {
- return err
- }
- return s.Serve(ln)
- }
- func (s *Server) Serve(l net.Listener) error {
- ...
- for {
- // Accept waits for and returns the next connection to the listener.
- rw, err := l.Accept()
- ...
- c := s.newConn(rw)
- // 异步启动协程处理请求
- go c.serve(connCtx)
- }
- }
复制代码 监听服务监听到请求后会异步调用 conn.serve 启动协程处理请求:- func (c *conn) serve(ctx context.Context) {
- for {
- // 读请求
- w, err := c.readRequest(ctx)
- ...
- // 调用多路复用器的 ServeHTTP 方法处理请求
- serverHandler{c.server}.ServeHTTP(w, w.req)
- ...
- }
- func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
- // 如果多路复用器为空,则用默认多路复用器
- handler := sh.srv.Handler
- if handler == nil {
- handler = DefaultServeMux
- }
- ...
-
- handler.ServeHTTP(rw, req)
- }
- func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
- ...
- var h Handler
- if use121 {
- h, _ = mux.mux121.findHandler(r)
- } else {
- // 根据请求从多路复用器找到请求 pattern 对应的路由处理器
- h, r.Pattern, r.pat, r.matches = mux.findHandler(r)
- }
-
- // 调用路由处理器的 ServeHTTP 方法处理请求
- h.ServeHTTP(w, r)
- }
复制代码 服务端的处理流程并不复杂,主要是注册路由处理器和回调路由处理器过程,流程图就不画了,接下来介绍客户端的处理逻辑,这是比较复杂的部分。
客户端
核心数据结构
Client- type Client struct {
- // 通信模块,负责和服务端建立通信
- Transport RoundTripper
-
- // Cookie 模块,负责管理 Cookie
- Jar CookieJar
-
- // 超时时间,这个时间是请求处理的总超时时间
- Timeout time.Duration
复制代码 RoundTripper- type RoundTripper interface {
- RoundTrip(*Request) (*Response, error)
- }
复制代码 RoundTripper 是通信模块的 interface,需要实现方法 Roundtrip。通过传入请求 Request,与服务端交互后获得响应 Response。
http.Transport- type Transport struct {
- idleMu sync.Mutex
- ...
- // 空闲连接,实现复用,每个连接只能被一个请求使用
- idleConn map[connectMethodKey][]*persistConn
- // 等待连接队列,需要等待的连接请求会放到 idleConnWait 中
- idleConnWait map[connectMethodKey]wantConnQueue
- // 空闲连接 lru,结合 idleConn 根据连接时间管理连接
- idleLRU connLRU
-
- // 建立长连接开关,如果为 true 则不复用连接
- DisableKeepAlives bool
复制代码 Transport 是实现了 RoundTripper 接口的方法,是用于通信的模块。
源码走读
客户端请求的示例如下:- func main() {
- resp, err := client.Get("http://localhost:12302/")
- if err != nil {
- // 错误处理
- fmt.Printf("Failed to send request: %v\n", err)
- return
- }
-
- // 读取响应体内容
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- // 错误处理
- fmt.Printf("Failed to read response body: %v\n", err)
- return
- }
-
- // 打印服务器返回的响应内容
- fmt.Printf("Server response: %s\n", body)
- if err := resp.Body.Close(); err != nil {
- fmt.Printf("Failed to close response body: %v\n", err)
- }
- }
复制代码 请求服务端
client.Get 调用 api 请求服务端,获取响应。- func (c *Client) Get(url string) (resp *Response, err error) {
- // 构造请求体 request
- req, err := NewRequest("GET", url, nil)
- if err != nil {
- return nil, err
- }
-
- // 调用 client.Do(req) 获取响应
- return c.Do(req)
- }
- func (c *Client) Do(req *Request) (*Response, error) {
- return c.do(req)
- }
- func (c *Client) do(req *Request) (retres *Response, reterr error) {
- ...
- for {
- ...
- // 调用 client.send() 获取响应
- if resp, didTimeout, err = c.send(req, deadline); err != nil {
- ...
- }
- ...
- }
复制代码 客户端调用 client.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
- }
- func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
- ...
- // 通信模块 Transport.RoundTrip
- resp, err = rt.RoundTrip(req)
- if err != nil {
- ...
- }
- ...
- }
复制代码 通信模块 Transport 开始接管通信过程。- func (t *Transport) RoundTrip(req *Request) (*Response, error) {
- return t.roundTrip(req)
- }
- func (t *Transport) roundTrip(req *Request) (_ *Response, err error) {
- ...
- // 获取连接
- pconn, err := t.getConn(treq, cm)
-
- var resp *Response
- if pconn.alt != nil {
- // HTTP/2 path.
- resp, err = pconn.alt.RoundTrip(req)
- } else {
- // 调用连接的 roundTrip 方法获取服务端响应
- resp, err = pconn.roundTrip(treq)
- }
- ...
- }
复制代码 Transport.roundTrip 方法是这里的重点。主要包括两大逻辑:
- 调用 Transport.getConn 获取连接;
- 调用连接的 roundTrip 方法获取响应;
获取连接
- func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (_ *persistConn, err error) {
- ...
- // 构造连接请求对象 wantConn
- w := &wantConn{
- cm: cm,
- key: cm.key(),
- ctx: dialCtx,
- cancelCtx: dialCancel,
- result: make(chan connOrError, 1),
- beforeDial: testHookPrePendingDial,
- afterDial: testHookPostPendingDial,
- }
- defer func() {
- if err != nil {
- w.cancel(t, err)
- }
- }()
-
- // 获取连接
- if delivered := t.queueForIdleConn(w); !delivered {
- t.queueForDial(w)
- }
-
- // 异步获取结果并处理 context
- select {
- case r := <-w.result:
- ...
- return r.pc, r.err
- case <-treq.ctx.Done():
- ...
- }
- }
复制代码 Transport.queueForIdleConn 判断 Transport.idleConn 是否有空闲连接,如果有则调用 wantConn.tryDeliver 传递连接:
[code]func (w *wantConn) tryDeliver(pc *persistConn, err error, idleAt time.Time) bool { w.mu.Lock() defer w.mu.Unlock() ... // 实际是往 wantConn.result 通道中写 connOrError 对象,该对象中包括连接 pc w.result |