Gin框架---中间件

标签:Go语言首次发布:2023-11-12最近修改:2025-11-30

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

定义中间件

Gin 中的中间件必须是一个 gin.HandlerFunc 类型。

洋葱模型

flowchart TB
    R[请求] --> L1
    L1 --> S[响应]

    subgraph L1[中间件1(外层)]
      direction TB
      subgraph L2[中间件2(中层)]
        direction TB
        subgraph L3[中间件3(内层)]
          direction TB
          H[(处理器/Handler)]
        end
      end
    end

看上图,我们的中间件就相当于一层一层的洋葱。如果要到洋葱内部,需要一层一层的进入。同理,出来也是一层一层的出来。也就是说,每个中间件先做“前置”,调用 next() 进入更内层,待内层完成后再执行“后置”并返回。

如下图所示:

sequenceDiagram
    participant C as 客户端/请求
    participant M1 as 中间件1
    participant M2 as 中间件2
    participant M3 as 中间件3
    participant H as 处理器/Handler

    C->>M1: 进入
    M1->>M2: next()
    M2->>M3: next()
    M3->>H: next()
    H-->>M3: 返回(业务结果)
    M3-->>M2: 后置逻辑
    M2-->>M1: 后置逻辑
    M1-->>C: 响应

统计耗时请求耗时的中间件

go
// Cost 是一个统计耗时请求耗时的中间件func Cost() gin.HandlerFunc {    return func(c *gin.Context) {        start := time.Now()        c.Set("name", "bing") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值        c.Next()// 调用该请求的剩余处理程序        // c.Abort() // 不调用该请求的剩余处理程序        cost := time.Since(start) // 计算耗时        log.Println(cost)    }}

跨域中间件 cors

推荐使用社区的https://github.com/gin-contrib/cors 库,几行代码解决前后端分离架构下的跨域问题。

go
r := gin.New()//解决跨域问题corsConfig := cors.Config{    AllowOrigins:     []string{"https://demo.com"},    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},    AllowHeaders:     []string{"*"},    MaxAge:           12 * time.Hour,    AllowCredentials: true,}r.Use(cors.New(corsConfig))

Gin 框架内置中间件

Gin 框架自带的中间件

  • 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 //将 http.HandlerFunc 包装成中间件
  • func WrapH(h http.Handler) HandlerFunc //将 http.Handler 包装成中间件

Gin 框架默认路由使用的中间件

默认路由使用了 Logger(), Recovery()全局作用了两个中间件。

go
r:=gin.Default()//Default()的定义:func Default() *Engine {    debugPrintWARNINGDefault()    engine := New()    engine.Use(Logger(), Recovery())    return engine}

注册中间件

为全局注册中间件

go
r := gin.Default()//全局中间件的注册r.Use(Cost())//全局中间件的注册r.GET("/middleware", M2)

为路由组注册中间件

写法 1:

go
shopGroup := r.Group("/shop", Cost()){    shopGroup.GET("/index", func(c *gin.Context) {...})    ...}

写法 2:

go
shopGroup := r.Group("/shop")shopGroup.Use(Cost()){    shopGroup.GET("/index", func(c *gin.Context) {...})    ...}

为单个路由注册中间件

go
r.GET("/test", Cost(), func(c *gin.Context) {    name := c.MustGet("name").(string) // 从上下文取值,MustGet()如果取不到值就会panic。MustGet()没有bool值的返回。    c.JSON(http.StatusOK, gin.H{        "name": name,    })})

在中间件中使用 Goroutine

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

go
func M1() gin.HandlerFunc {    return func(c *gin.Context) {        c.Set("name", "bing")        ctxCopy := c.Copy() //得到上下文的副本        go func() {            // 使用的是复制的上下文:ctxCopy            time.Sleep(3 * time.Second)            goRoutinePath := ctxCopy.Request.URL.Path            //goroutine结束后ctxCopy就会被销毁            log.Println(goRoutinePath)        }()    }}func M2(c *gin.Context) {    name := c.MustGet("name")    path := c.Request.URL.Path    c.JSON(http.StatusOK, gin.H{        "name": name,        "Path": path,    })}func main() {    r := gin.Default()    r.Use(M1())    r.GET("/middleware", M2)    err := r.Run("127.0.0.1:8080")    if err = nil {        fmt.Println(err.Error())    }}
结果如下:

image-20230331133149995

image-20230331133209363