Go net/http源码分析

133 阅读5分钟

简介

net/http 作为 Go 语言标准库,用来编写 Web服务非常方便,但为了更深入理解其运行原理,还需要进行源码分析。

1、基础使用

//用户自定义实现功能处理
func (g greeting) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, g)
}

//用户自定义实现功能处理
func index(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintln(w, "Hello World")
}

func TestInitialWeb(t *testing.T) {
   //1.基本使用方式,使用默认对象 DefaultServeMux(不安全,不推荐)
   //两种方式注册 处理器/函数 到默认对象 DefaultServeMux
   http.HandleFunc("/", index)
   http.Handle("/greeting/", greeting("Welcome ,dj"))
   http.ListenAndServe(":8080", nil)
}

通过 http.HandleFunc、http.Handle 注册处理器函数到指定的路径上,这两种处理方式对应接口如下,发现底层实际上都是通过 DefautltServeMux 这个对象的相关方法来处理。

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

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

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

DefaultServeMuxServeMux 结构体的对象,ServeMux 结构体主要包含了互斥锁对象、普通的map对象(map的value为muxEntry结构体,而muxEntry结构体包含路径pattern和对应的Handler处理函数)、muxEntry结构体数组。那么就可以猜测到在处理时通过 muxEntry结构体数组查询请求路径所匹配到的pattern,然后获取对应的 handler处理器进行处理。

type ServeMux struct {
   mu    sync.RWMutex         //互斥锁
   m     map[string]muxEntry  //注册handler、pattern
   es    []muxEntry           //前缀匹配
   hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
   h       Handler
   pattern string
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

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

var defaultServeMux ServeMux

2、HandlerFunc

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   if handler == nil {
      panic("http: nil handler")
   }
   mux.Handle(pattern, HandlerFunc(handler))   //这里将handler转化为HandlerFunc类型
}

//HandlerFunc类型是函数类型
type HandlerFunc func(ResponseWriter, *Request)

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

//实现了ServeHTTP接口
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
   f(w, r)
}
//两种不同注册方法的最终调用
func (mux *ServeMux) Handle(pattern string, handler Handler) {
   mux.mu.Lock()
   defer mux.mu.Unlock()

   if pattern == "" {
      panic("http: invalid pattern")
   }
   if handler == nil {
      panic("http: nil handler")
   }
   if _, exist := mux.m[pattern]; exist {
      panic("http: multiple registrations for " + pattern)
   }

   if mux.m == nil {
      mux.m = make(map[string]muxEntry)
   }
   e := muxEntry{h: handler, pattern: pattern}
   mux.m[pattern] = e
   if pattern[len(pattern)-1] == '/' {
      mux.es = appendSorted(mux.es, e)
   }

   if pattern[0] != '/' {
      mux.hosts = true
   }
}

继续深入可以发现 HandlerFunc 类型为函数类型,该类型实现了 Handler接口的 ServerHTTP 方法。http.HandleFunc("/",index) 方法在内部将handler 转化为 HandlerFunc 类型,此时和 http.Handle("/greeting", greeting("xxxx")) 方法最终调用相同的处理函数。也就是说 HandlerFunc 类型只是为了方便注册函数处理器。

3、Handle

func (mux *ServeMux) Handle(pattern string, handler Handler) {
   mux.mu.Lock()                    //并发加锁
   defer mux.mu.Unlock()            //执行完解锁

   if pattern == "" {
      panic("http: invalid pattern")
   }
   if handler == nil {
      panic("http: nil handler")
   }
   if _, exist := mux.m[pattern]; exist {
      panic("http: multiple registrations for " + pattern)
   }

   if mux.m == nil {
      mux.m = make(map[string]muxEntry)
   }
   e := muxEntry{h: handler, pattern: pattern}
   mux.m[pattern] = e
   if pattern[len(pattern)-1] == '/' {
      mux.es = appendSorted(mux.es, e)
   }

   if pattern[0] != '/' {
      mux.hosts = true
   }
}

Handle 方法主要在进行路径和处理函数的键值对注册,会把生成的 muxEntry 对象存储到 ServerMux 结构体中。

注意这里在注册完毕后还查看了路径 pattern是否以 '/' 结尾,如果是则将根路径处理注册到 ServerMux 结构体。它保证了在请求访问时先进行精准匹配,如果不能匹配到再根据注册的根路径处理进行最长前缀匹配。

4、ListenAndServe

func ListenAndServe(addr string, handler Handler) error {
   server := &Server{Addr: addr, Handler: handler}
   return 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)    //listen建立socket
   if err != nil {
      return err
   }
   return srv.Serve(ln)
}


func (srv *Server) Serve(l net.Listener) error {
   if fn := testHookServerServe; fn != nil {
      fn(srv, l) // call hook with unwrapped listener
   }

   origListener := l
   l = &onceCloseListener{Listener: l}
   defer l.Close()

   if err := srv.setupHTTP2_Serve(); err != nil {
      return err
   }

   if !srv.trackListener(&l, true) {
      return ErrServerClosed
   }
   defer srv.trackListener(&l, false)

   baseCtx := context.Background()
   if srv.BaseContext != nil {
      baseCtx = srv.BaseContext(origListener)
      if baseCtx == nil {
         panic("BaseContext returned a nil context")
      }
   }

   var tempDelay time.Duration // how long to sleep on accept failure

   ctx := context.WithValue(baseCtx, ServerContextKey, srv)
   for {
      rw, err := l.Accept()       //accept阻塞等待客户端请求
      if err != nil {
         select {
         case <-srv.getDoneChan():
            return ErrServerClosed
         default:
         }
         if ne, ok := err.(net.Error); ok && ne.Temporary() {  //指数退避策略应对连接出错
            if tempDelay == 0 {
               tempDelay = 5 * time.Millisecond
            } else {
               tempDelay *= 2
            }
            if max := 1 * time.Second; tempDelay > max {
               tempDelay = max
            }
            srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
            time.Sleep(tempDelay)
            continue
         }
         return err
      }
      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)            //处理
   }
}

ListenAndServe 方法按照 socket 的建立流程进行,通过 net.Listen("tcp", addr) 创建socket;然后通过 l.Accept() 阻塞地等待客户端连接,连接成功后通过 c.serve(connCtx) 进行请求处理。

注意如果连接失败不会立刻放弃本次请求,需要判断错误是不是临时性地,如果是临时错误睡眠一段时间后重试。通过指数退避策略,默认第一次睡眠 5毫秒,每次睡眠时间翻倍,最多睡眠 1秒。

5、回调接口 ServeHTTP

func (c *conn) serve(ctx context.Context) {
    // ... 省略代码
    serverHandler{c.server}.ServeHTTP(w, w.req)
    w.cancelCtx()
    if c.hijacked() {
      return
    }
    w.finishRequest()
    // ... 省略代码
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {               //handler为ListenAndServe方法第二个参数,传递为nil
      handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
      handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

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)
}

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
   mux.mu.RLock()
   defer mux.mu.RUnlock()

   // Host-specific pattern takes precedence over generic ones
   if mux.hosts {
      h, pattern = mux.match(host + path)    //handler匹配
   if h == nil {
      h, pattern = mux.match(path)
   }
   if h == nil {
      h, pattern = NotFoundHandler(), ""
   }
   return
}

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
   // Check for exact match first.
   v, ok := mux.m[path]                             //先查找 mux.m
   if ok {
      return v.h, v.pattern
   }

   // Check for longest valid match.  mux.es contains all patterns
   // that end in / sorted from longest to shortest.
   for _, e := range mux.es {
      if strings.HasPrefix(path, e.pattern) {       //后查找 mux.es
         return e.h, e.pattern
      }
   }
   return nil, ""
}

回调函数serveHTTP中对路径进行了匹配,先进行精准匹配,随后进行最长前缀匹配。这个匹配规则过于简单,所以 Gin 等其它框架对这里进行了更详细的优化处理。

所以整体的请求处理流程整理如下:

  1. ln, err := net.Listen(“tcp”, addr)做了初试化了socket, bind, listen的操作.
  2. rw, e := l.Accept()进行accept, 等待客户端进行连接
  3. go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发操作
  4. h, _ := mux.Handler(r) 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端