找回密码
 立即注册
首页 业界区 业界 go net/http 学习笔记

go net/http 学习笔记

赴忽 昨天 15:20
概述

在使用 Go 开发时几乎都会用到 net/http 标准库。但是,对库的内部实现不了解,仅限于会用。遇到问题容易懵,比如:

  • 长连接和短连接有什么区别?具体什么实现原理?
  • net/http 如何处理并发请求?
  • net/http 有用到缓存吗?缓存用来干什么?
  • ......
单个问题各个击破,不如深入梳理下 net/http 标准库一网打尽。本文将深入学习 net/http 标准库,力图做到知其然知其所以然。
服务端

源码走读

先看一段服务端示例代码:
  1. func helloHandler(w http.ResponseWriter, r *http.Request) {  
  2.     // 向客户端写入响应内容  
  3.     fmt.Fprint(w, "Hello I am server 3")  
  4. }  
  5.   
  6. func main() {  
  7.     // 创建自定义的ServeMux实例  
  8.     mux := http.NewServeMux()  
  9.   
  10.     // 在mux上注册路由和处理函数  
  11.     mux.HandleFunc("/", helloHandler)  
  12.   
  13.     // 启动服务器并监听12302端口,使用自定义的mux作为handler  
  14.     fmt.Println("Server is listening on port 12302...")  
  15.     if err := http.ListenAndServe(":12302", mux); err != nil {  
  16.        // 错误处理  
  17.        fmt.Printf("Failed to start server: %v\n", err)  
  18.     }  
  19. }
复制代码
示例中有几类概念需要介绍。
多路复用器 http.ServeMux
  1. type ServeMux struct {  
  2.   mu     sync.RWMutex  // 多路复用器锁
  3.   tree   routingNode   // 路由节点,用来存储路由 pattern 和对应的 handler
  4.   ...
  5. }
复制代码
路由处理器 handler

handler 是一个实现 func(ResponseWriter, *Request) 函数接口的函数,用来处理请求。
流程

服务端启动需要经过以下流程。
1) 创建多路复用器
创建自定义 http.ServeMux 多路复用器,如果不创建的话,则会使用默认 http.DefaultServeMux 多路复用器:
  1. // NewServeMux allocates and returns a new [ServeMux].  
  2. func NewServeMux() *ServeMux {  
  3.   return &ServeMux{}  
  4. }
  5. var DefaultServeMux = &defaultServeMux  
  6.   
  7. var defaultServeMux ServeMux
复制代码
2) 注册路由处理器
调用多路复用器的 HandleFunc 方法注册路由处理器:
  1. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {  
  2.   if use121 {  
  3.    mux.mux121.handleFunc(pattern, handler)  
  4.   } else {  
  5.    // 将 pattern 和路由处理器注册到多路复用器
  6.    mux.register(pattern, HandlerFunc(handler))  
  7.   }  
  8. }
复制代码
注册的过程实际是将 pattern 和 handler 的映射关系写入 ServeMux.tree 中。可以根据请求的 pattern 从 ServeMux.tree 中获取对应的 handler。
3)监听服务
调用 http.ListenAndServe 监听服务:
  1. func ListenAndServe(addr string, handler Handler) error {  
  2.   server := &Server{Addr: addr, Handler: handler}  
  3.   return server.ListenAndServe()  
  4. }
  5. func (s *Server) ListenAndServe() error {  
  6.   ...
  7.   // 调用 net.Listen 获取 listener
  8.   ln, err := net.Listen("tcp", addr)  
  9.   if err != nil {  
  10.    return err  
  11.   }  
  12.   return s.Serve(ln)  
  13. }
  14. func (s *Server) Serve(l net.Listener) error {
  15.   ...
  16.   for {  
  17.     // Accept waits for and returns the next connection to the listener.
  18.     rw, err := l.Accept()
  19.     ...
  20.     c := s.newConn(rw)  
  21.     // 异步启动协程处理请求
  22.     go c.serve(connCtx)  
  23.   }  
  24. }
复制代码
监听服务监听到请求后会异步调用 conn.serve 启动协程处理请求:
  1. func (c *conn) serve(ctx context.Context) {
  2.   for {  
  3.     // 读请求
  4.     w, err := c.readRequest(ctx)
  5.     ...
  6.     // 调用多路复用器的 ServeHTTP 方法处理请求
  7.     serverHandler{c.server}.ServeHTTP(w, w.req)
  8.     ...
  9. }
  10. func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {  
  11.   // 如果多路复用器为空,则用默认多路复用器
  12.   handler := sh.srv.Handler  
  13.   if handler == nil {  
  14.    handler = DefaultServeMux  
  15.   }  
  16.   ...
  17.   
  18.   handler.ServeHTTP(rw, req)  
  19. }
  20. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {  
  21.   ...
  22.   var h Handler  
  23.   if use121 {  
  24.    h, _ = mux.mux121.findHandler(r)  
  25.   } else {  
  26.    // 根据请求从多路复用器找到请求 pattern 对应的路由处理器
  27.    h, r.Pattern, r.pat, r.matches = mux.findHandler(r)  
  28.   }
  29.   
  30.   // 调用路由处理器的 ServeHTTP 方法处理请求  
  31.   h.ServeHTTP(w, r)  
  32. }
复制代码
服务端的处理流程并不复杂,主要是注册路由处理器和回调路由处理器过程,流程图就不画了,接下来介绍客户端的处理逻辑,这是比较复杂的部分。
客户端

核心数据结构

Client
  1. type Client struct {
  2.   // 通信模块,负责和服务端建立通信
  3.   Transport RoundTripper
  4.   
  5.   // Cookie 模块,负责管理 Cookie
  6.   Jar CookieJar
  7.   
  8.   // 超时时间,这个时间是请求处理的总超时时间
  9.   Timeout time.Duration
复制代码
RoundTripper
  1. type RoundTripper interface {
  2.   RoundTrip(*Request) (*Response, error)  
  3. }
复制代码
RoundTripper 是通信模块的 interface,需要实现方法 Roundtrip。通过传入请求 Request,与服务端交互后获得响应 Response。
http.Transport
  1. type Transport struct {  
  2.   idleMu       sync.Mutex  
  3.   ...
  4.   // 空闲连接,实现复用,每个连接只能被一个请求使用
  5.   idleConn     map[connectMethodKey][]*persistConn
  6.   // 等待连接队列,需要等待的连接请求会放到 idleConnWait 中
  7.   idleConnWait map[connectMethodKey]wantConnQueue  
  8.   // 空闲连接 lru,结合 idleConn 根据连接时间管理连接
  9.   idleLRU      connLRU
  10.   
  11.   // 建立长连接开关,如果为 true 则不复用连接
  12.   DisableKeepAlives bool
复制代码
Transport 是实现了 RoundTripper 接口的方法,是用于通信的模块。
源码走读

客户端请求的示例如下:
  1. func main() {
  2.     resp, err := client.Get("http://localhost:12302/")  
  3.     if err != nil {  
  4.        // 错误处理  
  5.        fmt.Printf("Failed to send request: %v\n", err)  
  6.        return  
  7.     }  
  8.   
  9.     // 读取响应体内容  
  10.     body, err := io.ReadAll(resp.Body)  
  11.     if err != nil {  
  12.        // 错误处理  
  13.        fmt.Printf("Failed to read response body: %v\n", err)  
  14.        return  
  15.     }  
  16.   
  17.     // 打印服务器返回的响应内容  
  18.     fmt.Printf("Server response: %s\n", body)  
  19.     if err := resp.Body.Close(); err != nil {  
  20.        fmt.Printf("Failed to close response body: %v\n", err)  
  21.     }  
  22. }
复制代码
请求服务端

client.Get 调用 api 请求服务端,获取响应。
  1. func (c *Client) Get(url string) (resp *Response, err error) {  
  2.   // 构造请求体 request
  3.   req, err := NewRequest("GET", url, nil)  
  4.   if err != nil {  
  5.    return nil, err  
  6.   }  
  7.   
  8.   // 调用 client.Do(req) 获取响应
  9.   return c.Do(req)  
  10. }
  11. func (c *Client) Do(req *Request) (*Response, error) {  
  12.   return c.do(req)  
  13. }
  14. func (c *Client) do(req *Request) (retres *Response, reterr error) {
  15.   ...
  16.   for {
  17.     ...
  18.     // 调用 client.send() 获取响应
  19.     if resp, didTimeout, err = c.send(req, deadline); err != nil {
  20.       ...
  21.     }
  22.     ...
  23. }
复制代码
客户端调用 client.send() 发送请求到服务端,获取响应。
  1. func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {  
  2.   ...
  3.   resp, didTimeout, err = send(req, c.transport(), deadline)  
  4.   if err != nil {  
  5.    return nil, didTimeout, err  
  6.   }  
  7.   ...
  8.   return resp, nil, nil  
  9. }
  10. func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {  
  11.   ...
  12.   // 通信模块 Transport.RoundTrip
  13.   resp, err = rt.RoundTrip(req)  
  14.   if err != nil {
  15.     ...
  16.   }
  17.   ...
  18. }
复制代码
通信模块 Transport 开始接管通信过程。
  1. func (t *Transport) RoundTrip(req *Request) (*Response, error) {  
  2.   return t.roundTrip(req)  
  3. }
  4. func (t *Transport) roundTrip(req *Request) (_ *Response, err error) {
  5.   ...
  6.   // 获取连接
  7.   pconn, err := t.getConn(treq, cm)
  8.   
  9.   var resp *Response  
  10.   if pconn.alt != nil {  
  11.     // HTTP/2 path.  
  12.     resp, err = pconn.alt.RoundTrip(req)  
  13.   } else {  
  14.     // 调用连接的 roundTrip 方法获取服务端响应
  15.     resp, err = pconn.roundTrip(treq)  
  16.   }
  17.   ...
  18. }
复制代码
Transport.roundTrip 方法是这里的重点。主要包括两大逻辑:

  • 调用 Transport.getConn 获取连接;
  • 调用连接的 roundTrip 方法获取响应;
获取连接
  1. func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (_ *persistConn, err error) {
  2.   ...
  3.   // 构造连接请求对象 wantConn
  4.   w := &wantConn{  
  5.     cm:         cm,  
  6.     key:        cm.key(),  
  7.     ctx:        dialCtx,  
  8.     cancelCtx:  dialCancel,  
  9.     result:     make(chan connOrError, 1),  
  10.     beforeDial: testHookPrePendingDial,  
  11.     afterDial:  testHookPostPendingDial,  
  12.   }  
  13.   defer func() {  
  14.     if err != nil {  
  15.       w.cancel(t, err)  
  16.     }  
  17.   }()
  18.   
  19.   // 获取连接
  20.   if delivered := t.queueForIdleConn(w); !delivered {  
  21.     t.queueForDial(w)  
  22.   }
  23.   
  24.   // 异步获取结果并处理 context
  25.   select {  
  26.     case r := <-w.result:
  27.       ...
  28.       return r.pc, r.err
  29.     case <-treq.ctx.Done():
  30.       ...
  31.   }
  32. }
复制代码
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

相关推荐

您需要登录后才可以回帖 登录 | 立即注册