公众号:程序员读书,欢迎关注
Gin
框架是支持中间件的,那什么是中间件呢?
在不同的场景下,中间件有不同的含义,而在Gin
框架中,中间件可以看作是请求拦截器,主要用在请求处理函数被调用前后执行一些业务逻辑,比如用户权限验证,数据编码转换,记录操作日志,接口运行时间统计等。
在Gin
框架中,中间件的本质是也是一个处理请求的函数,其函数签名都是:
go
func(ctx *gin.Context) {}
当添加一个中间件时,实际上就是在处理请求的函数处理链中添加一个节点。
中间件的使用
设置中间件使用gin.RouteGroup
的Use
方法。
在使用中间件时,可以将中间件设置为对全部路由有效,也可以只对某些路由分组有效,或者只对部分请求有效。
gin.Engine
实际上是最大的路由分组,因此直接调用gin.Engine
对象的Use
方法设置的中间件对全部路由有效:
go
package main
import "github.com/gin-gonic/gin"
func main() {
engine := gin.New()
//对所有路由有效
engine.Use(gin.Logger())
engine.GET("/index", func(ctx *gin.Context) {})
engine.GET("/list", func(ctx *gin.Context) {})
engine.Run()
}
路由分组设置中间件时,只对该分组有效:
go
package main
import "github.com/gin-gonic/gin"
func main() {
engine := gin.New()
//对所有路由有效
engine.Use(gin.Logger())
//在user中设置的中间件只对user分组下的路由有效
user := engine.Group("/user", func(ctx *gin.Context) {
//中间件1
})
user.Use(func(ctx *gin.Context) {
//中间件2
})
{
user.GET("/:id", func(ctx *gin.Context) {})
user.POST("/add", func(ctx *gin.Context) {})
}
engine.Run()
}
也可以在设置路由为单个路由添加设置中间件:
go
package main
import "github.com/gin-gonic/gin"
func main() {
engine := gin.New()
user.GET("/list", func(ctx *gin.Context) {
//中间件
},func(ctx *gin.Context){
//路由处理函数
})
engine.Run()
}
上面的例子中,可以看到,通过GET
等方法添加多个路由处理函数时,在前面的处理函数便是路由。
内置中间件
Gin
内置了异常恢复(Recovery()
)、日志(Logger()
)和用户授权(BasicAuth()
)等几个中间件,实际上,当我们用Default()
函数创建一个gin.Engine
对象时,默认就已经使用了异常恢复与日志这两个中间件:
scss
gin.Default()
//等价于
gin.New()
gin.Use(gin.Recovery(),gin.Logger())
异常恢复
直接调用Recovery()
创建的异常恢复中间件会把异常信息输出到控制台,也可以调用RecoveryWithWriter()
将异常信息写入到日志文件:
go
package main
import (
"os"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.New()
logFile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0775)
engine.Use(gin.RecoveryWithWriter(logFile))
engine.GET("/index", func(ctx *gin.Context) {
panic("模拟异常")
})
engine.Run()
}
日志
直接调用Logger()
中间件时,会把日志输出到控制台,而LoggerWithWriter()
可以把日志写入到日志文件:
go
package main
import (
"os"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.New()
logFile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0775)
engine.Use(gin.LoggerWithWriter(logFile))
engine.GET("/index", func(ctx *gin.Context) {
fmt.Fprintf(ctx.Writer, "业务处理\n")
})
engine.Run()
}
也可以调用LoggerWithFormatter
格式化日志输出:
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.New()
engine.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("[请求]%v %3d %13v %s %-7s %#v\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.StatusCode,
param.Latency,
param.ClientIP,
param.Method,
param.Path,
param.ErrorMessage,
)
}))
engine.GET("/index", func(ctx *gin.Context) {
fmt.Fprintf(ctx.Writer, "业务处理\n")
})
engine.Run()
}
如果要定制日志格式且同时输出到日志文件,则需要调用LoggerWithConfig
:
go
package main
import (
"fmt"
"os"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.New()
logFile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0775)
engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{
Formatter: func(param gin.LogFormatterParams) string {
return fmt.Sprintf("[请求]%v %3d %13v %s %-7s %#v\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.StatusCode,
param.Latency,
param.ClientIP,
param.Method,
param.Path,
param.ErrorMessage,
)
},
Output: logFile,
}))
engine.GET("/index", func(ctx *gin.Context) {
fmt.Fprintf(ctx.Writer, "业务处理\n")
})
engine.Run()
}
用户授权
用户授权调用BasicAuth()
中间件:
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.New()
accounts := gin.Accounts{
"小明": "123456",
}
router.Use(gin.BasicAuth(accounts))
router.GET("/index", func(ctx *gin.Context) {
fmt.Fprintf(ctx.Writer, "业务处理\n")
})
router.Run()
}
其他中间件
另外,Gin
官方在GitHub
上也有另外一个仓库专门用于存储中间件,其地址为:
自定义中间件
Gin
官方提供的中间件毕竟有限,当这些中间件无法满足我们的业务需求时,我们可以自定义中间件。
在Gin
框架中,一般通过调用一个函数来返回一个中间件,比如官方的Logger
等中间件就是这样的做法,在下面的例子中,我们通过Counter()
函数返回一个中间件:
scss
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
// 自定义中间件:用于统计接口执行时间
func Counter() gin.HandlerFunc {
return func(ctx *gin.Context) {
//请求前
start := time.Now().Unix()
fmt.Fprintf(ctx.Writer, "拦截前\n")
ctx.Next()
//请求后
end := time.Now().Unix()
fmt.Fprintf(ctx.Writer, "接口%s执行时间为:%d秒\n", ctx.Request.URL, end-start)
fmt.Fprintf(ctx.Writer, "拦截后\n")
}
}
func main() {
router := gin.New()
//调用自定义的中间件
router.Use(Counter())
router.GET("/index", func(ctx *gin.Context) {
fmt.Fprintf(ctx.Writer, "业务处理\n")
})
router.Run()
}
运行后发起请求,结果如下:
bash
$ curl http://localhost:8080/index
拦截前
业务处理
接口/index执行时间为:0秒
拦截后
Next方法
从上面例子的运行结果可以看出,一个中间件的处理逻辑被gin.Context
的Next()
方法划分为请求处理前后两个部分,而路由处理函数则在Next()
被调用的时候开始执行。
Abort方法
当用户请求不满足某些条件时,比如用户没有权限,可以在中间件中调用Abort
方法中断当前的请求:
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
// 自定义中间件:用于统计接口执行时间
func MyAuth() gin.HandlerFunc {
return func(ctx *gin.Context) {
token := ctx.Query("token")
if token == "" {
ctx.Abort()
return
}
//处理token,获取用户信息
}
}
func main() {
router := gin.New()
//调用自定义的中间件
router.Use(MyAuth())
router.GET("/index", func(ctx *gin.Context) {
fmt.Fprintf(ctx.Writer, "业务处理\n")
})
router.Run()
}
注意,调用Abort
方法后,则不会再向下调用业处理函数,但如果没有使用return
语句,则该中间件的后续代码仍会执行。
小结
Gin
中间件是一种简单但又强大的机制,类似于Java
中的切面编程,这种机制允许我们在请求处理函数的前后执行某些逻辑,而不需要修改原先业务逻辑。