IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Golang net/http & HTTP Serve 源码分析

    ike‘s blog发表于 2022-03-09 00:00:00
    love 0

    这篇是转载的!不过还是很有用的,go的http可以推荐用gin,下次梳理下gin的教程好了 Go HTTP Server的大致处理流程|wide

    服务器在收到请求时,首先进入路由 Router,接着路由会根据 request 请求的路径,找到对应的处理器(Handler),处理器再根据 request 进行处理并构造 response 进行返回。

    利用标准库实现一个简单HTTP Server

    向main.go文件写入如下内容:

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    // 方法一
    type HelloContext struct {
        content string
    }
    
    func(h *HelloContext) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, h.content)
    }
    
    // 方法二
    func helloHandler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, net/http! v2\n")
    }
    
    func main() {
        http.Handle("/v1", &HelloContext{content: "Hello, net/http! v1\n"})
        http.HandleFunc("/v2", helloHandler)
        http.ListenAndServe(":8080", nil)
    }
    

    运行后,可以用 curl 工具进行测试:

    mac:~ $ curl 127.0.0.1:8080/v1
    Hello, net/http! v1
    mac:~ $ curl 127.0.0.1:8080/v2
    Hello, net/http! v2
    

    这段代码我们用 http.Handle 和 http.HandleFunc 两种方法分别在路径 /v1 和 /v2 上注册了两个 http.Handler。注意:Handle 和 Handler 是两个东西。
    这两个 Handler 都对 request 进行了处理,并且通过 fmt.Fprintf 方法写入并返回数据。

    处理器

    http.Handler

    先来了解一下 http.Handler (处理器),

    type Handler interface {
     ServeHTTP(ResponseWriter, *Request)
    }
    

    它被定义为一个拥有 ServeHTTP 方法的接口,也就是说任何类型,只要实现了 ServeHTTP 方法,就实现了 http.Handler 接口。

    ServeHTTP 方法会读取 *Request 信息,并且向 ResponseWriter 写入 header 与 body 内容。

    路由注册

    http.Handle

    从 main函数出发,来看 http.Handle 函数源码:

    func Handle(pattern string, handler Handler) { 
      DefaultServeMux.Handle(pattern, handler) 
    }
    

    可以看到,http.Handle 函数调用了 DefaultServeMux.Handle 方法。

    http.HandleFunc

    再来看 http.HandleFunc 的源码:

    func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
     DefaultServeMux.HandleFunc(pattern, handler)
    }
    

    它也调用了 DefaultServeMux.HandleFunc 方法,再看此方法源码:

    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
     if handler == nil {
      panic("http: nil handler")
     }
     mux.Handle(pattern, HandlerFunc(handler))
    }
    

    不难看出,http.Handle 和 http.HandleFunc 都调用了一个和 ServeMux 对象的 Handle 方法有关。

    这两个方法的作用都是将传入的处理器 (Handler) 注册到对应的路由规则 (pattern)上。

    比如,倒数第三行将 处理器 helloHandler 注册到了路由规则 (路径) /v2 上。这样,当 HTTP 请求的地址是 /v2的时候,就由处理器 helloHandler 来负责处理请求,并且响应。

    mux.Handle 方法中还有一个 http.HandlerFunc ,注意不是 HandleFunc。

    适配器与处理器

    http.HandlerFunc

    type HandlerFunc func(ResponseWriter, *Request)
    

    HandlerFunc 可以理解为一个适配器,它允许使用普通的函数成为处理器 Handler 对象,前提是这个普通函数拥有 func(ResponseWriter, *Request) 签名。

    上文说到,任何类型只要实现了 ServeHTTP 方法,那它就实现了 Handler接口,它就是一个 Handler 类型。

    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
     f(w, r)
    }
    

    这里呢,非常的巧妙,HandlerFunc 类型实现了 ServeHTTP 方法,并且又将 ServeHTTP方法的参数传给了自身。

    也就是说:

    1. 一个普通的函数,只要参数是 ResponseWriter 和 *Request,或者换种标准点的说法,它的函数签名为 func(ResponseWriter,*Request),那么它就是 HandlerFunc 类型。
    2. 由于 HandlerFunc 自身实现了 ServeHTTP方法,所以这个普通函数又实现了 Handler 接口,成了 Handler 类型。

    到这里,如何注册、DefaultServeMux 和 ServeMux 是什么,我们暂时还不知道,为了便于理解,这个下文再说。

    监听与服务

    http.ListenAndServe

    接着往下走,看一下 http.ListenAndServe 做了哪些事情:

    // ListenAndServe listens on the TCP network address addr and then calls
    // Serve with handler to handle requests on incoming connections.
    // Accepted connections are configured to enable TCP keep-alives.
    //
    // The handler is typically nil, in which case the DefaultServeMux is used.
    //
    // ListenAndServe always returns a non-nil error.
    func ListenAndServe(addr string, handler Handler) error {
     server := &Server{Addr: addr, Handler: handler}
     return server.ListenAndServe()
    }
    

    不难看出,http.ListenAndServe 负责监听 TCP 网络地址 addr, 代码中写的是:8080 也即是监听 8080 端口,并且处理相关的请求。

    这里传入的第二个参数是 Handler 类型,根据注释可以看出:如果传入值为 nil ,那么将会使用 DefaultServeMux 。

    服务复用器

    DefaultServeMux

    // DefaultServeMux is the default ServeMux used by Serve.
    var DefaultServeMux = &defaultServeMux
    
    var defaultServeMux ServeMux
    

    说白了,DefaultServeMux 是 ServeMux 类型的一个实例,由标准库创建。

    下面看 ServeMux 结构体的源码。

    ServeMux

    ServeMux 是一个结构体,它的作用是服务复用器。

    type ServeMux struct {
     mu    sync.RWMutex
     m     map[string]muxEntry
     es    []muxEntry // slice of entries sorted from longest to shortest.
     hosts bool       // whether any patterns contain hostnames
    }
    

    因为涉及并发,所以这里有个读写锁 mu,主要用于保护下面的 map 类型的成员 m。

    es 与 hosts 和路由规则匹配有关。

    这里重点关注一下 m,它是一个 map ,key 是 string 类型的路由表达式,val 是 muxEntry 类型的结构体。

    type muxEntry struct {
     h       Handler
     pattern string
    }
    

    muxEntry 结构体,描述了路由规则 pattern 对应的处理器 h。

    mux.Handle

    上文中,http.Handle 和 http.HandleFunc 都调用了 mux.Handle 方法。

    它是结构体 ServeMux 的方法,也就是说,此方法主要把 Handler 对象注册到给定的 pattern 上,也即路由注册。

    // Handle registers the handler for the given pattern.
    // If a handler already exists for pattern, Handle panics.
    func (mux *ServeMux) Handle(pattern string, handler Handler) {
      // 为了保护 ServeMux 成员 map 类型的 m 的读写,分别在方法开始和结束的时候进行加锁和解锁的操作。
     mux.mu.Lock()
     defer mux.mu.Unlock()
     // 如果路由规则 pattern 为空,则直接 panic。
     if pattern == "" {
      panic("http: invalid pattern")
     }
      // 如果 http.Handler 类型的处理器 handler 为空,则panic。
     if handler == nil {
      panic("http: nil handler")
     }
      // 如果路由规则 pattern 已经存在,则直接 panic。
     if _, exist := mux.m[pattern]; exist {
      panic("http: multiple registrations for " + pattern)
     }
     // 如果成员 m 为空,则 make 一个新的 map。
     if mux.m == nil {
      mux.m = make(map[string]muxEntry)
     }
      // 创建一个 muxEntry,并将 pattern 对应的 Handler 放进去。
     e := muxEntry{h: handler, pattern: pattern}
      // 写入 m, key 为 pattern ,value 为新建的 muxEntry 类型的 e ,也即新增一个路由规则。
     mux.m[pattern] = e
      // 如果路由规则以字符 / 结尾,则给将新建的 muxEntry 类型的 e 放到成员 es 中。
      // es 是一个切片,使用 http.appendSorted 方法加入元素,以确保 es 中的元素(路由)是从最长到最短。
     if pattern[len(pattern)-1] == '/' {
      mux.es = appendSorted(mux.es, e)
     }
      // 最后,如果路由规则不是以字符 / 开头,那么给成员 hosts 赋值 true 。
     if pattern[0] != '/' {
      mux.hosts = true
     }
    }
    

    mux.ServeHTTP

    我把 ServeMux 的 ServeHTTP 方法简称为 mux.ServeHTTP,下文也是一样。

    func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
     if r.RequestURI == "*" {
      if r.ProtoAtLeast(1, 1) {
       w.Header().Set("Connection", "close")
      }
      w.WriteHeader(StatusBadRequest)
      return
     }
     h, _ := mux.Handler(r)
     h.ServeHTTP(w, r)
    }
    

    ServeMux 结构体同样实现了 ServeHTTP 方法,也即它也实现了 Handler 接口,是一个 Handler 类型的对象。

    但它并不负责处理具体的请求,篇幅有限,这里给出,调用的 mux.Handler 方法签名:

    func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
    

    总的来说,mux.ServeHTTP 调用了 mux.Handler 方法,通过 host 和 path 找到具体的 处理器 Handler 和路由规则 pattern ,然后让对应的 Handler 的 ServeHTTP 方法去处理请求。

    连接与请求的处理

    其实搞懂上面方法以及其之间的关系,对于进一步的学习 Go Web 框架 (比如 Gin ) 就已经有很大的帮助了。从监听与服务开始,代码更加底层,这里我主要关心的是,一次请求是如何到达 ServeHTTP 的。

    http.ListenAndServe 方法中,使用传入的监听地址 addr 和处理器 handler 初始化一个 HTTP 服务器 http.Server。

    Server 结构体,主要定义了需要跑一个 HTTP Server 所需要的参数:

    Server

    type Server struct {
     Addr string
     Handler Handler // handler to invoke, http.DefaultServeMux if nil
      
     TLSConfig *tls.Config
     ReadTimeout time.Duration
     ReadHeaderTimeout time.Duration
     WriteTimeout time.Duration
     IdleTimeout time.Duration
     MaxHeaderBytes int
     TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
     ConnState func(net.Conn, ConnState)
     ErrorLog *log.Logger
     BaseContext func(net.Listener) context.Context
     ConnContext func(ctx context.Context, c net.Conn) context.Context
     inShutdown atomicBool // true when server is in shutdown
     disableKeepAlives int32     // accessed atomically.
     nextProtoOnce     sync.Once // guards setupHTTP2_* init
     nextProtoErr      error     // result of http2.ConfigureServer if used
      
     mu         sync.Mutex
     listeners  map[*net.Listener]struct{}
     activeConn map[*conn]struct{}
     doneChan   chan struct{}
     onShutdown []func()
    }
    

    这些参数不是重点,接着往下。

    server.ListenAndServe

    func (srv *Server) ListenAndServe() error {
     if srv.shuttingDown() {
      return ErrServerClosed
     }
     addr := srv.Addr
     if addr == "" {
      addr = ":http"
     }
     ln, err := net.Listen("tcp", addr)
     if err != nil {
      return err
     }
     return srv.Serve(ln)
    }
    

    Server 结构体的 ListenAndServe 方法会监听 TCP 网络地址 addr ,然后调用 srv.Serve 处理传入连接的请求。

    srv.Serve

    func (srv *Server) Serve(l net.Listener) error {
     // ...省略部分
     for {
        // 循环监听 TCP 连接
      rw, err := l.Accept()
      if err != nil {
        ...省略部分 
      connCtx := ctx
      if cc := srv.ConnContext; cc != nil {
       connCtx = cc(connCtx, rw)
       if connCtx == nil {
        panic("ConnContext returned nil")
       }
      }
      tempDelay = 0
      c := srv.newConn(rw)
      c.setState(c.rwc, StateNew, runHooks) // before Serve can return
      go c.serve(connCtx)
     }
    }
    

    Serve 方法在 Listenner l 上接受传入的连接,并且为每一个连接创建 goroutine 。这些 gorutines 会读取请求并且调用 srv.Handler 去响应它们。

    c.Serve

    // Serve a new connection.
    func (c *conn) serve(ctx context.Context) {
      // ...
     for {
        // 循环接受请求,一个连接可以处理多个请求
      w, err := c.readRequest(ctx)
      if c.r.remain != c.server.initialReadLimitSize() {
       // If we read any bytes off the wire, we're active.
       c.setState(c.rwc, StateActive, runHooks)
      }
      // 这行代码是重点
      serverHandler{c.server}.ServeHTTP(w, w.req)
      inFlightResponse = nil
      w.cancelCtx()
      if c.hijacked() {
       return
      }
     }
    }
    

    serverHandler

    serverHandler 结构体是一个代理,它会代理 server 的 Handler 或 DefaultServeMux 。

    type serverHandler struct {
     srv *Server
    }
    
    func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
      // 这个 handler 就是最初 http.ListenAndServe 传入的 Handler 类型的 handler 。
     handler := sh.srv.Handler
      // 如果 http.ListenAndServe 第二个参数是 nil,那么使用 DefaultServeMux 。
     if handler == nil {
      handler = DefaultServeMux
     }
     if req.RequestURI == "*" && req.Method == "OPTIONS" {
      handler = globalOptionsHandler{}
     }
      
     if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
      var allowQuerySemicolonsInUse int32
      req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
       atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
      }))
      defer func() {
       if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
        sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
       }
      }()
     }
      // 调用 handler 的 ServeHTTP 方法处理请求。
     handler.ServeHTTP(rw, req)
    }
    

    到这里,连接中的请求,就交给了 handler.ServeHTTP 也即 mux.ServeHTTP 方法来处理。

    然后 mux.ServeHTTP 方法中,mux.Handler 方法,会根据 request 中的 host 和 path 信息,找到对应的 Handler, 这个 Handler 再处理信息。

    总结

    Go net/http 标准库,能让我们轻易地写出一个高性能的 HTTP Server,但肯定不能满足实际业务开发,比如动态路由、中间件、鉴权等这是标准库所不具有的。

    很多重复性的工作和常用的工具与特性要由框架来封装和实现,go 很多高性能框架 比如 Gin 都是直接封装了 net/http,这一点难能可贵,由此可见 Go 标准库的价值。

    所以学习优秀 Go web 框架的前提就是弄清楚 net/http Server 部分的源码,同时,也能方便更好的去使用和优化框架。
    本文所使用的源码均来自 go 1.18.3,部分方法说明翻译自官方注释。

    如有不当之处,请批评指出。



沪ICP备19023445号-2号
友情链接