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

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

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

github.com/gin-contrib

自定义中间件

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

相关推荐
金銀銅鐵1 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路1 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x2 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn4 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈4 小时前
AI 读你的提示词,其实很没耐心
后端
用户8356290780515 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123455 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒5 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记5 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk598785 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端