先了解闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境
func adder() func(int) int {var x intreturn func(y int) int {x += yreturn x}
}
func main() {var f = adder()fmt.Println(f(10)) //10fmt.Println(f(20)) //30fmt.Println(f(30)) //60f1 := adder()fmt.Println(f1(40)) //40fmt.Println(f1(50)) //90
}
Gin框架中中间件
在Gin框架中,中间件(Middleware)是在处理请求的过程中,在请求被真正的处理函数处理之前或之后执行的一些逻辑。
其原理主要是通过函数式编程和Go语言的闭包特性
来实现的。
当定义一个中间件时,实际上是定义了一个函数,这个函数接收gin.Context
作为参数。例如:
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框架中,有一个关键的结构体(简化示意),类似如下:
type Engine struct {// 用于存储中间件函数的切片middlewares []gin.HandlerFunc// 路由树等其他相关结构,这里重点关注中间件相关部分//...
}
Engine
结构体中的middlewares
切片就是用来存放通过Use
方法添加进来的中间件函数的,每个中间件函数都是gin.HandlerFunc
类型,它的定义本质上是一个接收*gin.Context
作为参数并返回void
的函数类型,例如:
type HandlerFunc func(*gin.Context)
2. Use
方法的作用
当调用Engine
的Use
方法来添加中间件时,代码逻辑大致如下(简化版):
func (engine *Engine) Use(middleware...gin.HandlerFunc) {engine.middlewares = append(engine.middlewares, middleware...)
}
Use
方法就是简单地将传入的中间件函数一个个追加到middlewares
切片中,这样就完成了中间件的收集,为后续构建链式结构做准备。
3. 构建链式结构(核心在于请求处理流程)
当有请求进来时,Gin会从已添加的中间件链头开始逐个执行中间件函数,这个过程是在处理请求的主逻辑中实现的。在Gin内部处理请求的函数中(简化示意如下):
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
类型),组合的方式大概是通过闭包和函数嵌套来实现类似如下逻辑(简化示意,实际更复杂):
func (engine *Engine) combineHandlers() gin.HandlerFunc {return func(c *gin.Context) {var currentIndex intvar 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.Request
和http.ResponseWriter
。这两个对象提供了相对底层的对HTTP请求和响应的操作接口,开发者需要自己处理诸如读取请求头、写入响应状态码等细节。 - Gin中间件:Gin的中间件基于
gin.Context
,它对http.Request
和http.ResponseWriter
进行了封装,提供了更高级、更方便的API。例如,可以通过c.Query("param_name")
直接获取URL查询参数,而不用像在原生http
中那样手动解析。
- Go原生http中间件:在Go原生的
- 中间件组织形式不同
- Go原生http中间件:在Go原生
http
中,若要构建中间件链,通常需要自己手动调用下一个中间件函数。这可能会导致代码结构不够清晰,特别是当中间件数量较多时,容易出现混乱。 - Gin中间件:Gin通过
Use
方法将中间件添加到引擎,自动构建中间件链。这种方式更加清晰、简洁,使得中间件的管理和复用更加方便。
- Go原生http中间件:在Go原生
- 功能丰富度不同
- Go原生http中间件:Go原生的中间件功能相对基础,主要聚焦于对HTTP基本操作的拦截和处理。
- Gin中间件:Gin中间件除了基本的HTTP操作外,还提供了许多与Web开发密切相关的功能。比如,Gin的中间件可以很方便地集成日志记录、错误处理、权限验证等多种功能,并且这些功能可以通过Gin提供的插件或者自定义中间件轻松扩展。