日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

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: %vn%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 RequestrnrnClient 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 = "rnContent-Type: text/plain; charset=utf-8rnConnection: closernrn"


      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 %sn", 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
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定