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

1.介绍

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

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

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

2.使用

2.1 全局使用

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

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

2.2 单个路由使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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()
}

2.3 路由组使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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()
}

3.自定义中间件

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

3.1 语法结构

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

代码说明:

  • MiddleName: 自定义中间件名称。

  • gin.HandlerFunc: 中间件始终返回这个类型。

  • func(context *gin.Context): 匿名函数,参数是上下文(context *gin.Context)

  • context.Next(): 继续执行调用这个函数。

  • context.Abort():终止执行调用这个函数。

3.2 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 声明自定义中间件
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()
}

4.多个中间件

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

4.1 执行流程

4.2 代码示例

 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
// 自定义中间件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
中间件1-- 请求前
中间件2-- 请求前
中间件2-- 请求后
中间件1-- 请求后

5.实践

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

5.1 源码

./main.go代码:

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

./practise/check_token.go 代码:

 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
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()
}

5.2 请求

1
2
3
4
5
6
7
8
9
# 不需要验证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":"请求成功"}

6.使用Goroutine

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

6.1 代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 在中间件中使用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()
    }
}

7.流行的中间件

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