gin添加自定义中间件

中间件的作用

在我们熟悉的一个完整的http请求中, 客户端先向服务端发送的http request, 服务端接收到request之后, 根据实际需要, 给客户端返回http response

站在服务端的角度来看, 就是接收request, 响应response, 而中间件的作用, 就在requestresponse之间

  • 收到request之后, 在request还未到达处理的handler之前, 可以通过中间件做出一系列的数据处理, 可以看做是前置校验或过滤
  • request到达handler之后, 中间件还可以根据需要对响应体做统一的数据处理

gin内置的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc
func ErrorLogger() HandlerFunc
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger() HandlerFunc
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH(h http.Handler) HandlerFunc

中间件的作用域

1
2
r := gin.Default()  //默认带Logger(), Recovery()这两个内置中间件
r := gin.New() //不带任何中间件

全局作用域

1
2
r := gin.New()  //不带任何中间件
r.Use(gin.Recovery()) //在全局使用内置中间件

组作用域

1
2
3
4
5
6
r := gin.New()
vm := r.Group("vm", gin.Logger(),gin.Recovery())
{
vm.GET("cpu", func(context *gin.Context) {})
vm.GET("ram", func(context *gin.Context) {})
}

单个路由作用域

1
2
3
4
r := gin.New()
router.GET("/healthcheck",gin.Recovery(),gin.Logger(),func(c *gin.Context){
c.JSON(200,"ok")
})

自定义中间件

  • 第一种形式
1
2
3
4
//定义中间件
func middleware1(c *gin.Context){
//中间件逻辑
}
1
2
3
//引用中间件
r := gin.Default()
r.Use(middleware1)

注意: r.Use()中的中间件函数没有()

  • 第二种形式
1
2
3
4
5
6
func middleware2() gin.HandlerFunc {
//自定义逻辑
return func(c *gin.Context) {
//中间件逻辑
}
}
1
2
3
//引用中间件
r := gin.Default()
r.Use(middleware2())

数据传递

gin.Context中提供了Set``Get函数用来存储和提取数据

1
2
3
4
5
6
//中间件set数据
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("name", c.Request.Header.Get("name"))
}
}
1
2
3
4
5
6
7
//handler中get数据
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"name": c.Get("name"),
"message": "ok",
})
})

Set方法存储的值为interface{}类型, Get方法中提供以下常用的数据类型方便提取数据

1
2
3
4
5
6
7
8
9
10
11
func (c *Context) GetBool(key string) (b bool)
func (c *Context) GetDuration(key string) (d time.Duration)
func (c *Context) GetFloat64(key string) (f64 float64)
func (c *Context) GetInt(key string) (i int)
func (c *Context) GetInt64(key string) (i64 int64)
func (c *Context) GetString(key string) (s string)
func (c *Context) GetStringMap(key string) (sm map[string]interface{})
func (c *Context) GetStringMapString(key string) (sms map[string]string)
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
func (c *Context) GetStringSlice(key string) (ss []string)
func (c *Context) GetTime(key string) (t time.Time)

拦截request请求

  • request请求添加header
1
2
3
4
5
//中间件
//在请求到达`handler`之前, 为`request`请求添加`header`
func middleware1(c *gin.Context) {
c.Request.Header.Set("abc", "123")
}
1
2
3
4
5
6
7
8
//handler
r.GET("/", func(c *gin.Context) {
//获取请求头
fmt.Println(c.Request.Header.Get("abc"))
c.JSON(200, gin.H{
"message": "ok",
})
})
  • response响应添加header
1
2
3
4
5
6
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
//在接收request请求时就设置好response时要添加的header
c.Header("xyz", "789")
}
}

追加handler处理后的动作

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 main

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

func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
c.String(200, "handler ok\n")
}
}

func main() {
r := gin.Default()

r.Use(middleware2())

r.GET("/", func(c *gin.Context) {
c.String(200, "handler ok\n")
})

if err := r.Run(":8080"); err != nil {
panic(err)
}
}
1
2
3
curl http://127.0.0.1:8080/
handler ok
middleware ok

c.Next()

c.Next()函数允许我们在中间件中控制调度的逻辑, 每执行到c.Next()一次, 执行权就会别切换到其他中间件或handler函数中

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 main

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

func middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware1")
c.Next()
fmt.Println("end middleware1")
}
}

func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware2")
c.Next()
fmt.Println("end middleware2")
}
}

func middleware3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware3")
c.Next()
fmt.Println("end middleware3")
}
}

func main() {
r := gin.Default()

r.Use(middleware1(), middleware2(), middleware3())

r.GET("/", func(c *gin.Context) {
fmt.Println("start handler")
c.String(200, "ok\n")
fmt.Println("end handler")
})

if err := r.Run(":8080"); err != nil {
panic(err)
}
}

运行:

1
go run main.go

访问:

1
curl http://127.0.0.1:8080  # 返回结果: ok

程序日志:

1
2
3
4
5
6
7
8
start middleware1
start middleware2
start middleware3
start handler
end handler
end middleware3
end middleware2
end middleware1

依据注册中间件的顺序, 每次遇到c.Next()函数就跳转到下一个中间件执行, 直到跳转到最后一个中间件, 再次遇到c.Next()的时候, 就跳到handler主函数中运行, 当handler执行完毕后, 再逐层向上返回, 继续执行c.Next()之后的代码

c.Abord()

c.Abord()用来终止request, 阻止其到达handler 一般情况下用在鉴权与认证的中间件中

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
47
package main

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

func middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware1")
c.Next()
fmt.Println("end middleware1")
}
}

//跟上面c.Next()的代码相比, 只是middleware2中的Next换成了Abort
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware2")
c.Abort()
fmt.Println("end middleware2")
}
}

func middleware3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware3")
c.Next()
fmt.Println("end middleware3")
}
}

func main() {
r := gin.Default()

r.Use(middleware1(), middleware2(), middleware3())

r.GET("/", func(c *gin.Context) {
fmt.Println("start handler")
c.String(200, "ok\n")
fmt.Println("end handler")
})

if err := r.Run(":8080"); err != nil {
panic(err)
}
}

访问后, 程序日志:

1
2
3
4
start middleware1
start middleware2
end middleware2
end middleware1

可以看到即使在middleware2中执行了c.Abord(), c.Abord()后面的代码依然被完整的执行, 并且返回到了middleware1跳转的地方, 继续执行了后续代码