Gin源码解析和例子——中间件(middleware)

本文深入探讨Gin框架的中间件机制,解释其作为匿名回调函数的本质,以及如何通过Use方法添加并影响请求处理流程。通过具体示例,展示了中间件在请求处理链中的作用顺序,以及如何自定义中间件。

        在《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

        可以看到,结果符合我们对代码的解读。

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值