go web framework gin middleware 设计原理

场景:一个middleware可以具体为一个函数,而由前面的gin 路由分析可得,每一个路径都对有一个HandlersChain 与其对应。

那么实际上增加一个middleware的过程,就是将每一个路由策略加进来之前,与其绑定,这样就能使得这一类的路由到来的时候触发这个中间件生效。

下面看看gin web framework中是如何实现的?

首先是:gin.default()函数

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
        debugPrintWARNINGDefault()
        engine := New()
        engine.Use(Logger(), Recovery()) #加载Logger(), 和Recovery()中间件
        return engine
}

然后查看engine.Use()

// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
        engine.RouterGroup.Use(middleware...) #这个函数的参数是一个不定参数,也就是说我们可以追加的中间件没有限制
        engine.rebuild404Handlers()
        engine.rebuild405Handlers()
        return engine
}

接着是engine.RouterGroup.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...) #将middleware暂存在group.Handlers中
        return group.returnObj()
}

这个时候,我们再来看一个路由信息的调用过程,如,我们定义一个Get的路由

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

查看r.GET函数的实现

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
        return group.handle("GET", relativePath, handlers) #将handlers 函数以及相对路径传递给group.handle()函数
}

查看group.handle()函数的实现

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
        absolutePath := group.calculateAbsolutePath(relativePath) #计算绝对路径
        handlers = group.combineHandlers(handlers) #将参数的handlers 和 middleware 的handlers拼接在一起作为一条路由信息,传递给addRoute()函数
        group.engine.addRoute(httpMethod, absolutePath, handlers)
        return group.returnObj()
}

 查看group.combineHandlers()函数的实现

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) #创建一个HandlersChain的数组,然后把group.handlers 和 参数的handlers copy 进去
        copy(mergedHandlers, group.Handlers)
        copy(mergedHandlers[len(group.Handlers):], handlers)
        return mergedHandlers
}

 于是每一条路由的达到,如果根据基树找到这里的HandlersChain,那么就会触发该HandlersChain中所有的func, 至于如何找到的,查看gin 启动流程分析这篇文章。

这样的中间件设计有什么优缺点呢?

优点:保证所有的路由信息都会用到中间件。

缺点:如果中间件并不适用于所有的路由策略,则更改起来比较麻烦,需要在中间件内部根据context做判断。