golang如何将http请求流转到gin

gin是作为golang web开发中被广泛使用到的框架,了解其内部的实现有助于我们更好地理解gin的设计思想。

这篇文章主要探讨两个问题。

  • http请求如何流转到gin
  • gin为什么比golang的http路由寻找更快

开始之前我们先来看看分别用golang原生的http包实现一个http服务和使用gin实现的代码,先看看原生http包实现的http服务

package main


import (
  "net/http"
)


func main() {
  http.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte(`{"message":"ok"}`))
  })
  http.ListenAndServe(":9090", nil)
}

这段代码做了两件事情,注册路由、启动服务监听9090端口。接下来我们对这段代码进一步分析,在第8行的地方是将路由/ping和对应的处理函数注册到http服务中,我们进入http.HandleFunc()函数看看该函数做了什么事情。

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}

将路由和处理函数注册到了DefaultServeMux中,所以我们先看看DefaultServeMux的结构是什么。

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
}


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

第17行代码就是刚刚用来注册http路由的服务,通过第19行代码知道了他是一个ServeMux类型。知道了DefaultServeMux的类型我们接着看具体的实现代码。

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


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

主要的代码就是第29行,这里将路由和处理函数保存在了ServeMux的m中,通过前面的代码我们知道m是一个map,到这里路由注册的过程就分析完了。接下来我们来看看 http.ListenAndServe()做了什么事情。

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
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)
}

第22行就是真正开始启动http服务,并接受请求的函数。第17行创建了主动套接字并监听套接字,接着我们进入Serve()函数。

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

比较关键的几行代码是第31行和第61行,他们做的事情分别是接收到请求并解析请求数据,使用新的goroutines处理该请求。接着我们需要看看golang具体是如何处理接收到的请求

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
  c.remoteAddr = c.rwc.RemoteAddr().String()
  ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
  defer func() {
    if err := recover(); err != nil && err != ErrAbortHandler {
      const size = 64 << 10
      buf := make([]byte, size)
      buf = buf[:runtime.Stack(buf, false)]
      c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
    }
    if !c.hijacked() {
      c.close()
      c.setState(c.rwc, StateClosed, runHooks)
    }
  }()


  if tlsConn, ok := c.rwc.(*tls.Conn); ok {
    if d := c.server.ReadTimeout; d > 0 {
      c.rwc.SetReadDeadline(time.Now().Add(d))
    }
    if d := c.server.WriteTimeout; d > 0 {
      c.rwc.SetWriteDeadline(time.Now().Add(d))
    }
    if err := tlsConn.HandshakeContext(ctx); err != nil {
      // If the handshake failed due to the client not speaking
      // TLS, assume they're speaking plaintext HTTP and write a
      // 400 response on the TLS conn's underlying net.Conn.
      if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
        io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
        re.Conn.Close()
        return
      }
      c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
      return
    }
    c.tlsState = new(tls.ConnectionState)
    *c.tlsState = tlsConn.ConnectionState()
    if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
      if fn := c.server.TLSNextProto[proto]; fn != nil {
        h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
        // Mark freshly created HTTP/2 as active and prevent any server state hooks
        // from being run on these connections. This prevents closeIdleConns from
        // closing such connections. See issue https://golang.org/issue/39776.
        c.setState(c.rwc, StateActive, skipHooks)
        fn(c.server, tlsConn, h)
      }
      return
    }
  }


  // HTTP/1.x from here on.


  ctx, cancelCtx := context.WithCancel(ctx)
  c.cancelCtx = cancelCtx
  defer cancelCtx()


  c.r = &connReader{conn: c}
  c.bufr = newBufioReader(c.r)
  c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)


  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)
    }
    if err != nil {
      const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"


      switch {
      case err == errTooLarge:
        // Their HTTP client may or may not be
        // able to read this if we're
        // responding to them and hanging up
        // while they're still writing their
        // request. Undefined behavior.
        const publicErr = "431 Request Header Fields Too Large"
        fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
        c.closeWriteAndWait()
        return


      case isUnsupportedTEError(err):
        // Respond as per RFC 7230 Section 3.3.1 which says,
        //      A server that receives a request message with a
        //      transfer coding it does not understand SHOULD
        //      respond with 501 (Unimplemented).
        code := StatusNotImplemented


        // We purposefully aren't echoing back the transfer-encoding's value,
        // so as to mitigate the risk of cross side scripting by an attacker.
        fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
        return


      case isCommonNetReadError(err):
        return // don't reply


      default:
        if v, ok := err.(statusError); ok {
          fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
          return
        }
        publicErr := "400 Bad Request"
        fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
        return
      }
    }


    // Expect 100 Continue support
    req := w.req
    if req.expectsContinue() {
      if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
        // Wrap the Body reader with one that replies on the connection
        req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
        w.canWriteContinue.setTrue()
      }
    } else if req.Header.get("Expect") != "" {
      w.sendExpectationFailed()
      return
    }


    c.curReq.Store(w)


    if requestBodyRemains(req.Body) {
      registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
    } else {
      w.conn.r.startBackgroundRead()
    }


    // HTTP cannot have multiple simultaneous active requests.[*]
    // Until the server replies to this request, it can't read another,
    // so we might as well run the handler in this goroutine.
    // [*] Not strictly true: HTTP pipelining. We could let them all process
    // in parallel even if their responses need to be serialized.
    // But we're not going to implement HTTP pipelining because it
    // was never deployed in the wild and the answer is HTTP/2.
    serverHandler{c.server}.ServeHTTP(w, w.req)
    w.cancelCtx()
    if c.hijacked() {
      return
    }
    w.finishRequest()
    if !w.shouldReuseConnection() {
      if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
        c.closeWriteAndWait()
      }
      return
    }
    c.setState(c.rwc, StateIdle, runHooks)
    c.curReq.Store((*response)(nil))


    if !w.conn.server.doKeepAlives() {
      // We're in shutdown mode. We might've replied
      // to the user without "Connection: close" and
      // they might think they can send another
      // request, but such is life with HTTP/1.1.
      return
    }


    if d := c.server.idleTimeout(); d != 0 {
      c.rwc.SetReadDeadline(time.Now().Add(d))
      if _, err := c.bufr.Peek(4); err != nil {
        return
      }
    }
    c.rwc.SetReadDeadline(time.Time{})
  }
}

关键的代码是第137行将需要返回的response和reques

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  handler := sh.srv.Handler
  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(rw, req)
}

在这里我们终于又和注册路由时候使用的DefaultServeMux见面了,因为在启动服务的时候handler传入的是nil,所以这里默认的使用DefaultServeMux,然而此时的DefaultServeMux已经包含了注册的路由。接下来我们来看看DefaultServeMux的ServeHTTP()是如何实现的。

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}

第11行就是通过通过请求中的路由再返回路由对应的处理函数

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {


  // CONNECT requests are not canonicalized.
  if r.Method == "CONNECT" {
    // If r.URL.Path is /tree and its handler is not registered,
    // the /tree -> /tree/ redirect applies to CONNECT requests
    // but the path canonicalization does not.
    if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
      return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }


    return mux.handler(r.Host, r.URL.Path)
  }


  // All other requests have any port stripped and path cleaned
  // before passing to mux.handler.
  host := stripHostPort(r.Host)
  path := cleanPath(r.URL.Path)


  // If the given path is /tree and its handler is not registered,
  // redirect for /tree/.
  if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
    return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
  }


  if path != r.URL.Path {
    _, pattern = mux.handler(host, path)
    u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
    return RedirectHandler(u.String(), StatusMovedPermanently), pattern
  }


  return mux.handler(host, r.URL.Path)
}

第32行然后接着往下走

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)
  }
  if h == nil {
    h, pattern = mux.match(path)
  }
  if h == nil {
    h, pattern = NotFoundHandler(), ""
  }
  return
}

第7行

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
  // Check for exact match first.
  v, ok := mux.m[path]
  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) {
      return e.h, e.pattern
    }
  }
  return nil, ""
}

第3~5行,如请求的路由有对应的处理函数则返回对应的处理函数。得到了对应的处理函数,然后调用处理函数实现的ServeHTTP()的逻辑

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)


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

通过刚开始注册路由的时候我们传入的处理函数是HandlerFunc类型,而且对应的ServeHTTP()逻辑是运行处理函数,所以到这里逻辑就走到了我们的业务逻辑了,这就是使用golang原生http包实现的http服务具体的实现过程。

接着我们来看看gin的http服务有什么不同,gin中匹配路由和处理函数的的数据结构是Radix Tree,这是前缀树的优化方案

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
  assert1(path[0] == '/', "path must begin with '/'")
  assert1(method != "", "HTTP method can not be empty")
  assert1(len(handlers) > 0, "there must be at least one handler")


  debugPrintRoute(method, path, handlers)


  root := engine.trees.get(method)
  if root == nil {
    root = new(node)
    root.fullPath = "/"
    engine.trees = append(engine.trees, methodTree{method: method, root: root})
  }
  root.addRoute(path, handlers)


  // Update maxParams
  if paramsCount := countParams(path); paramsCount > engine.maxParams {
    engine.maxParams = paramsCount
  }


  if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
    engine.maxSections = sectionsCount
  }
}

第14行向该树添加节点,gin中每一个http请求方法都单独维护了一棵Radix Tree。接着我们看Run()函数做了什么事情

func (engine *Engine) Run(addr ...string) (err error) {
  defer func() { debugPrintError(err) }()


  if engine.isUnsafeTrustedProxies() {
    debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
      "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
  }


  address := resolveAddress(addr)
  debugPrint("Listening and serving HTTP on %s\n", address)
  err = http.ListenAndServe(address, engine)
  return
}

第11行将我们将建的gin实例作为handler传入ListenAndServe,之后的逻辑就是http包原生的逻辑,唯一不同的是最后调用的ServeHTTP是gin的实现而不是DefaultServeMux的实现接下来我们看看gin的ServeHTTP实现

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  c := engine.pool.Get().(*Context)
  c.writermem.reset(w)
  c.Request = req
  c.reset()


  engine.handleHTTPRequest(c)


  engine.pool.Put(c)
}

gin将请求包装成Context然后调用handleHTTPRequest在Radix Tree找到路由对应的处理函数,并调用该函函数。

func (engine *Engine) handleHTTPRequest(c *Context) {
  httpMethod := c.Request.Method
  rPath := c.Request.URL.Path
  unescape := false
  if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
    rPath = c.Request.URL.RawPath
    unescape = engine.UnescapePathValues
  }


  if engine.RemoveExtraSlash {
    rPath = cleanPath(rPath)
  }


  // Find root of the tree for the given HTTP method
  t := engine.trees
  for i, tl := 0, len(t); i < tl; i++ {
    if t[i].method != httpMethod {
      continue
    }
    root := t[i].root
    // Find route in tree
    value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
    if value.params != nil {
      c.Params = *value.params
    }
    if value.handlers != nil {
      c.handlers = value.handlers
      c.fullPath = value.fullPath
      c.Next()
      c.writermem.WriteHeaderNow()
      return
    }
    if httpMethod != "CONNECT" && rPath != "/" {
      if value.tsr && engine.RedirectTrailingSlash {
        redirectTrailingSlash(c)
        return
      }
      if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
        return
      }
    }
    break
  }


  if engine.HandleMethodNotAllowed {
    for _, tree := range engine.trees {
      if tree.method == httpMethod {
        continue
      }
      if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
        c.handlers = engine.allNoMethod
        serveError(c, http.StatusMethodNotAllowed, default405Body)
        return
      }
    }
  }
  c.handlers = engine.allNoRoute
  serveError(c, http.StatusNotFound, default404Body)
}

第22~31行获取处理函数,并执行中间件和处理函数。

到这里我们就一起知道了http请求是如何从golang流转到gin的,只要我们自己定义的结构体实现了ServeHTTP函数并在启动服务使用我们自己实现的handler类型的结构体,那么最后就会流转的自定义的http handler。

通过分析我们知道原生的DefaultServeMux路由和处理函数对应关系使用的是map,而gin使用的是Radix Tree,所以gin比原生http快的原因就是这两种数据结构的的性能差别,map在最糟糕的条件下时间复杂度会变成O(n)也就是所有的key hash只有相同,最后变成链表。而且由于map的性质,所有的key不是很可能不是连续的,所有可能造成空间浪费。

关于gin的学习今天就到这里,有什么错误的地方希望指正。

微信公众号:
golang如何将http请求流转到gin_第1张图片

你可能感兴趣的:(golang,golang,http,开发语言)