Gin框架中间件原理

先了解闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

go 复制代码
func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60

	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}

Gin框架中中间件

在Gin框架中,中间件(Middleware)是在处理请求的过程中,在请求被真正的处理函数处理之前或之后执行的一些逻辑。

其原理主要是通过函数式编程和Go语言的闭包特性来实现的。

当定义一个中间件时,实际上是定义了一个函数,这个函数接收gin.Context作为参数。例如:

go 复制代码
func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
       // 记录请求开始时间
       t := time.Now()
       // 调用下一个中间件或者处理函数
       c.Next()
       // 计算请求耗时并打印日志
       latency := time.Since(t)
       log.Printf("%s %s %v", c.Request.Method, c.Request.URL.Path, latency)
    }
}

在这个中间件函数内部,通过c.Next()来调用下一个中间件或者最终的处理函数。

当使用Use方法添加中间件到Gin引擎时,Gin会将这些中间件函数组成一个链式结构。当一个请求到来时,Gin会从链头开始逐个执行中间件函数。在每个中间件函数中,可以在c.Next()调用之前进行前置操作,比如验证用户身份、记录日志等;在c.Next()调用之后进行后置操作,比如添加响应头、记录响应时间等。这样就实现了对请求处理过程的拦截和增强。

具体实现

以下是Gin框架将中间件函数组成链式结构的具体实现原理及相关细节:

1. 核心数据结构

在Gin框架中,有一个关键的结构体(简化示意),类似如下:

go 复制代码
type Engine struct {
    // 用于存储中间件函数的切片
    middlewares []gin.HandlerFunc
    // 路由树等其他相关结构,这里重点关注中间件相关部分
    //...
}

Engine结构体中的middlewares切片就是用来存放通过Use方法添加进来的中间件函数的,每个中间件函数都是gin.HandlerFunc类型,它的定义本质上是一个接收*gin.Context作为参数并返回void的函数类型,例如:

go 复制代码
type HandlerFunc func(*gin.Context)

2. Use方法的作用

当调用EngineUse方法来添加中间件时,代码逻辑大致如下(简化版):

go 复制代码
func (engine *Engine) Use(middleware...gin.HandlerFunc) {
    engine.middlewares = append(engine.middlewares, middleware...)
}

Use方法就是简单地将传入的中间件函数一个个追加到middlewares切片中,这样就完成了中间件的收集,为后续构建链式结构做准备。

3. 构建链式结构(核心在于请求处理流程)

当有请求进来时,Gin会从已添加的中间件链头开始逐个执行中间件函数,这个过程是在处理请求的主逻辑中实现的。在Gin内部处理请求的函数中(简化示意如下):

go 复制代码
func (engine *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    c := engine.pool.Get().(*gin.Context)
    c.Writer = &responseWriter{Writer: w}
    c.Request = r

    // 遍历中间件切片,构建链式调用
    h := engine.combineHandlers()
    h(c)

    engine.pool.Put(c)
}

关键在于engine.combineHandlers()这个方法,它会将middlewares切片中的中间件函数组合起来,返回一个最终的处理函数(也是gin.HandlerFunc类型),组合的方式大概是通过闭包和函数嵌套来实现类似如下逻辑(简化示意,实际更复杂):

go 复制代码
func (engine *Engine) combineHandlers() gin.HandlerFunc {
    return func(c *gin.Context) {
        var currentIndex int
        var nextHandler func()
        // 定义下一个中间件或最终处理函数的执行逻辑
        nextHandler = func() {
            if currentIndex < len(engine.middlewares) {
                middleware := engine.middlewares[currentIndex]
                currentIndex++
                // 调用当前中间件,并在当前中间件内部调用nextHandler来执行后续的中间件或最终处理函数
                middleware(c)
            } else {
                // 如果中间件都执行完了,执行路由对应的最终处理函数(比如某个具体的控制器方法)
                engine.handleContext(c)
            }
        }
        // 启动链式调用,从第一个中间件开始
        nextHandler()
    }
}

在上述代码中:

  • 通过定义nextHandler函数,它会根据当前中间件的索引来决定是调用下一个中间件还是执行最终的路由处理函数(engine.handleContext(c)部分,这里省略其具体细节,它主要涉及根据路由匹配去执行对应的业务逻辑处理函数)。
  • 每个中间件函数内部通过调用c.Next()其实就是间接调用了这个nextHandler函数,从而实现了控制权在中间件之间以及最终处理函数之间的有序转移,构建起了链式结构,使得请求在处理过程中会依次经过各个中间件,并且在合适的时机执行后续操作或者返回响应。

总的来说,Gin借助切片收集中间件函数,然后在请求处理阶段通过巧妙的函数嵌套、闭包以及对函数执行顺序的控制,实现了将中间件函数组成链式结构来处理请求的机制。

理解gin中间件和go直接作为http server的中间件有什么区别?

Gin中间件实现原理总结

  • Gin中间件是基于Go语言的函数式编程和闭包来实现的。它主要围绕gin.Context进行操作。gin.Context包含了请求和响应的所有信息,如请求方法、请求路径、请求头、响应状态码等。
  • 链式调用 :Gin通过将多个中间件函数组合成一个链来工作。当一个请求到达时,它会按添加中间件的顺序依次执行这些中间件。每个中间件函数可以选择在调用c.Next()之前执行一些前置操作,如权限验证、日志记录开始时间等。
  • 控制权转移c.Next()函数是关键。当执行到c.Next()时,它会暂停当前中间件的执行,将控制权转移给下一个中间件或者最终的处理函数。在所有中间件(以及最终处理函数)执行完返回后,c.Next()之后的代码会继续执行,用于完成后置操作,如记录响应时间、修改响应头等。

与Go直接作为http server中间件的区别

  • 抽象程度不同
    • Go原生http中间件 :在Go原生的http包中写中间件,需要直接操作http.Requesthttp.ResponseWriter。这两个对象提供了相对底层的对HTTP请求和响应的操作接口,开发者需要自己处理诸如读取请求头、写入响应状态码等细节。
    • Gin中间件 :Gin的中间件基于gin.Context,它对http.Requesthttp.ResponseWriter进行了封装,提供了更高级、更方便的API。例如,可以通过c.Query("param_name")直接获取URL查询参数,而不用像在原生http中那样手动解析。
  • 中间件组织形式不同
    • Go原生http中间件 :在Go原生http中,若要构建中间件链,通常需要自己手动调用下一个中间件函数。这可能会导致代码结构不够清晰,特别是当中间件数量较多时,容易出现混乱。
    • Gin中间件 :Gin通过Use方法将中间件添加到引擎,自动构建中间件链。这种方式更加清晰、简洁,使得中间件的管理和复用更加方便。
  • 功能丰富度不同
    • Go原生http中间件:Go原生的中间件功能相对基础,主要聚焦于对HTTP基本操作的拦截和处理。
    • Gin中间件:Gin中间件除了基本的HTTP操作外,还提供了许多与Web开发密切相关的功能。比如,Gin的中间件可以很方便地集成日志记录、错误处理、权限验证等多种功能,并且这些功能可以通过Gin提供的插件或者自定义中间件轻松扩展。
相关推荐
DanceDonkey1 小时前
基于wait/notify方法 实现生产/消费者模型
java·数据库·中间件
喝醉的小喵1 小时前
【traefik】forwadAuth中间件跨namespace请求的问题
网关·中间件·k8s·traefik
等一场春雨1 小时前
MySQL 主从同步中间件
数据库·mysql·中间件
问仙长何方蓬莱7 小时前
中间件 | RocketMq - [broker 配置]
中间件·rocketmq
huaqianzkh21 小时前
深入学习Topic Exchange交换机
中间件·系统架构·rabbitmq
Mike铭2 天前
Golang Gin 实现无本地文件下载功能
开发语言·golang·gin
桃园码工3 天前
12-Gin 中的 Session --[Gin 框架入门精讲与实战案例]
gin·session·实战案例·入门精讲
web_132334214363 天前
【服务治理中间件】consul介绍和基本原理
中间件·consul
测开小林3 天前
Fastapi + vue3 自动化测试平台(2)--日志中间件
自动化测试·中间件·fastapi·测试工具开发