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

Posted breaksoftware

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gin源码解析和例子——中间件(middleware)相关的知识,希望对你有一定的参考价值。

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

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

以上是关于Gin源码解析和例子——中间件(middleware)的主要内容,如果未能解决你的问题,请参考以下文章

Gin源码解析和例子——路由

Gin源码解析和例子——路由

Koa 系列 — Koa 中间件机制解析

Gin框架上传文件

Gin框架上传文件

Gin框架使用通用http.Handler中間件