前言
本文是探讨的是"Go高级之从源码分析Gin框架的路由链
"
此文章是个人学习归纳的心得,掘金首发,未经允许,严禁转载,如有不对, 还望斧正, 感谢!
什么是Gin框架
Gin 是一个用 Go 语言开发的 Web
框架,提供类 Martini
的 API,并且由于GO语言的特性,然后性能更好。由于采用了httprouter
库,它的性能比同类框架快了 40 倍左右。如果你需要一个高性能、高开发效率的框架,那么 Gin 就非常适合你,我很喜欢Go中文网对它的描述------晶莹剔透
Gin 提供了一系列的功能,包括但不限于路由管理、中间件、上下文参数传递等。它还支持多种数据格式,包括JSON、XML、HTML 等。此外,它还具有插件机制,可以方便地扩展框架的功能。 Gin 的主要优点如下:
- 高性能:由于采用了
httprouter
库,Gin 的性能非常出色,比同类框架快了 40 倍左右。 - 易用性:Gin 提供了一系列的内置功能,使你可以轻松地实现 Web 开发。
- 扩展性:Gin 具有插件机制,可以方便地扩展框架的功能。
其实就是一句话,Gin框架对新手很友好
Gin框架的核心
要想用一篇文章讲完一个框架,很有难度,我将分开来讲,主要是从方法链,Gin的九颗方法树,上下文处理来讲
初识Gin核心理念
Gin框架的实现原理主要涉及以下几个核心部分:
-
路由匹配与处理:Gin使用
基数树(Radix Tree)
来管理路由,将路由路径分解为多个节点,通过匹配路径的前缀来快速找到对应的路由处理函数。当有新的路由注册时,Gin会根据路径构建对应的节点,并将处理函数与该节点绑定。在请求到来时,Gin会从根节点开始遍历路由树,根据请求路径匹配到对应的处理函数进行执行。 -
中间件机制:Gin的中间件机制是通过方法链实现的。每个中间件都是一个函数,它接收一个上下文对象(Context)和一个函数参数(next),并在执行过程中可以处理请求和响应,然后通过调用next()函数将控制权交给下一个中间件。这样,多个中间件可以形成一个链式调用的过程,依次对请求进行处理和控制。
-
上下文管理:Gin的上下文对象(Context)封装了一次HTTP请求的上下文信息,包括请求参数、请求头、响应内容等。在处理请求过程中,可以通过上下文对象获取和设置这些信息。Gin通过将上下文对象作为参数传递给中间件和路由处理函数,实现了在这些函数之间共享数据和状态的能力。
-
异常处理:Gin框架内置了对异常的处理机制。当发生异常时,Gin会捕获异常并返回一个合适的错误响应。同时,Gin还提供了一些辅助方法,如Abort()和AbortWithStatus(),用于在处理过程中终止请求并返回特定的错误响应。
-
并发处理:Gin框架使用goroutine来实现并发处理请求。每个请求都会在独立的goroutine中执行,这样可以提高服务器的并发处理能力。
细品函数链
Engine结构体
gin框架的核心就是Engine结构体,我给一些字段加了必要的注释,我们一开始的 gin.Default()
或者gin.New()
其实都是实例化一个Engine结构体
go
type Engine struct {
RouterGroup
// RedirectTrailingSlash启用自动重定向,如果当前路由无法匹配但存在没有尾部斜杠的路径的处理程序。
// 例如,如果请求/foo/,但只有/foo的路由存在,那么对于GET请求,客户端将被重定向到/foo,并返回301状态码,对于其他请求方法,返回307状态码。
RedirectTrailingSlash bool
// RedirectFixedPath如果启用,路由器将尝试修复当前请求路径,如果没有为其注册处理程序。
// 首先,会删除多余的路径元素,如../或//。
// 然后,路由器对清理后的路径进行不区分大小写的查找。
// 如果能找到此路由的处理程序,则路由器将使用状态码301对GET请求进行重定向,对于其他请求方法,返回307状态码。
// 例如,/FOO和/..//Foo可能会被重定向到/foo。
// RedirectTrailingSlash与此选项无关。
RedirectFixedPath bool
// HandleMethodNotAllowed如果启用,路由器将检查当前路由是否允许使用其他方法,
// 如果当前请求无法路由。
// 如果是这种情况,请求将以"Method Not Allowed"响应,并返回HTTP状态码405。
// 如果没有其他方法被允许,则将请求委托给NotFound处理程序。
HandleMethodNotAllowed bool
// ForwardedByClientIP如果启用,将从请求的头部中解析与`(*gin.Engine).RemoteIPHeaders`匹配的客户端IP。
// 如果未获取到IP,则回退到从`(*gin.Context).Request.RemoteAddr`获取的IP。
ForwardedByClientIP bool
// AppEngine已废弃。
// Deprecated: 使用值为gin.PlatformGoogleAppEngine的`TrustedPlatform`代替
// #726 #755 如果启用,将信任以"X-AppEngine..."开头的某些头部,以更好地与该PaaS集成。
AppEngine bool
// UseRawPath如果启用,将使用url.RawPath查找参数。
UseRawPath bool
// UnescapePathValues如果为true,则解码路径值。
// 如果UseRawPath为false(默认情况下),则UnescapePathValues实际上为true,
// 因为将使用url.Path,而url.Path已经解码。
UnescapePathValues bool
// RemoveExtraSlash即使有额外的斜杠,也可以从URL中解析参数。
// 参见PR#1817和问题#1644
RemoveExtraSlash bool
// RemoteIPHeaders用于在`(*gin.Engine).ForwardedByClientIP`为`true`且
// `(*gin.Context).Request.RemoteAddr`匹配`(*gin.Engine).SetTrustedProxies()`定义的网络源列表之一时,
// 获取客户端IP的头部列表。
RemoteIPHeaders []string
// TrustedPlatform如果设置为值为gin.Platform*的常数,将信任该平台设置的头部,例如用于确定客户端IP
TrustedPlatform string
// MaxMultipartMemory是传递给http.Request的ParseMultipartForm方法调用的'maxMemory'参数的值。
MaxMultipartMemory int64
// UseH2C启用h2c支持。
UseH2C bool
// ContextWithFallback在Context.Request.Context()不为nil时,启用回退Context.Deadline(),Context.Done(),Context.Err()和Context.Value()。
ContextWithFallback bool
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool
trees methodTrees //方法树列表,是一个数组
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
在这个结构体里面,还内嵌了一个很重要的结构体 RouterGroup,这种内嵌的效果,其实类似于其他语言中的继承
,Engine结构体得到了,RouterGroup结构体的属性和方法
RouterGroup结构体
RouterGroup
结构体的源码如下
go
type RouterGroup struct {
Handlers HandlersChain //存储将要执行的函数,包括路由中间件和路由函数
basePath string // 路径
engine *Engine // 指向根结构体
root bool // 标志是不是根结构体
}
第一个字段其实就是方法链,用来储存方法,详细结构如下
go
// 起别名
type HandlerFunc func(*Context)
// 定义函数切片类型
type HandlersChain []HandlerFunc
到这里你肯定还是懵的,不急,接下来我们再分析一下RouterGroup
结构体身上的几个常用的方法,相信到后面,你应该也能明白,大概是怎么一回事
Use方法使用中间件
先是Use
方法,我们一般使用Use
函数来使用中间件函数,看下面的代码,相信你也明白了,其实Use
一个中间件,就是把这个中间件函数放到了RouterGroup
中的Handlers
函数链里面去了,通过使用append
函数,来添加。
scss
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
Group方法划分路由组
然后是Group
方法,这个方法,我们一般用来划分路由组,就比如说 http://192.168.127.1:9090/demo/one 和http://192.168.127.1:9090/demo/two 我们可以先用Group
方法,划分一个demo
路由组出来,然后再进行相关的操作。
这个方法,新建了一个RouterGroup
结构体,调用了两个函数group.combineHandlers()
和group.calculateAbsolutePath()
函数,然后返回了这个新建的RouterGroup
结构体的地址
csharp
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers), //该字段存储将要执行的函数,包括路由中间件和路由函数
basePath: group.calculateAbsolutePath(relativePath), // 路径
engine: group.engine, // 指向根结构体
}
}
group.combineHandlers()
我们再看看group.combineHandlers()
函数,其实就是将新使用的中间件塞入函数链,这个函数是内置,没有开放给外界使用
go
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
来看看具体解释
该函数的目的是将当前RouterGroup
的中间件处理函数和传入的handlers
合并成一个新的中间件处理函数链。
函数接收一个handlers
参数,它是一个HandlersChain
类型,表示一组中间件处理函数。HandlersChain
是一个自定义类型,它实际上是一个切片,存储了多个中间件处理函数。
函数首先计算了合并后的中间件处理函数链的长度,通过将当前RouterGroup
中已有中间件处理函数的数量和传入的handlers
的数量相加得到。然后,使用assert1
函数进行断言,确保合并后的长度没有超过abortIndex
的最大值。abortIndex
是一个常量,用于限制中间件处理函数链的最大长度。
接下来,函数创建了一个长度为finalSize
的切片mergedHandlers
,用于存储合并后的中间件处理函数链。然后,通过copy
函数将当前RouterGroup
的中间件处理函数复制到mergedHandlers
的起始位置,再将传入的handlers
复制到mergedHandlers
的末尾位置。
最后,函数返回合并后的中间件处理函数链mergedHandlers
。
group.calculateAbsolutePath()函数
go
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
group.calculateAbsolutePath()
函数,用于计算绝对路径。它接收一个相对路径作为参数,然后调用joinPaths
函数将相对路径与路由组的基本路径拼接在一起,得到一个完整的绝对路径。
joinPaths
函数接收一个绝对路径和一个相对路径作为参数,将它们拼接在一起并返回拼接后的路径。首先,函数会检查相对路径是否为空,如果为空,则直接返回绝对路径。接下来,函数使用path.Join
函数将绝对路径和相对路径拼接在一起,得到最终路径。然后,函数会检查相对路径的最后一个字符是否为'/',如果是,并且最终路径的最后一个字符不是'/',则在最终路径的末尾添加'/'。最后,函数返回最终路径。
这段代码的作用是将路由组的基本路径和相对路径拼接在一起,得到一个完整的绝对路径。它处理了相对路径为空和最终路径的最后一个字符的情况,确保返回的路径是正确的。
然后介绍的就是Next
方法,这个方法常用在中间件里面,当我们在一个中间件中需要执行后面的中间件,我们就可以使用Next函数,如下,其实就是执行了函数链中的下一个函数,对了,我们通过gin.Default()
得到的默认的日志中间件,其实就是用了Next
方法
scss
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
当路由被访问的时候,然后就会依次执行函数链里面的函数。
总结
-
Engine
是gin
的核心结构体,它包含了RouterGroup
结构体,实现了路由的注册和中间件的添加等功能。RouterGroup
用来表示一个路由组,它包含了中间件处理函数链Handlers
、路径basePath
、引擎指针engine
等字段。RouterGroup
实现了路由组相关的添加路由,添加中间件等操作。
-
- 当路由被匹配时,会依次调用
Handlers
链中的中间件函数和路由处理函数。
- 当路由被匹配时,会依次调用
-
Engine
和RouterGroup
实现了分组路由和中间件的机制,形成了路由注册和中间件添加的链式调用风格式, gin通过这种机制,提供了强大的路由与中间件功能,形成了简洁的API风格。