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()) }}结果如下:

