Gin源码解析和例子——中间件(middleware)
? ? ? ? 在《Gin源碼解析和例子——路由》一文中,我們已經初識中間件。本文將繼續探討這個技術。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 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 testr.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 mainimport ("log""net/http""github.com/gin-gonic/gin"
)func MiddlewareA() gin.HandlerFunc {return func(c *gin.Context) {log.Println("MiddlewareA before request")// before requestc.Next()// after requestlog.Println("MiddlewareA after request")}
}func MiddlewareB() gin.HandlerFunc {return func(c *gin.Context) {log.Println("MiddlewareB before request")// before requestc.Next()// after requestlog.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-warer := 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
? ? ? ? 可以看到,結果符合我們對代碼的解讀。
總結
以上是生活随笔為你收集整理的Gin源码解析和例子——中间件(middleware)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Gin源码解析和例子——路由
- 下一篇: C++拾取——Linux下实测布隆过滤器