Go Gin 中间件源码解析和例子

2022年3月15日 392点热度 0人点赞 0条评论

【导读】本文从源码角度解读了 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

 - EOF -

推荐阅读(点击标题可打开)

1、Golang 互斥锁内部实现

2、Go 分布式游戏服务器引擎之服务注册源码阅读

3、Go 安装配置golint

Go 开发大全

参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。

图片

关注后获取

回复 Go 获取6万star的Go资源库

分享、点赞和在看

支持我们分享更多好文章,谢谢!

13110Go Gin 中间件源码解析和例子

root

这个人很懒,什么都没留下

文章评论