Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin中的中间件必须是一个 gin.HandlerFunc
类型。
c.Next()
调用后续的处理函数
记录接口耗时的中间件
定义一个统计请求耗时的中间件 m1
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// HandlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定义一个中间件:统计请求处理函数的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
// 计时
start := time.Now()
c.Next()
cost := time.Since(start)
fmt.Printf("cost:%v\n", cost)
fmt.Println("m1 out...")
}
func main() {
r := gin.Default()
// GET(relativePath string, handlers ...HandlerFuncs) IRouter
r.GET("/index", m1, indexHandler)
r.Run(":9090")
}
全局注册1个中间件
go
func main() {
r := gin.Default()
r.Use(m1) // 全局注册中间件函数m1
// GET(relativePath string, handlers ...HandlerFuncs() IRouter
r.GET("/index", indexHandler)
r.GET("/shop", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/user", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user",
})
})
r.Run(":9090")
}
全局注册2个中间件
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// HandlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定义一个中间件:统计请求处理函数的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
// 计时
start := time.Now()
c.Next() // 调用后续的处理函数
//c.Abort() // 阻止调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost:%v\n", cost)
fmt.Println("m1 out...")
}
func m2(c *gin.Context) {
fmt.Println("m2 in...")
c.Next()
fmt.Println("m2 out...")
}
func main() {
r := gin.Default()
r.Use(m1, m2) // 全局注册中间件函数m1, m2
// GET(relativePath string, handlers ...HandlerFuncs() IRouter
r.GET("/index", indexHandler)
r.Run(":9090")
}
c.Abort()
阻止调用后续的处理函数
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// HandlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定义一个中间件:统计请求处理函数的耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
// 计时
start := time.Now()
c.Next() // 调用后续的处理函数
//c.Abort() // 阻止调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost:%v\n", cost)
fmt.Println("m1 out...")
}
func m2(c *gin.Context) {
fmt.Println("m2 in...")
c.Abort()
fmt.Println("m2 out...")
}
func main() {
r := gin.Default()
r.Use(m1, m2) // 全局注册中间件函数m1, m2
// GET(relativePath string, handlers ...HandlerFuncs() IRouter
r.GET("/index", indexHandler)
r.Run(":9090")
}
return
go
func m2(c *gin.Context) {
fmt.Println("m2 in...")
c.Abort()
return
fmt.Println("m2 out...")
}
高阶中间件
go
// 接受一个布尔参数 doCheck 来决定是否进行检查
// 如果 doCheck 为 true,可以在中间件中添加检查
// 如果为 false,则直接调用 c.Next(),继续请求处理
// 使得可以灵活控制是否需要验证
func authMiddleware(doCheck bool) gin.HandlerFunc {
// 连接数据库
// 或者一些其他准备工作
return func(c *gin.Context) {
if doCheck {
// 是否登录的判断
// if 是登录用户
// c.Next()
// else
// c.Abort()
} else {
c.Next()
}
}
}
go
func main() {
r := gin.Default()
r.Use(m1, m2, authMiddleware(false))
// GET(relativePath string, handlers ...HandlerFuncs() IRouter
r.GET("/index", indexHandler)
r.Run(":9090")
}
记录响应体的中间件
有时候可能会想要记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。
go
type bodyLogWriter struct {
gin.ResponseWriter // 嵌入gin框架ResponseWriter
body *bytes.Buffer // 我们记录用的response
}
// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b) // 我们记录一份
return w.ResponseWriter.Write(b) // 真正写入响应
}
// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
c.Writer = blw // 使用我们自定义的类型替换默认的
c.Next() // 执行业务逻辑
fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}
跨域中间件cors
推荐使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。
注意: 该中间件需要注册在业务处理函数前面。
这个库支持各种常用的配置项,具体使用方法如下。
go
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"}, // 允许跨域发来请求的网站
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的请求方法
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool { // 自定义过滤源站的方法
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
router.Run()
}
当然可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。
go
func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}
注册中间件
在gin框架中,可以为每个路由添加任意数量的中间件。
为全局路由注册
go
func main() {
// 新建一个没有任何默认中间件的路由
r := gin.New()
// 注册一个全局中间件
r.Use(StatCost())
r.GET("/test", func(c *gin.Context) {
name := c.MustGet("name").(string) // 从上下文取值
log.Println(name)
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
})
r.Run()
}
为某个路由单独注册
go
// 给/test2路由单独注册中间件(可注册多个)
r.GET("/test2", StatCost(), func(c *gin.Context) {
name := c.MustGet("name").(string) // 从上下文取值
log.Println(name)
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
})
为路由组注册中间件
为路由组注册中间件有以下两种写法。
写法1
go
shopGroup := r.Group("/shop", StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
写法2
go
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
中间件注意事项
gin默认中间件
gin.Default()
默认使用了 Logger
和 Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会 recover 任何 panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用 gin.New()
新建一个没有任何默认中间件的路由。
gin中间件中使用 goroutine
当在中间件或 handler
中启动新的 goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本( c.Copy()
)。