Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
1.定义中间件
Gin中的中间件必须是一个gin.HandlerFunc类型。
洋葱模型
看上图,我们的中间件就相当于一层一层的洋葱。如果要到洋葱内部,需要一层一层的进入。同理,出来也是一层一层的出来。
统计耗时请求耗时的中间件
// 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 库,一行代码解决前后端分离架构下的跨域问题。
2.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()全局作用了两个中间件。
r:=gin.Default()
//Default()的定义:
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
3.注册中间件
为全局注册中间件
r := gin.Default()//全局中间件的注册
r.Use(Cost())//全局中间件的注册
r.GET("/middleware", M2)
为路由组注册中间件
写法1:
shopGroup := r.Group("/shop", Cost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
写法2:
shopGroup := r.Group("/shop")
shopGroup.Use(Cost())
{
shopGroup.GET("/index", func(c *gin.Context) {...})
...
}
为单个路由注册中间件
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,
})
})
5.在中间件中使用 Goroutine
当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本。
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())
}
}