初识中间件

从最简单的模板开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

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

func main() {

r := gin.Default()

r.GET("/", func(c *gin.Context) {
c.String(200, "测试页面")
})

r.Run()

}

在这个例子中,只有一个函数处理根目录下的路由

然而其实也可以传递多个函数,它们将被依次执行,最后一个函数前面触发的方法都可以称为中间件

中间件适合处理一些公共的业务逻辑,比如登录认证权限校验数据分页记录日志耗时统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"

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

func initMiddleware(ctx *gin.Context) {
fmt.Println("我是一个中间件")
}

func main() {

r := gin.Default()

r.GET("/", initMiddleware, func(c *gin.Context) {
c.String(200, "测试页面")
})

r.Run()

}

可以看见,在处理路由时,先执行了中间件,再响应了页面

.Next()方法

在中间件中调用.Next()方法可以递归调用下一个中间件或最终函数

例1:统计一个请求的执行时间

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

import (
"fmt"
"time"

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

func initMiddleware(ctx *gin.Context) {
start := time.Now().UnixNano()
ctx.Next()
end := time.Now().UnixNano()
fmt.Println(end - start)
}

func main() {

r := gin.Default()

r.GET("/", initMiddleware, func(c *gin.Context) {
time.Sleep(time.Second) //休眠1秒
c.String(200, "测试页面")
})

r.Run()

}

例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
31
package main

import (
"fmt"

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

func MiddlewareOne(ctx *gin.Context) {
fmt.Println("第一个中间件开始")
ctx.Next()
fmt.Println("第一个中间件结束")
}

func MiddlewareTwo(ctx *gin.Context) {
fmt.Println("第二个中间件开始")
ctx.Next()
fmt.Println("第二个中间件结束")
}

func main() {

r := gin.Default()

r.GET("/", MiddlewareOne, MiddlewareTwo, func(c *gin.Context) {
c.String(200, "测试页面")
})

r.Run()

}

.Abort()方法

在中间件中调用.Next()方法可以终止该请求的剩余处理程序

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

import (
"fmt"

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

func MiddlewareOne(ctx *gin.Context) {
fmt.Println("第一个中间件开始")
ctx.Abort()
fmt.Println("第一个中间件结束")
}

func MiddlewareTwo(ctx *gin.Context) {
fmt.Println("第二个中间件开始")
ctx.Next()
fmt.Println("第二个中间件结束")
}

func main() {

r := gin.Default()

r.GET("/", MiddlewareOne, MiddlewareTwo, func(c *gin.Context) {
c.String(200, "测试页面")
})

r.Run()

}

全局中间件

在引擎上调用.Use()方法可以配置全局中间件

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

import (
"fmt"

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

func initMiddleware(ctx *gin.Context) {
fmt.Println("我是一个中间件")
}

func main() {

r := gin.Default()

r.Use(initMiddleware) //配置全局中间件

r.GET("/test1", func(c *gin.Context) {
c.String(200, "测试页面1")
})

r.GET("/test2", func(c *gin.Context) {
c.String(200, "测试页面2")
})
r.Run()

}

在路由分组中配置中间件

还记得之前的路由分组吗?

\routers\adminRouters.go中,我们之前是这样写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package routers

import (
"test/controllers/admin"

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

func AdminRoutersInit(r *gin.Engine) {
//后台路由
adminRouters := r.Group("/admin")
{
adminRouters.GET("/", admin.AdminController{}.Index)
adminRouters.GET("/user", admin.AdminController{}.User)
adminRouters.GET("/article", admin.AdminController{}.Article)
}
}

现在,需要为adminRouters这个路由组配置中间件,一共有两种写法

写法1:

1
2
3
4
5
6
7
8
9
10
11
12
13
func initMiddleware(ctx *gin.Context) {
fmt.Println("我是一个中间件")
}

func AdminRoutersInit(r *gin.Engine) {
//后台路由
adminRouters := r.Group("/admin", initMiddleware) //配置中间件
{
adminRouters.GET("/", admin.AdminController{}.Index)
adminRouters.GET("/user", admin.AdminController{}.User)
adminRouters.GET("/article", admin.AdminController{}.Article)
}
}

写法2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func initMiddleware(ctx *gin.Context) {
fmt.Println("我是一个中间件")
}

func AdminRoutersInit(r *gin.Engine) {
//后台路由
adminRouters := r.Group("/admin")
adminRouters.Use(initMiddleware) //配置中间件
{
adminRouters.GET("/", admin.AdminController{}.Index)
adminRouters.GET("/user", admin.AdminController{}.User)
adminRouters.GET("/article", admin.AdminController{}.Article)
}
}

中间件和对应控制器之间共享数据

在中间件中可以设置键值对(.Set()方法),供其他中间件或控制器读取(.Get()方法)

\routers\adminRouters.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
package routers

import (
"fmt"
"test/controllers/admin"

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

func initMiddleware(ctx *gin.Context) {
fmt.Println("我是一个中间件")
ctx.Set("username", "张三") //设置数据
}

func AdminRoutersInit(r *gin.Engine) {
//后台路由
adminRouters := r.Group("/admin")
adminRouters.Use(initMiddleware)
{
adminRouters.GET("/", admin.AdminController{}.Index)
adminRouters.GET("/user", admin.AdminController{}.User)
adminRouters.GET("/article", admin.AdminController{}.Article)
}
}

\controllers\admin\adminController.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package admin

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

type AdminController struct {
BaseController
}

func (c AdminController) Index(con *gin.Context) {
con.String(200, "后台首页")
}

func (c AdminController) User(con *gin.Context) {
username, _ := con.Get("username") //获取数据
con.String(200, username.(string))
}

func (c AdminController) Article(con *gin.Context) {
con.String(200, "新闻列表")
}

中间件注意事项

默认中间件

gin.Default()默认使用了 LoggerRecovery 中间件

  • Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release
  • Recovery 中间件会 recover 任何 panic,如果有 panic 的话,会写入 500 响应码

如果不想使用上面两个默认的中间件,可以使用 gin.New()新建一个没有任何默认中间件的路由

中间件中使用 goroutine

当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context), 必须使用其只读副本(c.Copy())

1
2
3
4
5
6
func initMiddleware(ctx *gin.Context) {
ctxCp:=ctx.Copy()
go func () {
...
}
}