Gin 中间件的配置方式有许多种,大致为:

  1. Handle()Any()Match()GET() 等基本路由方法中配置 HandlerFunc
  2. RouterGroupGroup() 方法中配置 HandlerFunc
  3. 使用 Use() 方法配置 HandlerFunc

特定请求的中间件

为特定请求配置中间件,可以直接在请求路由的 HandlerFunc 参数中配置。例如:

  • controllers/user

    type userController struct{}
    
    var UserController userController
    
    func (userController) GetUser(ctx *gin.Context) {
      id := ctx.Param("id")
      // 获取 Context 内部字典的值
      token, exists := ctx.Get(middlewares.TOKEN)
      if !exists {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": "Unauthorized..."})
      }
      ctx.PureJSON(http.StatusOK, gin.H{
        "id": id, "username": "zhangsan", "token": token,
      })
    }
    
  • middlewares/user

    type userMiddlewares struct{}
    
    var UserMiddlewares userMiddlewares
    
    const Authorization = "Authorization"
    
    func (userMiddlewares) VerifyToken(ctx *gin.Context) {
      auth := strings.TrimSpace(ctx.GetHeader(Authorization))
      if auth == "Bearer 123456" {
        // 使用 Context.Set() 设置键值对
        // 这个键值对只能在当前处理链中按顺序流转
        // 换句话说,这个键值对只是存储在当前 Context 中
        ctx.Set(TOKEN, "123456")
        ctx.Next()
      } else {
        ctx.JSON(http.StatusBadRequest, gin.H{"error": "Unauthorized..."})
        ctx.Abort()
      }
    }
    

    中间件也可专门为其定义一个 middlewares 包来存放项目中所有的中间件。

    处理链:即一组按顺序执行的中间件和处理函数。

  • routers/user.go

    将中间函数和处理函数绑定到路由上:

    func UserRoutersInit(engin *gin.Engine) {
    	engin.Group("/user", middlewares.UserMiddleware).
    		GET("/:id", middlewares.UserMiddlewares.VerifyToken,
    			controllers.UserController.GetUser)
    }
    

全局中间件

全局中间件就是绑定在根路由上的、全局生效的中间件,可以使用 RouterGroup.Use() 方法绑定全局中间件。例如:

func Router() (engine *gin.Engine) {
	engine = gin.Default()
	// 设置全局中间件
	engine.Use(middlewares.GlobalMiddlewares.VerifyToken)

	// ...
}

如果需要绑定的中间件过多,除了多次调用 User() 方法绑定或在 User() 方法中传入多个中间件外,还能使用 gin.HandlersChain 类型。gin.HandlersChain 的定义如下:

type HandlersChain []HandlerFunc

使用 gin.HandlersChain 来聚集多个中间件,然后将它们一次性传入 User() 中:

var globalMiddlewares = gin.HandlersChain{
	middlewares.GlobalMiddlewares.VerifyToken,
	middlewares.GlobalMiddlewares.VerifyAuthorization,
 	// ...
}

func Router() (engine *gin.Engine) {
  engine = gin.Default()
	// 设置全局中间件
	engine.Use(globalMiddlewares...)

	// ...
}

通过使用 gin.HandlersChain 聚集并注册全局中间件后,中间函数的执行顺序将按照 gin.HandlersChain 中的顺序执行。


分组中间件

分组中间件同样可以使用 RouterGroup.Use() 方法,不同的是调用方法的对象是具体的路由分组对象,也就是 Engin.Group() 调用之后返回的对象。例如:

engin.Group("/user").
	Use(middlewares.UserMiddlewares.VerifyToken)

除了使用 RouterGroup.Use() 方法,分组中间件还可以在 Engin.Group() 调用的时候注册。例如:

engin.Group("/user" middlewares.UserMiddlewares.VerifyToken)

同样地,也可以使用 gin.HandlersChain

var groupMiddlewares = gin.HandlersChain{
	middlewares.UserMiddlewares.VerifyToken,
 	// ...
}

engin.Group("/user" ...groupMiddlewares)

注:User() 方法注册的中间件,和使用其它路由方法(如 Handle()Any()GET()Match() 等等)注册的中间件、处理函数它们之间的调用顺序也是按照注册的顺序执行。例如:

engin.Group("/user", middlewares.UserMiddlewares.VerifyToken).
	GET("/info/:username", controllers.UserController.GetInfo).
	POST("/list", controllers.UserController.GetList).
	// 在此之前的路由,在执行时不会调用 middlewares.UserMiddlewares.VerifyAuthorization
	// 只会调用 middlewares.UserMiddlewares.VerifyToken
	Use(middlewares.UserMiddlewares.VerifyAuthorization).
	// 在此之前的路由,在执行时会按顺序调用 middlewares.UserMiddlewares.VerifyToken
	// 和 middlewares.UserMiddlewares.VerifyAuthorization
	GET("/:id", controllers.UserController.GetUser)

中间件 Context

在中间件中,Context 有一些方法可用于中间件的编写:

  • Context.Next():将控制权传递给下一个中间件或处理函数。

    func MyMiddleware(c *gin.Context) {
    	// 执行某些预处理逻辑
    	c.Next()
    	// 执行某些后处理逻辑
    }
    
  • Context.Abort():立即停止执行后续的中间件和处理函数,可以用来处理错误或特殊请求。

    func ErrorMiddleware(c *gin.Context) {
    	if c.Query("error") == "true" {
    		c.Abort()	// 停止执行后续的中间件和处理函数
    	} else {
    		c.Next()
    	}
    }
    
  • Context.Set()Context.Get():在当前请求处理链中存储和检索数据,允许在处理链中不同的中间件和处理函数之间共享信息。

    func StoreDataMiddleware(c *gin.Context) {
    	// 存储数据
    	c.Set("user_id", 123)
    	c.Next()
    }
    
    func RetrieveDataMiddleware(c *gin.Context) {
    	// 获取数据
    	userId, err := c.Get("user_id")
    	// 使用 userId ...
    }
    
    
    `Context.Set()` 和 `Context.Get()` 存储和获取的数据,是存储在当前 `Context` 对象的内部字典(`store` 字段)中。在 Gin 中,对于一条处理链中所有的中间件和处理函数共享同一个 `Context` 对象。
    

Contex 中除了上述的 4 个方法外,还有其它可以用于控制中间件或处理函数执行流的方法。例如以 Abort 开头的 Abort 系方法,它们在终止执行链执行的同时,还提供了其它一些额外的操作。比如说 Context.AbortWithStatus(),在中止执行链的同时设置响应状态码。