likes
comments
collection
share

Gin框架完全使用指南 | Gin框架中间件详解

作者站长头像
站长
· 阅读数 8

公众号:程序员读书,欢迎关注

Gin框架是支持中间件的,那什么是中间件呢?

在不同的场景下,中间件有不同的含义,而在Gin框架中,中间件可以看作是请求拦截器,主要用在请求处理函数被调用前后执行一些业务逻辑,比如用户权限验证,数据编码转换,记录操作日志,接口运行时间统计等。

Gin框架中,中间件的本质是也是一个处理请求的函数,其函数签名都是:

 func(ctx *gin.Context) {}

当添加一个中间件时,实际上就是在处理请求的函数处理链中添加一个节点。

中间件的使用

设置中间件使用gin.RouteGroupUse方法。

在使用中间件时,可以将中间件设置为对全部路由有效,也可以只对某些路由分组有效,或者只对部分请求有效。

gin.Engine实际上是最大的路由分组,因此直接调用gin.Engine对象的Use方法设置的中间件对全部路由有效:

 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()
 }

路由分组设置中间件时,只对该分组有效:

 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()
 }

也可以在设置路由为单个路由添加设置中间件:

 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对象时,默认就已经使用了异常恢复与日志这两个中间件:

 gin.Default()
 //等价于
 gin.New()
 gin.Use(gin.Recovery(),gin.Logger())

异常恢复

直接调用Recovery()创建的异常恢复中间件会把异常信息输出到控制台,也可以调用RecoveryWithWriter()将异常信息写入到日志文件:

 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()可以把日志写入到日志文件:

 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格式化日志输出:

 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

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()中间件:

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上也有另外一个仓库专门用于存储中间件,其地址为:

github.com/gin-contrib

自定义中间件

Gin官方提供的中间件毕竟有限,当这些中间件无法满足我们的业务需求时,我们可以自定义中间件。

Gin框架中,一般通过调用一个函数来返回一个中间件,比如官方的Logger等中间件就是这样的做法,在下面的例子中,我们通过Counter()函数返回一个中间件:

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()
}

运行后发起请求,结果如下:

$ curl http://localhost:8080/index
拦截前
业务处理
接口/index执行时间为:0秒
拦截后

Next方法

从上面例子的运行结果可以看出,一个中间件的处理逻辑被gin.ContextNext()方法划分为请求处理前后两个部分,而路由处理函数则在Next()被调用的时候开始执行。

Abort方法

当用户请求不满足某些条件时,比如用户没有权限,可以在中间件中调用Abort方法中断当前的请求:

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中的切面编程,这种机制允许我们在请求处理函数的前后执行某些逻辑,而不需要修改原先业务逻辑。

转载自:https://juejin.cn/post/7328112739187703817
评论
请登录