Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

  1. func MyMiddleWare()(gin.HandlerFunc){
  2. return func(ctx *gin.Context){
  3. fmt.Println("中间件开始......")
  4. ctx.Set("name","joker")
  5. start := time.Now()
  6. // 调用Next()处理函数
  7. ctx.Next()
  8. // 计算函数耗时多少
  9. cost := time.Since(start)
  10. fmt.Println("耗时:",cost)
  11. }
  12. }

其中最关键的一点是ctx.Next(),调用这个的作用就是处理后续的处理函数。

注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

全局中间件

定义全局中间件的话就在主函数中用User()方法加载中间件。为了代码规范,建议绑定路由规则都包含在{}中,如下:

  1. func main(){
  2. // 1、创建路由
  3. g := gin.Default()
  4. // 2、加载中间件
  5. g.Use(MyMiddleWare())
  6. {
  7. // 2、绑定路由规则
  8. g.GET("/middleware",myFunc)
  9. }
  10. g.Run(":8000")
  11. }
  12. func myFunc(context *gin.Context){
  13. // 获取中间件中设置的值
  14. ret := context.MustGet("name")
  15. fmt.Println("中间件中的值:",ret)
  16. time.Sleep(time.Second*5)
  17. }

然后访问输出如下:

  1. 中间件开始......
  2. 中间件中的值: joker
  3. 耗时: 5.001355s
  4. [GIN] 2020/04/02 - 18:19:56 |?[97;42m 200 ?[0m| 5.0050022s | 127.0.0.1 |?[97;44m GET ?[0m "/middleware"

局部中间件

局部中间件也就是为某个路由单独注册中间件,只需要在绑定路由的时候在路径后面跟上中间件函数即可。
如下:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. "time"
  6. )
  7. func MyMiddleWare()(gin.HandlerFunc){
  8. return func(ctx *gin.Context){
  9. fmt.Println("中间件开始......")
  10. ctx.Set("name","joker")
  11. start := time.Now()
  12. // 调用Next()处理函数
  13. ctx.Next()
  14. // 计算函数耗时多少
  15. cost := time.Since(start)
  16. fmt.Println("耗时:",cost)
  17. }
  18. }
  19. func main(){
  20. // 1、创建路由
  21. g := gin.Default()
  22. // 2、加载中间件
  23. g.Use(MyMiddleWare())
  24. {
  25. // 2、绑定路由规则
  26. g.GET("/test2",MyMiddleWare(),test2)
  27. }
  28. g.Run(":8000")
  29. }
  30. func test2(ctx *gin.Context){
  31. // 获取中间件中设置的值
  32. ret := ctx.MustGet("name")
  33. fmt.Println("中间件中的值:",ret)
  34. time.Sleep(time.Second*5)
  35. }

然后访问如下:

  1. 中间件开始......
  2. 中间件开始......
  3. 中间件中的值: joker
  4. 耗时: 5.0002031s
  5. 耗时: 5.0007657s
  6. [GIN] 2020/04/02 - 18:40:55 |?[97;42m 200 ?[0m| 5.0037571s | 127.0.0.1 |?[97;44m GET ?[0m "/test2"

我们可以看到执行了两遍中间件,其中在执行函数之前是先执行的全局中间件,然后再是局部中间件,再返回的时候先执行局部中间件,再执行全局中间件。
流程图如下:
image.png

为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1:

  1. shopGroup := r.Group("/shop", StatCost())
  2. {
  3. shopGroup.GET("/index", func(c *gin.Context) {...})
  4. ...
  5. }

写法2:

  1. shopGroup := r.Group("/shop")
  2. shopGroup.Use(StatCost())
  3. {
  4. shopGroup.GET("/index", func(c *gin.Context) {...})
  5. ...
  6. }

中间件注意事项

gin默认中间件

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

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

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

gin中间件中使用goroutine

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