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 設計中比較奇葩的地方:
-
Context 的 Next 方法讓合併之後的 handlers 中的回調執行
-
handlers 中的回調調用 Context 的 Next 方法以驅動下個回調執行
如果我們不看 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