在《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 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 可以看到,结果符合我们对代码的解读。
有疑问加站长微信联系(非本文作者)
