Golang gin middleware的编写与使用 context.Next函数

中间件


在web应用服务中,完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。

在实际的业务开发和处理中,会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度的系统支持。

鉴权认证、权限管理、安全检查、日志记录等这些保障和支持系统业务属于全系统的业务,和具体的系统业务没有关联,对于系统中的所有业务都适用。

由此,在业务开发过程中,为了更好的梳理系统架构,可以将上述描述所涉及的一些通用业务单独抽离并进行开发,然后以插件化的形式进行对接。这种方式既保证了系统功能的完整,同时又有效的将具体业务和系统功能进行解耦,并且,还可以达到灵活配置的目的。

这种通用业务独立开发并灵活配置使用的组件,一般称之为"中间件",因为其位于服务器和实际业务处理程序之间。其含义就是相当于在请求和具体的业务逻辑处理之间增加某些操作,这种以额外添加的方式不会影响编码效率,也不会侵入到框架中。中间件的位置和角色示意图如下图所示:

比如在浏览电商网站进行购买的时候要进行下单或者购买一些商品,那么需要进行登入,在对登入这个功能而言,那么很多模块都会使用到,都会使用到登入的权限。所以可以将登入的功能做成统一的模块,在需要的地方配置登入的权限,调用这个登入的功能。这样就不需要在下单购买商品的时候还需要去判断是不是已经登入了或者有没有权限。这样可以达到功能的细分,方便开发人员聚焦具体的业务逻辑,这是解耦的一种开发方式。 

还是下单的例子,如果最后一步是下单,那么中间有很多操作,权限操作,认证的管理等等都可以放在中间件这个地方。这样权限的认证就不需要在下单这一块写了。

那么中间件写的这一部分不仅仅下单的这个模块可以调用,其他的模块也可以调用。这就是中间件的一个作用,最后程序结束了原路的返回就行了。

由此可见中间件的作用是非常明显的。

Gin的中间件


在gin中,中间件称之为middieware,中间件的类型定义如下所示:

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

HandlerFunc是一个函数类型,接收一个Context参数。用于编写程序处理函数并返回HandleFunc类型,作为中间件的定义。

type HandlerFunc func(*Context) 其实就代表着是一个中间件。 

中间件Use用法


在之前学习的课程中,均使用gin.Default创建了gin引擎engins变量,其中,就使用了中间件。如下图所示:

这里可以看到设置了两个默认的中间件,一个是日志,一个是修复,当程序发生错误的时候会返回500错误,同时将程序发生Panic的时候恢复回来。这样可以让请求比较友好一些,帮助排查服务器内部的错误。

在Default函数中,engine调用Use方法设置了Logger中间件covery中间件。Use函数接收一个可变参数,类型为HandlerFunc,恰为中间件的类型。Use方法定义如下:

这个use方法接受的是可变参数有三个点,可变参数存放的类型是HandlerFunc。这样可以按照自己的自定义需求来添加任意多个中间件。

自定义中间件


根据上文的介绍,可以自己定义实现一个特殊需求的中间件,中间件的类型是函数,有两条标准:

  • func函数
  • 返回值类型为HandlerFunc

比如,我们自定义一个自己的中间件。在前面所学的内容中,我们在处理请求时,为了方便代码调试,通常都将请求的一些信息打印出来。有了中间件以后,为了避免代码多次重复编写,使用统一的中间件来完成。定义一个名为RequestInfos的中间件,在该中间件中打印请求的path和method。具体代码实现如下所示:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
)

func RequestInfo() gin.HandlerFunc {
	return func(context *gin.Context) {
		path := context.FullPath()
		method := context.Request.Method
		fmt.Println("请求路径为:", path, "请求方法:", method)
	}
}

func main() {
	engine := gin.Default()
	engine.Use(RequestInfo())
	engine.GET("/query", func(c *gin.Context) {
		c.JSON(http.StatusOK, map[string]interface{}{
			"code": 1,
			"msg":  c.FullPath(),
		})
	})
	engine.Run()
}

请求路径为: /query 请求方法: GET
[GIN] 2023/06/14 - 15:54:38 | 200 |     114.125µs |       127.0.0.1 | GET      "/query"

如果有多个请求,想单独的为某个接口解析来使用中间件的话,那么可以在接口解析的方法当中对其单独的使用,使用中间件作为参数传递进去。

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes
第二个是可变一类的参数
	engine.GET("/get", RequestInfo(), func(context *gin.Context) {

	})

上面可以看到中间件在打印的调试信息都是在请求之前打印的,都是在接口解析之前。当某个接口解析处理之后的信息也通过中间件打印出来。

context.Next函数


通过一个例子和使用场景来说明Next函数的作用。

在上文自定义的中间件RequestInfos中,打印了请求了请求的path和method,接着去执行了正常的业务处理函数。如果我们想输出业务处理结果的信息,该如何实现呢。答案是使用context.Next函数。

context.Next函数可以将中间件代码的执行顺序一分为二,Next函数调用之前的代码在请求处理之前之前,当程序执行到context.Next时,会中断向下执行,转而先去执行具体的业务逻辑,执行完业务逻辑处理函数之后,程序会再次回到context.Next处,继续执行中间件后续的代码。

具体用法如下:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func RequestInfo() gin.HandlerFunc {
	return func(context *gin.Context) {
		path := context.FullPath()
		method := context.Request.Method
		fmt.Println("请求路径为:", path, "请求方法:", method)
		fmt.Println("状态码信息:", context.Writer.Status())

		context.Next() //这样会将func代码5行一分为二,第一部分是前面三行,执行完第一部分之后到了next
		//它就不再往下执行了,那么就会走下一个中间件的解析

		fmt.Println("状态码信息:", context.Writer.Status())
	}
}

func main() {
	engine := gin.Default()
	engine.Use(RequestInfo())
	engine.GET("/query", func(c *gin.Context) {
		c.JSON(404, map[string]interface{}{
			"code": 1,
			"msg":  c.FullPath(),
		})
		fmt.Println("handler func 业务代码")
	})
	engine.Run()
}


请求路径为: /query 请求方法: GET
状态码信息: 200
handler func 业务代码
状态码信息: 404
[GIN] 2023/06/14 - 16:37:42 | 404 |      61.458µs |       127.0.0.1 | GET      "/query"

可以看到next方法将中间件的执行分为了两个部分,这样可以方便处理后续的业务逻辑。

猜你喜欢

转载自blog.csdn.net/qq_34556414/article/details/130952287