Go Gin 中間件源碼解析和例子

Gin 的中間件,本質是一個匿名回調函數。這和綁定到一個路徑下的處理函數本質是一樣的。

再以 Engine 的 Default 方法爲例

func Default() *Engine {
 debugPrintWARNINGDefault()
 engine := New()
 engine.Use(Logger(), Recovery())
 return engine
}

第 4 行就讓該 Engine 使用了 Logger 和 Revoery 兩個中間件。Use 方法將新增的中間件加入到中間件集合中

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

因爲是 append,所以後加入的中間件排在集合後面。理解這個特性對我們正確使用中間件很重要。

再回顧下路由的代碼

 r := gin.Default()

 // Ping test
 r.GET("/ping", func(c *gin.Context) {
  c.String(http.StatusOK, "pong")
 })

host:port/ping 下的請求,將被路由到輸出 pong 的匿名函數里。GET 方法封裝了 handle 方法

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
 absolutePath := group.calculateAbsolutePath(relativePath)
 handlers = group.combineHandlers(handlers)
 group.engine.addRoute(httpMethod, absolutePath, handlers)
 return group.returnObj()
}

這兒注意下第 3 行,上面這個匿名函數似乎是和其他匿名函數合併成一個匿名函數集合。然後再在第 4 行和絕對路徑綁定。

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
 finalSize := len(group.Handlers) + len(handlers)
 if finalSize >= int(abortIndex) {
  panic("too many handlers")
 }
 mergedHandlers := make(HandlersChain, finalSize)
 copy(mergedHandlers, group.Handlers)
 copy(mergedHandlers[len(group.Handlers):], handlers)
 return mergedHandlers
}

這兒合併的就是中間件集合(group.Handlers)。第 7~8 行代碼,告訴我們中間件的回調要先於用戶定義的路徑處理函數。那麼上例中,mergeHandlers 中的成員是【logger 回調,recovery 回調,GET 的匿名回調】。

這樣,每個路徑的回調函數鏈都將包含中間件的回調,即【logger 回調,recovery 回調】。

我再看一個最簡單的中間件的實現

func MiddlewareDemo() gin.HandlerFunc {
 return func(c *gin.Context) {
  c.Next()
 }
}

這個中間件只是返回了一個匿名函數,該函數內部需要調用 Conext 的 Next 函數來驅動執行之後的 handler。

func (c *Context) Next() {
 c.index++
 for s := int8(len(c.handlers)); c.index < s; c.index++ {
  c.handlers[c.index](c)
 }
}

這也是 Gin 設計中比較奇葩的地方:

如果我們不看 Next 的實現,單從上面的話中可以感覺到似乎邏輯進入了一種異常循環的狀態。其實 Gin 使用了一個 Context 中的 index 變量來解決了這個問題。於是中間件、框架和路徑對應的回調之前的關係是

我們看個例子

package main

import (
 "log"
 "net/http"

 "github.com/gin-gonic/gin"
)

func MiddlewareA() gin.HandlerFunc {
 return func(c *gin.Context) {
  log.Println("MiddlewareA before request")
  // before request
  c.Next()
  // after request
  log.Println("MiddlewareA after request")
 }
}

func MiddlewareB() gin.HandlerFunc {
 return func(c *gin.Context) {
  log.Println("MiddlewareB before request")
  // before request
  c.Next()
  // after request
  log.Println("MiddlewareB after request")
 }
}

// This function's name is a must. App Engine uses it to drive the requests properly.
func main() {
 // Starts a new Gin instance with no middle-ware
 r := gin.New()
 r.Use(MiddlewareA(), MiddlewareB())
 r.GET("/ping", func(c *gin.Context) {
  c.String(http.StatusOK, "pong")
  log.Println("pong")
 })
 r.Run(":8080")
}

觸發一次請求後,服務器的日誌輸出是

2018/12/03 16:07:30 MiddlewareA before request
2018/12/03 16:07:30 MiddlewareB before request
2018/12/03 16:07:30 pong
2018/12/03 16:07:30 MiddlewareB after request
2018/12/03 16:07:30 MiddlewareA after request

可以看到,結果符合我們對代碼的解讀。

轉自:

blog.csdn.net/breaksoftware/article/details/84765060

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/PEV1o6xG_cwWpkl6EVGo9g