中间件

1991/6/26 Gin框架

中间件(英语:Middleware),又译中间件、中介层,是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。

# 1.介绍

Gin框架中,中间件本质上是gin.HandlerFunc 函数,如果我们要自定义中间件,只需要返回类型是gin.HandlerFunc 即可。

Gin框架中,使用中间件可分为以下几种场景:

  • 全局使用
  • 单个路由使用
  • 路由组使用

# 2.使用

# 2.1 全局使用

gin.Default()函数中,默认注册全局中间件Logger、Recovery,具体代码如下:

func Default() *Engine {
  // 打印debug信息
 debugPrintWARNINGDefault()
  // 创建引擎
 engine := New()
  // 注册全局中间件
 engine.Use(Logger(), Recovery())
 return engine
}
1
2
3
4
5
6
7
8
9

# 2.2 单个路由使用

func main()  {
 engine := gin.New()
 // 添加一个中间件: Logger()
 engine.GET("/route",gin.Logger(), func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"针对单个路由"})
 })
 // 添加多个中间件: Logger(),Recovery()
 engine.GET("/route2",gin.Logger(),gin.Recovery(), func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"针对单个路由添加多个中间件"})
 })
 _ = engine.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12

# 2.3 路由组使用

func main()  {
 engine := gin.New()
  // 声明路由后,通过Use注册中间件
 v1Group := engine.Group("/v1").Use(gin.Logger())
 {
  v1Group.GET("/middleGroup", func(context *gin.Context) {
    context.JSON(200,gin.H{"msg":"succss"})
  })
 }
 _ = engine.Run()
}
1
2
3
4
5
6
7
8
9
10
11

# 3.自定义中间件

我们可以创建自己的中间件,在中间件中需要继续执行时,使用c.Next(),而要终止执行时则需调用c.Abort()终止请求。

# 3.1 语法结构

func MiddleName() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 请求前业务逻辑...
    // 继续往下执行
  context.Next()
    // context.Abort() 停止往下执行
  // 请求后业务逻辑...
  end := time.Now().Unix()
  fmt.Printf("接口耗时:%v 秒 \n", end - t)
 }
}
1
2
3
4
5
6
7
8
9
10
11

代码说明:

  • MiddleName: 自定义中间件名称。
  • gin.HandlerFunc: 中间件始终返回这个类型。
  • func(context *gin.Context): 匿名函数,参数是上下文(context *gin.Context)
  • context.Next(): 继续执行调用这个函数。
  • context.Abort():终止执行调用这个函数。

# 3.2 示例

// 声明自定义中间件
func MyMiddleware() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 请求前
  fmt.Println("中间件-- 请求前")
  t := time.Now().Unix()
    // 继续往下执行
  context.Next()
    // context.Abort() 停止往下执行
  // 请求后
  end := time.Now().Unix()
  fmt.Printf("接口耗时:%v 秒 \n", end - t)
 }
}
// 自定义中间件使用
func RunWithMyMiddle()  {
 engine := gin.New()
 // 注册自定义中间件
 engine.GET("/route",MyMiddleware(), func(context *gin.Context) {
  time.Sleep(time.Second * 3)
  context.JSON(200,gin.H{"msg":"自定义路由"})
 })
 _ = engine.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 4.多个中间件

如果同时注册多个中间件,那么他们之间的执行顺序是什么样呢?

# 4.1 执行流程

# 4.2 代码示例

// 自定义中间件A
func MyMiddleware() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 请求前
  fmt.Println("中间件1-- 请求前")
  context.Next()
  // 请求后
  fmt.Println("中间件1-- 请求后")
 }
}
// 自定义中间件B
func MyMiddleware2() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 请求前
  fmt.Println("中间件2-- 请求前")
  context.Next()
  // 请求后
  fmt.Println("中间件2-- 请求后")
 }
}
// 启动服务
func main()  {
 engine := gin.New()
 // 使用多个中间件
 engine.GET("/route",MyMiddleware(), MyMiddleware2(),func(context *gin.Context) {
  time.Sleep(time.Second * 3)
  context.JSON(200,gin.H{"msg":"自定义路由"})
 })
 _ = engine.Run()
}
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
27
28
29
30

请求控制台输出:

中间件1-- 请求前
中间件2-- 请求前
中间件2-- 请求后
中间件1-- 请求后
1
2
3
4

# 5.实践

创建自定义中间件用来判断Token是否有效(是否是登录状态)。

# 5.1 源码

./mian.go代码:

package main
import "go-use/practise"
func main() {
 practise.RunServeWithCheckToken()
}
1
2
3
4
5

./practise/check_token.go 代码:

package practise
import (
 "github.com/gin-gonic/gin"
)
// 检测token
func CheckTokenMiddle() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 获取token
  token := context.DefaultQuery("token","")
  // 检测token
  if token != "abcd" {
   context.JSON(500,gin.H{"msg":"请先登录!"})
   // 终止请求
   context.Abort()
  }
  // token合法则继续往下执行
  context.Next()
 }
}
// 启动服务
func RunServeWithCheckToken()  {
 engine := gin.Default()
 // 登录接口不需要添加检测Token中间件
 engine.GET("/user/login", func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"登录成功!","token":"abcd"})
 })
 // 需要验证token的路由
 user := engine.Group("/user").Use(CheckTokenMiddle())
 {
  // 用户基本信息
  user.GET("/info", func(context *gin.Context) {
   data := map[string]interface{} {
    "name": "张三",
    "age": 18,
    "likes": []string{"打游戏","旅游"},
   }
   context.JSON(200,gin.H{"msg":"请求成功","data":data})
  })
  // 更新用户
  user.GET("/update", func(context *gin.Context) {
   context.JSON(200,gin.H{"msg":"请求成功"})
  })
 }
 // 启动服务
 _ = engine.Run()
}
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 5.2 请求

# 不需要验证token的接口
➜ curl -X GET http://127.0.0.1:8080/user/login
{"msg":"登录成功!","token":"abcd"}
# 需要验证Token,但是没传时
➜ curl -X GET http://127.0.0.1:8080/user/info
{"msg":"请先登录!"}
# 需要验证Token,并且已传时
➜  curl -X GET http://127.0.0.1:8080/user/info?token=abcd
{"data":{"age":18,"likes":["打游戏","旅游"],"name":"张三"},"msg":"请求成功"}
1
2
3
4
5
6
7
8
9

# 6.使用Goroutine

当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本。

# 6.1 代码示例

// 在中间件中使用Goroutine
func main()  {
 engine := gin.Default()
 engine.Use(MyMiddleWithGoRoutine())
 engine.GET("/useGo", func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"success"})
 })
 _ = engine.Run()
}
// 在中间件中使用Goroutine
func MyMiddleWithGoRoutine() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 复制上下文
  cpContext := context.Copy()
  go func() {
   time.Sleep(3 * time.Second)
   fmt.Println(cpContext.Request.URL.Path)
  }()
  context.Next()
 }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 7.流行的中间件

在gin-gonic/contrib(https://github.com/gin-gonic/contrib)库中整理很多常用的中间件,可以直接使用,具体中间件有以下: