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