自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

高性能 Gin 框架原理學習教程

開發(fā)
Gin是一款高性能的Go語言Web框架,本文我們從源碼層面學習一下其特性和原理。

作者 | jiayan

工作中的部分項目使用到了gin框架,因此從源碼層面學習一下其原理。

一、概述

Gin是一款高性能的Go語言Web框架,Gin的一些特性:

  • 快速 基于 Radix 樹的路由,小內(nèi)存占用,沒有反射,可預測的 API 性能。
  • 支持中間件 傳入的 HTTP 請求可以由一系列中間件和最終操作來處理,例如:Logger,Authorization,GZIP,最終操作 DB。
  • 路由組 組織路由組非常方便,同時這些組可以無限制地嵌套而不會降低性能。
  • 易用性 gin封裝了標準庫的底層能力。標準庫暴露給開發(fā)者的函數(shù)參數(shù)是(w http.ResponseWriter, req *http.Request),易用性低,需要直接從請求中讀取數(shù)據(jù)、反序列化,響應時手動序列化、設置Content-Type、寫響應內(nèi)容,返回碼等。使用gin可以幫我們更專注于業(yè)務處理。

二、源碼學習

下面從一個簡單的包含基礎路由和路由組路由的demo開始分析:

func main() {
 // 初始化
 mux := gin.Default()

 // 設置全局通用handlers,這里是設置了engine的匿名成員RouterGroup的Handlers成員
 mux.Handlers = []gin.HandlerFunc{
  func(c *gin.Context) {
   log.Println("log 1")
   c.Next()
  },
  func(c *gin.Context) {
   log.Println("log 2")
   c.Next()
  },
 }

 // 綁定/ping 處理函數(shù)
 mux.GET("/ping", func(c *gin.Context) {
  c.String(http.StatusOK, "ping")
 })
 mux.GET("/pong", func(c *gin.Context) {
  c.String(http.StatusOK, "pong")
 })
 mux.GET("/ping/hello", func(c *gin.Context) {
  c.String(http.StatusOK, "ping hello")
 })

 mux.GET("/about", func(c *gin.Context) {
  c.String(http.StatusOK, "about")
 })

 // system組
 system := mux.Group("system")
 // system->auth組
 systemAuth := system.Group("auth")
 {
  // 獲取管理員列表
  systemAuth.GET("/addRole", func(c *gin.Context) {
   c.String(http.StatusOK, "system/auth/addRole")
  })
  // 添加管理員
  systemAuth.GET("/removeRole", func(c *gin.Context) {
   c.String(http.StatusOK, "system/auth/removeRole")
  })
 }
 // user組
 user := mux.Group("user")
 // user->auth組
 userAuth := user.Group("auth")
 {
  // 登陸
  userAuth.GET("/login", func(c *gin.Context) {
   c.String(http.StatusOK, "user/auth/login")
  })
  // 注冊
  userAuth.GET("/register", func(c *gin.Context) {
   c.String(http.StatusOK, "user/auth/register")
  })
 }

 mux.Run("0.0.0.0:8080")
}

1. 初始化Gin(Default函數(shù))

初始化步驟主要是初始化engine與加載兩個默認的中間件:

func Default(opts ...OptionFunc) *Engine {
    debugPrintWARNINGDefault()
 // 初始化engine實例
    engine := New()
 // 默認加載log & recovery中間件
    engine.Use(Logger(), Recovery())
    return engine.With(opts...)
}

(1) 初始化engine

engine是gin中的核心對象,gin通過 Engine 對象來定義服務路由信息、組裝插件、運行服務,是框架的核心發(fā)動機,整個 Web 服務的都是由它來驅(qū)動的 關鍵字段:

  • RouterGroup: RouterGroup 是對路由樹的包裝,所有的路由規(guī)則最終都是由它來進行管理。RouteGroup 對象里面還會包含一個 Engine 的指針,可以調(diào)用engine的addRoute函數(shù)。
  • trees: 基于前綴樹實現(xiàn)。每個節(jié)點都會掛接若干請求處理函數(shù)構成一個請求處理鏈 HandlersChain。當一個請求到來時,在這棵樹上找到請求 URL 對應的節(jié)點,拿到對應的請求處理鏈來執(zhí)行就完成了請求的處理。
  • addRoute: 用于添加 URL 請求處理器,它會將對應的路徑和處理器掛接到相應的請求樹中。
  • RouterGroup: 內(nèi)部有一個前綴路徑屬性(basePath),它會將所有的子路徑都加上這個前綴再放進路由樹中。有了這個前綴路徑,就可以實現(xiàn) URL 分組功能。
func New(opts ...OptionFunc) *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
       RouterGroup: RouterGroup{
          Handlers: nil,
      // 默認的basePath為/,綁定路由時會用到此參數(shù)來計算絕對路徑
          basePath: "/",
          root:     true,
       },
       FuncMap:                template.FuncMap{},
       RedirectTrailingSlash:  true,
       RedirectFixedPath:      false,
       HandleMethodNotAllowed: false,
       ForwardedByClientIP:    true,
       RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
       TrustedPlatform:        defaultPlatform,
       UseRawPath:             false,
       RemoveExtraSlash:       false,
       UnescapePathValues:     true,
       MaxMultipartMemory:     defaultMultipartMemory,
       trees:                  make(methodTrees, 0, 9),
       delims:                 render.Delims{Left: "{{", Right: "}}"},
       secureJSONPrefix:       "while(1);",
       trustedProxies:         []string{"0.0.0.0/0", "::/0"},
       trustedCIDRs:           defaultTrustedCIDRs,
    }
    engine.RouterGroup.engine = engine
 // 池化gin核心context對象,有新請求來時會使用到該池
    engine.pool.New = func() any {
       return engine.allocateContext(engine.maxParams)
    }
    return engine.With(opts...)
}

(2) 初始化中間件

① Engine.Use函數(shù)

Engine.Use函數(shù)用于將中間件添加到當前的路由上,位于gin.go中,代碼如下:

// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
 engine.RouterGroup.Use(middleware...)
 engine.rebuild404Handlers()
 engine.rebuild405Handlers()
 return engine
}

② RouterGroup.Use函數(shù)

實際上,還需要進一步調(diào)用engine.RouterGroup.Use(middleware...)完成實際的中間件注冊工作,函數(shù)位于gin.go中,代碼如下:

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
 group.Handlers = append(group.Handlers, middleware...)
 return group.returnObj()
}

實際上就是把中間件(本質(zhì)是一個函數(shù))添加到HandlersChain類型(實質(zhì)上為數(shù)組type HandlersChain []HandlerFunc)的group.Handlers中。

③ HandlerFunc

type HandlerFunc func(*Context)

如果需要實現(xiàn)一個中間件,那么需要實現(xiàn)該類型,函數(shù)參數(shù)只有*Context。

④ gin.Context

貫穿一個 http 請求的所有流程,包含全部上下文信息。

提供了很多內(nèi)置的數(shù)據(jù)綁定和響應形式,JSON、HTML、Protobuf 、MsgPack、Yaml 等,它會為每一種形式都單獨定制一個渲染器

engine的 ServeHTTP 函數(shù),在響應一個用戶的請求時,都會先從臨時對象池中取一個context對象。使用完之后再放回臨時對象池。為了保證并發(fā)安全,如果在一次請求新起一個協(xié)程,那么一定要copy這個context進行參數(shù)傳遞。

type Context struct {
    writermem responseWriter
    Request   *http.Request  // 請求對象
    Writer    ResponseWriter // 響應對象
    Params   Params  // URL 匹配參數(shù)
    handlers HandlersChain // // 請求處理鏈
    ...
}

2. 綁定處理函數(shù)到對應的HttpMethod上

(1) 普通路由實現(xiàn)

調(diào)用綁定函數(shù):

mux.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "ping")
})

函數(shù)實際上走到了engine對象的匿名成員RouterGroup的handle函數(shù)中

// POST is a shortcut for router.Handle("POST", path, handlers). 
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPost, relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handlers). 
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodGet, relativePath, handlers)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handlers). 
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodDelete, relativePath, handlers)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handlers). 
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPatch, relativePath, handlers)
}

// PUT is a shortcut for router.Handle("PUT", path, handlers). 
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPut, relativePath, handlers)
}

綁定邏輯:

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    // 使用相對路徑與路由組basePath 計算絕對路徑
    absolutePath := group.calculateAbsolutePath(relativePath)
 // 將函數(shù)參數(shù)中的 "處理函數(shù)" handlers與本路由組已有的Handlers組合起來,作為最終要執(zhí)行的完整handlers列表
    handlers = group.combineHandlers(handlers)
 // routerGroup會存有engine對象的引用,調(diào)用engine的addRoute將絕對路徑與處理函數(shù)列表綁定起來
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

從源碼中 handlers = group.combineHandlers(handlers) 可以看出我們也可以給gin設置一些全局通用的handlers,這些handlers會綁定到所有的路由方法上,如下:

// 設置全局通用handlers,這里是設置了engine的匿名成員RouterGroup的Handlers成員
mux.Handlers = []gin.HandlerFunc{
    func(c *gin.Context) {
       log.Println("log 1")
       c.Next()
    },
    func(c *gin.Context) {
       log.Println("log 2")
       c.Next()
    },
}

addRoute函數(shù):

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)
    
 // 每個HTTP方法(如:GET,POST)的路由信息都各自由一個樹結(jié)構來維護,該樹結(jié)構的模型與函數(shù)實現(xiàn)位于gin/tree.go中,此處不再繼續(xù)展開。不同http方法的樹根節(jié)點組成了 engine.trees 這個數(shù)組
 // 從engine的路由樹數(shù)組中遍歷找到該http方法對應的路由樹的根節(jié)點
    root := engine.trees.get(method)
    if root == nil {
    // 如果根節(jié)點不存在,那么新建根節(jié)點
       root = new(node)
       root.fullPath = "/"
    // 將根節(jié)點添加到路由樹數(shù)組中
       engine.trees = append(engine.trees, methodTree{method: method, root: root})
 }
 // 調(diào)用根節(jié)點的addRoute函數(shù),將絕對路徑與處理函數(shù)鏈綁定起來
    root.addRoute(path, handlers)
 
 ...
}
// 路由樹數(shù)組數(shù)據(jù)結(jié)構
type methodTree struct {
    method string     root   *node 
}

type methodTrees []methodTree

(2) 路由組的實現(xiàn)

// system組
system := mux.Group("system")
// system->auth組
systemAuth := system.Group("auth")
{
 // 獲取管理員列表
 systemAuth.GET("/addRole", func(c *gin.Context) {
  c.String(http.StatusOK, "system/auth/addRole")
 })
 // 添加管理員
 systemAuth.GET("/removeRole", func(c *gin.Context) {
  c.String(http.StatusOK, "system/auth/removeRole")
 })
}
// user組
user := mux.Group("user")
// user->auth組
userAuth := user.Group("auth")
{
 // 登陸
 userAuth.GET("/login", func(c *gin.Context) {
  c.String(http.StatusOK, "user/auth/login")
 })
 // 注冊
 userAuth.GET("/register", func(c *gin.Context) {
  c.String(http.StatusOK, "user/auth/register")
 })
} 

Group函數(shù)會返回一個新的RouterGroup對象,每一個RouterGroup都會基于原有的RouterGroup而生成:

  • 這里demo中生成的systemGroup,會基于根routerGroup(basePath:"/")而生成,那么systemGroup的basePath為 joinPaths("/" + "system")
  • 基于systemGroup生成的systemAuthGroup,basePath為sysetmGroup的basePath: "/system" 與函數(shù)參數(shù)中"auth"組成join("/system", "auth"),
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix. // For example, all the routes that use a common middleware for authorization could be grouped. 
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
    return &RouterGroup{
    // 新routerGroup的handlers由原routerGroup的handlers和本次傳入的handlers組成
       Handlers: group.combineHandlers(handlers),
    // 新routerGroup的basePath由原routerGroup的basePath + relativePath 計算出來
       basePath: group.calculateAbsolutePath(relativePath),
    // 新routerGroup依舊持久全局engine對象
       engine:   group.engine,
    }
}

// 合并handlers,將group的handlers與傳入?yún)?shù)的handlers合并
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    assert1(finalSize < int(abortIndex), "too many handlers")
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}

// 拼裝routerGroup的basePath與傳入的相對路徑relativePath
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    return joinPaths(group.basePath, relativePath)
}

需要注意的一點是,每個routerGroup都持有全局engine對象,調(diào)用Group()生成新RouterGroup后,再調(diào)用GET, POST..綁定路由時,依舊會使用全局engine對象的handle方法,最終會走到:

group.engine.addRoute(httpMethod, absolutePath, handlers)

根據(jù)http方法找到對應的路由樹根節(jié)點,然后再更新路由樹。

3. 啟動Gin

(1) Engine.Run函數(shù)

// Run attaches the router to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. 
func (engine *Engine) Run(addr ...string) (err error) {
 ...
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine.Handler())
    return 
}

可以看到,最核心的監(jiān)聽與服務實質(zhì)上是調(diào)用Go語言內(nèi)置庫net/http的http.ListenAndServe函數(shù)實現(xiàn)的。

(2) net/http的ListenAndServe函數(shù)

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


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

ListenAndServe函數(shù)實例化Sever,調(diào)用其ListenAndServe函數(shù)實現(xiàn)監(jiān)聽與服務功能。在gin中,Engine對象以Handler接口的對象的形式被傳入給了net/http庫的Server對象,作為后續(xù)Serve對象處理網(wǎng)絡請求時調(diào)用的函數(shù)。

(3) 標準庫中的Handler接口

net/http的Server結(jié)構體類型中有一個Handler接口類型的Handler。

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
 // Addr optionally specifies the TCP address for the server to listen on,
 // in the form "host:port". If empty, ":http" (port 80) is used.
 // The service names are defined in RFC 6335 and assigned by IANA.
 // See net.Dial for details of the address format.
 Addr string

 Handler Handler // handler to invoke, http.DefaultServeMux if nil
    // ...
}


//Handler接口有且只有一個函數(shù),任何類型,只需要實現(xiàn)了該ServeHTTP函數(shù),就實現(xiàn)了Handler接口,就可以用作Server的Handler,供HTTP處理時調(diào)用。
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

(4) 標準庫中的Server.Serve函數(shù)

Server.Serve函數(shù)用于監(jiān)聽、接受和處理網(wǎng)絡請求,代碼如下:

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
 //...
 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)
 }
}

在Server.Serve函數(shù)的實現(xiàn)中,啟動了一個無條件的for循環(huán)持續(xù)監(jiān)聽、接受和處理網(wǎng)絡請求,主要流程為:

  • 接受請求:l.Accept()調(diào)用在無請求時保持阻塞,直到接收到請求時,接受請求并返回建立的連接;
  • 處理請求:啟動一個goroutine,使用標準庫中conn連接對象的serve函數(shù)進行處理(go c.serve(connCtx));

(5) conn.serve函數(shù)

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) 
    ... 
    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) 
        ... 
        // 根據(jù)請求路由調(diào)用處理器處理請求
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest() 
        ...
    }
}

一個連接建立之后,該連接中所有的請求都將在這個協(xié)程中進行處理,直到連接被關閉。在 for 循環(huán)里面會循環(huán)調(diào)用 readRequest 讀取請求進行處理??梢栽诘?6行看到請求處理是通過調(diào)用 serverHandler結(jié)構體的ServeHTTP函數(shù)進行的。

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
       handler = DefaultServeMux
    }
    if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
       handler = globalOptionsHandler{}
    }

    handler.ServeHTTP(rw, req)
}

可以看到上面第八行 handler := sh.srv.Handler,在gin框架中,sh.srv.Handler其實就是engine.Handler()。

func (engine *Engine) Handler() http.Handler {
    if !engine.UseH2C {
       return engine
    }
 // 使用了標準庫的h2c(http2 client)能力,本質(zhì)還是使用了engine對象自身的ServeHTTP函數(shù)
    h2s := &http2.Server{}
    return h2c.NewHandler(engine, h2s)
}

engine.Handler()函數(shù)使用了http2 server的能力,實際的邏輯處理還是依賴engine自身的ServeHTTP()函數(shù)。

(6) Gin的Engine.ServeHTTP函數(shù)

gin在gin.go中實現(xiàn)了ServeHTTP函數(shù),代碼如下:

// ServeHTTP conforms to the http.Handler interface.
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)
}

主要步驟為:

  • 建立連接上下文:從全局engine的緩存池中提取上下文對象,填入當前連接的http.ResponseWriter實例與http.Request實例;
  • 處理連接:以上下文對象的形式將連接交給函數(shù)處理,由engine.handleHTTPRequest(c)封裝實現(xiàn)了;
  • 回收連接上下文:處理完畢后,將上下文對象回收進緩存池中。

gin中對每個連接都需要的上下文對象進行緩存化存取,通過緩存池節(jié)省高并發(fā)時上下文對象頻繁創(chuàng)建銷毀造成內(nèi)存頻繁分配與釋放的代價。

(7) Gin的Engine.handleHTTPRequest函數(shù)

handleHTTPRequest函數(shù)封裝了對請求進行處理的具體過程,位于gin/gin.go中,代碼如下:

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
 // 根據(jù)http方法找到路由樹數(shù)組中對應的路由樹根節(jié)點
 t := engine.trees
 for i, tl := 0, len(t); i < tl; i++ {
  if t[i].method != httpMethod {
   continue
  }
        // 找到了根節(jié)點
  root := t[i].root
  // Find route in tree
  // 使用請求參數(shù)和請求路徑從路由樹中找到對應的路由節(jié)點
  value := root.getValue(rPath, c.params, unescape)
  if value.params != nil {
   c.Params = *value.params
  }
  
  // 調(diào)用context的Next()函數(shù),實際上也就是調(diào)用路由節(jié)點的handlers方法鏈
  if value.handlers != nil {
   c.handlers = value.handlers
   c.fullPath = value.fullPath
   c.Next()
   c.writermem.WriteHeaderNow()
   return
  }
  // ...
  break
 }

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

Engine.handleHTTPRequest函數(shù)的主要邏輯位于中間的for循環(huán)中,主要為:

  • 遍歷查找engine.trees以找出當前請求的HTTP Method對應的處理樹;
  • 從該處理樹中,根據(jù)當前請求的路徑與參數(shù)查詢出對應的處理函數(shù)value;
  • 將查詢出的處理函數(shù)鏈(gin.HandlerChain)寫入當前連接上下文的c.handlers中;
  • 執(zhí)行c.Next(),調(diào)用handlers鏈上的下一個函數(shù)(中間件/業(yè)務處理函數(shù)),開始形成LIFO的函數(shù)調(diào)用棧;
  • 待函數(shù)調(diào)用棧全部返回后,c.writermem.WriteHeaderNow()根據(jù)上下文信息,將HTTP狀態(tài)碼寫入響應頭。

四、Gin 路由樹

gin的路由樹源碼上面沒有展開,實際上就是實現(xiàn)了radix tree的數(shù)據(jù)結(jié)構:

  • trie tree: 前綴樹,一顆多叉樹,用于字符串搜索,每個樹節(jié)點存儲一個字符,從根節(jié)點到任意一個葉子結(jié)點串起來就是一個字符串。
  • radix tree: 基數(shù)樹(壓縮前綴樹),對空間進一步壓縮,從上往下提取公共前綴,非公共部分存到子節(jié)點,這樣既節(jié)省了空間,同時也提高了查詢效率(左邊字符串sleep查詢需要5步, 右邊只需要3步)。

實際看下demo中的代碼會生成的radix tree:

mux.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "ping")
})
mux.GET("/pong", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
})
mux.GET("/ping/hello", func(c *gin.Context) {
    c.String(http.StatusOK, "ping hello")
})

mux.GET("/about", func(c *gin.Context) {
    c.String(http.StatusOK, "about")
})

實際上源碼中的基數(shù)樹還涉及可變參數(shù)路由的處理,會更復雜一些。

五、Gin 中間件實現(xiàn)

每個路由節(jié)點都會掛載一個函數(shù)鏈,鏈的前面部分是插件函數(shù),后面部分是業(yè)務處理函數(shù)。

在 gin 中插件和業(yè)務處理函數(shù)形式是一樣的,都是 func(*Context)。當我們定義路由時,gin 會將插件函數(shù)和業(yè)務處理函數(shù)合并在一起形成一個鏈條結(jié)構。gin 在接收到客戶端請求時,找到相應的處理鏈,構造一個 Context 對象,再調(diào)用它的 Next() 函數(shù)就正式進入了請求處理的全流程。

慣用法: 可以通過在處理器中調(diào)用c.Next()提前進入下一個處理器,待其執(zhí)行完后再返回到當前處理器,這種比較適合需要對請求做前置和后置處理的場景,如請求執(zhí)行時間統(tǒng)計,請求的前后日志等。

有時候我們可能會希望,某些條件觸發(fā)時直接返回,不再繼續(xù)后續(xù)的處理操作。Context提供了Abort方法幫助我們實現(xiàn)這樣的目的。原理是將 Context.index 調(diào)整到一個比較大的數(shù)字,gin中要求一個路由的全部處理器個數(shù)不超過63,每次執(zhí)行一個處理器時,會先判斷index是否超過了這個限制,如果超過了就不會執(zhí)行。

// Next should be used only inside middleware. // It executes the pending handlers in the chain inside the calling handler. // See example in GitHub. 
func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
       if c.handlers[c.index] == nil {
          continue        }
       c.handlers[c.index](c)
       c.index++
    }
}

func (c *Context) Abort() {
    c.index = abortIndex 
}  
const abortIndex int8 = math.MaxInt8 >> 1

總結(jié)

從源碼中可以看到gin與fasthttp庫基于自身的http庫來實現(xiàn)網(wǎng)絡層不同,gin是基于標準http庫的能力來構建的自己的網(wǎng)絡層的,所以性能應該是與原生http庫接近的。

  • gin通過實現(xiàn)Go語言提供的接口快捷地接入Go的內(nèi)置庫功能,使得上層應用與底層實現(xiàn)之間互不依賴。
  • gin在性能上針對HTTP Web框架常見的高并發(fā)問題進行了優(yōu)化,例如:通過上下文對象的緩存池節(jié)省連接高并發(fā)時內(nèi)存頻繁申請與釋放的代價
  • gin的壓縮前綴樹數(shù)據(jù)結(jié)構設計,不同于標準庫中基于map的路由,實現(xiàn)了高效的路由查找(匹配時間復雜度是O(k)),另外可以支持模糊匹配、參數(shù)匹配等功能,具有更高的靈活性和可擴展性。
  • gin采用中間件(Middleware)來處理請求和響應,可以實現(xiàn)各種功能,例如日志記錄、權限驗證、請求限流等。
責任編輯:趙寧寧 來源: 騰訊技術工程
相關推薦

2024-11-04 08:16:08

Go語言Web 框架

2024-08-12 08:43:09

2022-08-15 08:01:35

微服務框架RPC

2024-09-03 09:15:37

2017-08-07 21:10:55

MySQLUbuntusysbench

2020-06-17 16:43:40

網(wǎng)絡IO框架

2023-10-31 18:52:29

網(wǎng)絡框架XDP技術

2011-08-25 10:07:24

Lua 5.0函數(shù)編譯器

2019-07-31 14:36:46

Linux服務器框架

2018-02-28 10:11:50

騰訊框架開源

2013-09-17 14:00:19

AndroidListView原理

2025-01-13 13:00:00

Go網(wǎng)絡框架nbio

2024-04-28 10:17:30

gnetGo語言

2024-02-26 07:43:10

大語言模型LLM推理框架

2022-06-28 08:42:03

磁盤kafka高性能

2021-06-21 17:00:05

云計算Hologres云原生

2025-03-04 08:00:00

機器學習Rust開發(fā)

2012-08-08 10:10:31

PHP

2021-08-11 05:06:23

NETJSON框架

2020-03-13 07:40:36

Plato數(shù)據(jù)分析
點贊
收藏

51CTO技術棧公眾號