在之前的文章中,基本上都是使用的默认的 gin.Default() 来初始化一个引擎,之后再调用引擎的Run方法来运行一个web服务,现在如果有个需求,某些api只有管理员权限的才可以进行访问,非管理员的就很401,我们可以写一个方法,在需要授权的接口调用该方法来进行验权,但是在gin中还可以使用更加优雅的方法,中间件。

gin.Default() 是如何工作的?

我们先来看下gin.Default() 是如何工作的

1
2
3
4
5
6
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

它先使用New()方法初始化一个引擎,然后再调用引擎的Use 方法,加载了两个中间件

1
2
3
4
5
6
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}

加载了两个gin中的中间件,Logger()与Recovery()

Logger()的源码为

1
2
3
func Logger() HandlerFunc {
	return LoggerWithConfig(LoggerConfig{})
}

Recovery() 的源码为

1
2
3
func Recovery() HandlerFunc {
	return RecoveryWithWriter(DefaultErrorWriter)
}

什么是中间件

中间件为一类函数,HandlerFunc , 定义为 type HandlerFunc func(*Context)

也就是该函数的参数为Context,也就是说,如果我们要自定义一个中间件函数的话,只需要返回一个参数为(*gin.Content)的函数即可。

何为中间件? 当一个请求到达gin服务的某个路由以后,gin 会根据路由中定义好的处理类来进行处理,以GET方法为例

1
2
3
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

handlers 为不定参数,也就是说可以有多个处理类,而这里的处理类类型也是HandlerFunc,和中间件是一样的。

可以理解为是间件为多个处理请求的Handler,某个请求先经过一个Handler, 之后再经过第二个,第三个,最后将结果返回给调用者。

中间件函数中有以下两个方法比较重要

Next() 方法,该中间件处理以后,交由下一个中间件处理

Abort() 方法,调用完该方法以后,之后的HandlerFunc则不进行处理了

中间件的两种定义方式

中间件常用的有两种方式

  1. 定义一个返回值为gin.HandlerFunc 的函数,
  2. 直接定义一个参数为*gin.Context 的函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package midwares

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"time"
)

//打印耗时的中间件

func Checktime() gin.HandlerFunc {
	//这里可以处理一些别的逻辑
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()
		spend:= time.Since(start).Microseconds()
		fmt.Printf("use: %d \n", spend)
	}
}

func Abort(c *gin.Context){
	fmt.Println("使用了Abort中间件,不会进行之后处理")
	c.JSON(200, gin.H{"msg": "no next"})
	c.Abort()
}

这两种方式效果是一样的,只是在gin初始化加载的方式不同

1
2
3
r := gin.Default()
r.Use(midwares.Checktime()) //需要加上()
r.Use(midwares.Abort)  // 不需要加上()

个人还是比较喜欢加上()的方式。

中间件的几种使用方式

中间件有两种使用方式,一种是全局的中间件,一种是局部中间件

如上面的使用r.Use(midware) 的方式是全局的,这种方式,每个路由都会走该中间件的逻辑。

局部中间件为在路由定义时使用,这种定义则只在该路由上有效.

1
userv1.GET("/abort", midwares.Abort, userv1_h.Abort)

上面我定义了两个中间件,Checktime 为记录下该请求所用的时间,Abort 中间件为测试c.Abort 函数功能,使用了该中间件的路由只会走到这里,不会再经过之后的路由了。

访问上面的路由地址 /abort 以下,控制台会输出

1
2
使用了Abort中间件,不会进行之后处理
use: 443 

第一行输出为Abort 中间件的输出,第二行输出为Checktime中间件的输出。

http的返回为Abort中间件定义的返回,之后的HandlerFunc不再执行了。

1
2
3
{
	msg: "no next"
}

基于中间件的授权管理

创建一个中间件,如果校验成功则继续往下走,如果不是管理员,则就不要往下走了,返回401

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func CheckAdmin() gin.HandlerFunc{
	return func(c *gin.Context) {
		username := c.Query("user")
		// 这里可以是从cookie或者 session 中判断
		if username == "admin"{
			c.Next()
		}else{
			c.JSON(401, gin.H{"msg": "No Private"})
			c.Abort()
		}
	}
}

中间件的执行顺序

gin在初始化的时候加载了一些是间件,又定义了全局的中单件,又定义了路由的中间件,那么这些中间件的执行顺序是如何的呢?

执行的顺序为 全局中间件 > 路由组中间件 > 路由中间件。

##参考文章

Gin框架入门(四)—中间件