Gin 中间件的编写和使用——Go Gin框架(六)

中间件

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

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

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

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

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

Gin的中间件

定义:

// HandlerFunc defines(定义) the handler(处理器) used by gin middleware(中间件) as return value.
// HandleFunc函数 定义了一个返回值可以被用作gin中间件的处理器。
type HandlerFunc func(*Context)

从一开始创建Gin engine使用的函数gin.Default()源码如下:

// Default returns an Engine instance(实例) with the Logger and Recovery middleware(中间件) already attached(附加).
// Default函数 返回一个已经附加了Logger和Recovery的中间件引擎实例
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

其中第5行enging.Use(Logger(), Recovery())就是一个使用中间件的实例。

  • func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes:对引擎中所有的请求,附加中间件。
  • 参数 middleware ...HandlerFunc:需要被添加的多个中间件
// Use attaches(附加、附属品) a global(总体的,全局的) middleware to the router.
//ie. the middleware attached though Use() will be included in the handlers chain(链) for every single(单一的) request.
//【中间件将通过Use()被附加在每一个处理器请求中。】
//Even(即使、甚至) 404, 405, static files...
//For example, this is the right(正确的) place for a logger or error management(管理) middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}
  • Logger() 返回的是一个日志的中间件,它可以在控制台输出调试日志。
// Logger instances(实例) a Logger middleware that will write the logs to gin.DefaultWriter.
// By default gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc {
    return LoggerWithConfig(LoggerConfig{})
}
  • Recovery() 返回的是一个用于捕获任何panic的中间件,并在捕获之后,如果只有一个panic的情况下,其向客户端返回一个500错误。
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() HandlerFunc {
    return RecoveryWithWriter(DefaultErrorWriter)
}

如何自定义一个中间件

根据上文实例,中间件有两个要点:
1. 中间件是一个func
2. 该func返回值的类型是HandleFunc

例:

方法一:使用 Use() 函数

该方法适用于为一整个engine中所有的请求添加中间件。

中间件调用函数

func main(){
    engine := gin.Default()
    engine.POST("/yyy",funcfunc(context *gin.Context){
        /*TODO 业务逻辑*/
    })//这个yyy的POST请求没有绑定中间件

    //第一种添加中间件的方法(为以下所有方法添加中间件)
    engine.Use(requestInfo())//从这行代码以下定义的请求才会绑定requestInfo()中间件

    engine.Get("/xxx", funcfunc(context *gin.Context){
        /*TODO 业务逻辑*/
    })//该xxx的GET请求已绑定了request中间件。
    engine.Run()
}

中间件函数

//用于向控制台输出请求信息的中间件
func requestInfo() gin.HandlerFunc {
    return func(context *gin.Context) {
        //执行具体业务之前运行
        fmt.Println("(middleware)request route: ", context.FullPath(), "     request method: ", context.Request.Method)
        fmt.Println("(middleware)wrong status code:", context.Writer.Status()) //这里获取到的状态码应该是错误的(永远都是200)

        //开始执行具体业务
        context.Next()

        //执行完具体业务之后
        fmt.Println("(middleware)right status code:", context.Writer.Status())
    }
}

方法二:单个Handle使用中间件的方式

该方法适用于对某一个Handle处理器使用特定的中间件

处理器函数

engine.GET("/M4SH", requestInfo(), func(context *gin.Context) {
    fmt.Println("↓---运行具体业务---↓")
    context.JSON(404, map[string]interface{}{
        "code": 0,
        "msg":  "咕噜灵波",
    })
    fmt.Println("↑---运行具体业务---↑")
})
  • func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes:第一种方法Use()函数,只为在该语句后面的处理器函数附加中间件。其使用了可变参数,意味着该函数可以同时添加多个中间件。
  • func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes:第二种方法,在定义处理器的同时,直接附加该处理器所需要的中间件。该函数也可以传入可变参数。一般情况下,会把真正处理业务逻辑的匿名函数放在最后一个参数位置上。
  • func (c *Context) Next()context.Next()只允许使用在中间件函数中。该函数可以把一个中间件函数分成三层。

有些中间件功能必须在第三层后续语句中运行,例如:

    fmt.Println("(middleware)wrong status code:", context.Writer.Status()) //这里获取到的状态码是错误的(是200)
    context.Next()      //开始执行具体业务,例如具体业务返回了404错误码。
    //执行完具体业务之后
    fmt.Println("(middleware)right status code:", context.Writer.Status())//获取到的状态码是404,正确
点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注