Gin源码分析 – 中间件 – 介绍及使用

1 介绍

中间件是Gin的精髓。一个个中间件组成一条中间件链,对HTTP Request请求进行拦截处理,实现了代码的解耦和分离,每个中间件只需要处理自己需要处理的业务。今天我们就通过这篇文章,详细的介绍Gin中间的使用和原理。

2 中间件介绍

2.1 什么是中间件

中间件,英译middleware,顾名思义,放在中间的物件,那么放在谁中间呢?本来,客户端可以直接请求到服务端接口。

Gin源码分析 - 中间件 - 介绍及使用

现在,中间件横插一脚,它能在请求到达接口之前拦截请求,做一些特殊处理,比如日志记录,故障处理等。这就是今天要讲述的中间件,那么,它在Gin框架中是怎么使用的呢?

Gin源码分析 - 中间件 - 介绍及使用

2.2 中间件的作用

简单来说,Gin中间件的作用有两个:

(1)Web请求到到达我们定义的HTTP请求处理方法之前,拦截请求并进行相应处理(比如:权限验证,数据过滤等),这个可以类比为前置拦截器或前置过滤器

(2)在我们处理完成请求并响应客户端时,拦截响应并进行相应的处理(比如:添加统一响应部头或数据格式等),这可以类型为后置拦截器或后置过滤器

2.3 中间件的定义

在Gin框架中,中间件的类型定义如下代码所示,可以看出,中间件实际上就是一个以gin.Context为形参的函数而已,与我们定义处理HTTP请求的Handler本质上是一样的,并没有什么神秘可言。

type HandlerFunc func(*Context)

2.4 内置中间件

在使用Gin框架开发Web应用时,常常需要自定义中间件,不过,Gin也内置一些中间件,我们可以直接使用,下面是内置中间件列表:

func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc
func ErrorLogger() HandlerFunc
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger() HandlerFunc
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH(h http.Handler) HandlerFunc

3 中间件的使用

3.1 默认情况

使用gin.Default()返回的gin.Engine时,已经默认使用了Recovery和Logger中间件,从下面gin.Default() 的代码可以看出:

// Default returns an Engine instance with the 
// Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

如果不想使用任何中间件,可以使用gin.New()方法返回一个不带中间件的gin.Engine对象:

router := gin.New()

3.2 全局使用中间件

直接使用gin.Engine结构体的Use()方法便可以在所有请求应用中间件,这样做,中间件便会在全局起作用。如上面的Default中的情况。

3.3 分组使用中间件

更多的时候,我们会根据业务不同划分不同路由分組(RouterGroup ),不同的路由分组再应用不同的中间件,在这种下就是在路由分组中局部使用中间件。在下面的例子中就是在user的分组中使用了Logger和 Recovery中间件。

router := gin.New()
user := router.Group("user", gin.Logger(), gin.Recovery()) {
   user.GET("info", func(context *gin.Context) {
      ...
   })
   user.GET("article", func(context *gin.Context) {
      ...
   })
}

3.4 单个路由使用中间件

除了路由分组,在单个请求路由中也可以应用中间件,代码如下所示,在GET(‘/test’)的路由中单独使用了Recovery中间件。

router := gin.New()
router.GET("/test", gin.Recovery(), func(c *gin.Context){
   c.JSON(200,"test")
})

在下面的代码中,在GET(‘/test’)的路由中使用了多个中间件。

router := gin.New()
router.GET("/test", gin.Recovery(), gin.Logger(), func(c *gin.Context){
  c.JSON(200,"test")
})

4 中间件的开发

虽然Gin提供了一些中间件,我们直接使用即可,但内置中间件可能满足不我们业务开发的需求,在开发过程中我们需要开自己的中间件,同时Gin的官网也维护了一些第三方的中间件,下面说一下如何开发中间件。

4.1 中间件定义

自定义gin中间件有两种写法。

(1)定义一个方法接收一个*gin.Context类型的参数,和handler的写法是一样的。代码参考如下:

func MyMiddleware(c *gin.Context){
    
}

router = gin.Default()
router.Use(MyMiddleware)

(2)定义一个返回值为HandlerFunc类型的函数,参考代码如下:

func MyMiddleware(){
    return func(c *gin.Context){
    }
}

router = gin.Default()
router.Use(MyMiddleware())

第二种方法封装性更好一些,在这类方法中可以在MyMiddleware中进行大量的初始化工作,提供了更好的封装性。这个也是Gin中所有的中间件的默认使用方法。

4.2 中间件的开发流程

在开发中间件的过程中主要使用4个常用的方法,它们分别是c.Next()、c.Abort()、c.Set()、c.Get()。

(1)c.Next()将请求传递给请求链中下一个处理方法;

(2)c.Abort()当处理出现错误时将提前结束请求,即不执行后续的中间件任务了;

(3)c.Set(),c.Get()用于在中间件和最终的业务处理方法中传递数据。

一个中间件的基本框架流程如下,

func MyMiddleware(c *gin.Context){
    // 开始部分    
    beforeProcess()
    // 保存数据
    c.Set()
    // 交给下一个中间件处理
    c.Next()
    // 收尾处理
    endProcess()
}

4.2 Next的作用

当在一个路由中注册了多个中间件时,就能形成一个串行的处理流程,Next将整个处理流程分成了开始部分和结束部分。

Next 应该仅可以在中间件中使用,它在调用的函数中的链中执行挂起的函数。

4.3 Abort的作用

在执行过程中,可以调用该方法退出后续的操作,并返回,该方法有这样几个变化形式,下面三个方法中断请求后,直接返回200或者错误码,但响应的body中不会有数据。

Abort 在被调用的函数中阻止挂起函数。

func (c *Context) Abort()
func (c *Context) AbortWithError(code int, err error) *Error
func (c *Context) AbortWithStatus(code int)

下面这个方法可以返回一个JSON响应

func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})

4.4 Set和Get的作用

c.Set()和c.Get()这两个方法多用于在多个函数之间通过c传递数据的。比如我们可以在认证中间件中获取当前请求的相关信息(userID等)通过c.Set()存入;然后在后续处理业务逻辑的函数中通过c.Get()来获取当前请求的用户。

似乎他们的设计逻辑支持泛型:

其实这两个所属的知识点,官方名称是:

Metadata Management (是个Key-Value模式)

这个模块比较简单, 就是从gin.Context中Set Key-Value, 以及各种个样的Get方法, 如GetBool, GetString等

其实就是一个map[string]interface{}

value是个接口,因此可以存入任意类型,不过目前的需求看来只需要保存“字符串”

无论如何,在实战过程中,逻辑越简单越好

毫无疑问他被设计成了“支持泛型

5 演示说明

上面的说明其实还是太抽象,下面用一个实际的例子对整个过程进行一个说明,回过头再对照上面的说明,就更清楚啦。

5.1 例子1

在这个例子中,没有调用c.Next(),可以看到输出实际上就是将多个func串行执行。

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

func fun1(c *gin.Context) {
    fmt.Println("fun1 start")
    fmt.Println("fun1 end")
}

func fun2(c *gin.Context) {
    fmt.Println("fun2 start")
    fmt.Println("fun2 end")
}

func fun3(c *gin.Context) {
    fmt.Println("fun3 start")
    fmt.Println("fun3 end")
}

func main() {
    r := gin.Default()

    r.Use(fun1, fun2, fun3)

    // Example ping request.
    r.GET("/ping", func(c *gin.Context) {
        fmt.Println("ping start")

	c.String(http.StatusOK, "pong")

	fmt.Println("ping end")
    })

    r.Run(":8080")
}

结果:

fun1 start
fun1 end
fun2 start
fun3 end
fun3 start
fun4 end
ping start
ping end

5.2 例子2

在这个例子中,增加了对c.Next()的调用,可以看到输出符合我么的预期。

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

func fun1(c *gin.Context) {
    fmt.Println("fun1 start")
    c.Next()
    fmt.Println("fun1 end")
}

func fun2(c *gin.Context) {
    fmt.Println("fun2 start")
    c.Next()
    fmt.Println("fun2 end")
}

func fun3(c *gin.Context) {
    fmt.Println("fun3 start")
    c.Next()
    fmt.Println("fun3 end")
}

func main() {
    r := gin.Default()

    r.Use(fun1, fun2, fun3)

    // Example ping request.
    r.GET("/ping", func(c *gin.Context) {
	fmt.Println("ping start")

	c.String(http.StatusOK, "pong")

	fmt.Println("ping end")
    })

    r.Run(":8080")
}

结果:

fun1 start
fun2 start
fun3 start
ping start
ping end
fun1 end
fun3 end
fun4 end

5.3 例子3

下面这个例子给出了c.Set()和c.Get()的用法。

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

func fun1(c *gin.Context) {
    fmt.Println("fun1 start")
    c.Set("key1", "val1")
    c.Next()
    fmt.Println("fun1 end")
}

func fun2(c *gin.Context) {
    fmt.Println("fun2 start")
    c.Set("key2", "val2")
    c.Next()
    fmt.Println("fun2 end")
}

func fun3(c *gin.Context) {
    fmt.Println("fun3 start")
    c.Set("key3", "val3")
    c.Next()
    fmt.Println("fun3 end")
}

func main() {
    r := gin.Default()

    r.Use(fun1, fun2, fun3)

    // Example ping request.
    r.GET("/ping", func(c *gin.Context) {
	fmt.Println("ping start")

	fmt.Println("key1 = ", c.GetString("key1"))
	fmt.Println("key2 = ", c.GetString("key2"))
	fmt.Println("key3 = ", c.GetString("key3"))

	c.String(http.StatusOK, "pong")

	fmt.Println("ping end")
    })

    r.Run(":8080")
}

6 总结

本文首先对Gin中的中间件的定义以及使用、扩展开发进行了一个基本的介绍,下一篇文章将对关键的代码进行分析,最后将对一些常用中间件的实现进行分析。