一。前言
前面说了gin大体的理解方向,这里一起整理第一个侧重点,路由的实现。
他的基本流程就是匹配API的路由,执行对应的hander方法
第一章说了大致的流程:https://www.jianshu.com/p/e2ffc43a7ca9
1.1问题方向确定
这整个的过程里面我们只要了解清楚下面三个问题,也就知道gin是如何运行起来了
如何构造路由结构?
如何匹配路由?
如何分配handler?
1.2基础代码探究
看如下代码
首先Engine继承了RouterGroup,所以获取他的所有属性
type Engine struct {
RouterGroup
trees methodTrees
}
路由直接的表示就是Use和Group函数。这个RouterGroup就是最直接的和用户使用体验的结构,所有的路由方法也在改对象下。
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
2.路由结构的构造
底层使用了数据结构中的“基数树”,的结构。由于比较抽象,下面用一个例子来表示添加多个路由的底层结构变化过程,不了解的同学先去了解一下,因为下面都是以他为基础的讲解
2.1用例子实际解释路由构造的过程
底层实现的基本数据结构,以下面这个例子为基准(由于实际gin代码内部是这些过程的实现,而且内容多,理解该结构后自己探索吧)
2.1.1代码路由
func GinInit() {
e := gin.Default()
e.Use(f1)
e.POST("/p1", f2)
e.POST("/p", f3)
e.POST("/", f4)
e.POST("/p1/p34", f5)
e.POST("/p12", f6)
e.POST("/p12/p56", f7)
e.POST("/p12/p56/:id", f8)
e.POST("/p12/p56/:id/p78", f9)
e.Run()
}
func f1(ctx *gin.Context) {}
func f2(ctx *gin.Context) {}
func f3(ctx *gin.Context) {}
func f4(ctx *gin.Context) {}
func f5(ctx *gin.Context) {}
func f6(ctx *gin.Context) {}
func f7(ctx *gin.Context) {}
func f8(ctx *gin.Context) {}
func f9(ctx *gin.Context) {}
2.2底层数据结构变换示意图(图文讲解,以下step为操作的步骤,分别用图和实际的数据结构表示操作后当前的树的直观表现)
基树的构建
构建的过程其实是不断寻找最长前缀的过程。
从数据结构变化来看具体实现:
最初的数据时:engine.roots = nil
step1: engine.Use(f1)
step2:添加{method: POST, path: /p1, handler:f2}
/p1
node_/p1 = {
path:"/p1"
indices:""
handlers:[f1, f2]
priority:1
nType:root
maxParams:0
wildChild:false
}
engine.roots = [{
method: POST,
root: node_/p1
}]
step3:添加{method: POST, path: /p, handler:f3}
说明:添加当前这个路由的时候,由于/p
重复,所以可以提取,第一步中的那个节点,只需要存一个1
即可。根节点变成本次插入的节点
/p
/p1
node_/p = {
path:"/p"
indices:"1"
handlers:[f1, f3]
priority:2
nType:root
maxParams:0
wildChild:false
children: [
{
path:"1"
indices:""
children:[]
handlers: [f1, f2]
priority:1
nType:static
maxParams:0
wildChild:false
}
]
}
engine.roots = [{
method: POST,
root: node_/p
}]
step4:添加{method: POST, path: /, handler:f4}
说明:这一步同理。这个/
,也可以提取为相同的前缀,前面step3
中插入的节点中的/
就不用存,只留一个p
即可。此时,根节点就是当前这个节点。
/
/p
/p1
node_/ = {
path:"/"
indices:"p"
handlers:[f1, f4]
priority:3
nType:root
maxParams:0
wildChild:false
children:[
{
path:"p"
indices:"1"
handlers:[f1, f3]
priority:2
nType:static
maxParams:0
wildChild:false
children:[
{
path:"1"
indices:""
children:[]
handlers:[f1, f2]
priority:1
nType:static
maxParams:0
wildChild:false
}
]
}
]
}
engine.roots = [{
method: POST,
root: node_/
}]
step5:添加{method: POST, path: /p1/p34, handler:f5}
说明:添加这个节点时,由于前面的前缀已经有了,所以本节点只需存一个/p34
即可。后面同理,先匹配前缀,只需要留本次的路径即可。
/
/p
/p1
/p1/p34
node_/ = {
path:"/"
indices:"p"
handlers:[f1, f4]
priority:4
nType:root
maxParams:0
wildChild:false
children:[
{
path:"p"
indices:"1"
handlers:[f1, f3]
priority:3
nType:static
maxParams:0
wildChild:false
children:[
{
path:"1"
indices:""
handlers:[f1, f2]
priority:2
nType:static
maxParams:0
wildChild:false
children:[
{
path:"/p34"
indices:""
handlers:[f1, f5]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
]
}
]
}
engine.roots = [{
method: POST,
root: node_/
}]
step6:添加{method: POST, path: /p12, handler:f6}
/
/p
/p1
/p1/p34
/p12
node_/ = {
path:"/"
indices:"p"
handlers:[f1, f4]
priority:5
nType:root
maxParams:0
wildChild:false
children:[
{
path:"p"
indices:"1"
handlers:[f1, f3]
priority:4
nType:static
maxParams:0
wildChild:false
children:[
{
path:"1"
indices:"/2"
handlers:[f1, f2]
priority:3
nType:static
maxParams:0
wildChild:false
children:[
{
path:"/p34"
indices:""
handlers:[f1, f5]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
{
path:"2"
indices:""
handlers:[f1, f6]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
]
}
]
}
step7:添加{method: POST, path: /p12/p56, handler:f7}
/
/p
/p1
/p1/p34
/p12
/p12/p56
node_/ = {
path:"/"
indices:"p"
handlers:[f1, f4]
priority:5 + 1
nType:root
maxParams:0
wildChild:false
children:[
{
path:"p"
indices:"1"
handlers:[f1, f3]
priority:4 + 1
nType:static
maxParams:0
wildChild:false
children:[
{
path:"1"
indices:"2/"
handlers:[f1, f2]
priority:3 + 1
nType:static
maxParams:0
wildChild:false
children:[
{
path:"2"
indices:""
handlers:[f1, f6]
priority:2
nType:static
maxParams:0
wildChild:false
children:[
{
path:"/p56"
indices:""
handlers:[f1, f7]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
{
path:"/p34"
indices:""
handlers:[f1, f5]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
]
}
]
}
step8:添加{method: POST, path: /p12/p56/:id, handler:f8}
/
/p
/p1
/p1/p34
/p12
/p12/p56
/p12/p56/:id
node_/ = {
path:"/"
indices:"p"
handlers:[f1, f4]
priority:6 + 1
nType:root
maxParams:0 + 1
wildChild:false
children:[
{
path:"p"
indices:"1"
handlers:[f1, f3]
priority:5 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"1"
indices:"2/"
handlers:[f1, f2]
priority:4 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"2"
indices:""
handlers:[f1, f6]
priority:2 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"/p56"
indices:""
handlers:[f1, f7]
priority:1 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"/"
indices:""
handlers:[]
priority:1
nType:static
maxParams:1
wildChild:false
children:[
{
path:":id"
indices:""
handlers:[f1, f8]
priority:1
nType:param
maxParams:1
wildChild:false
children:[]
}
]
}
]
}
]
}
{
path:"/p34"
indices:""
handlers:[f1, f5]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
]
}
]
}
step9:添加{method: POST, path: /p12/p56/:id/p78, handler:f9}
/
/p
/p1
/p1/p34
/p12
/p12/p56
/p12/p56/:id
node_/ = {
path:"/"
indices:"p"
handlers:[f1, f4]
priority:7 + 1
nType:root
maxParams:0 + 1
wildChild:false
children:[
{
path:"p"
indices:"1"
handlers:[f1, f3]
priority:6 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"1"
indices:"2/"
handlers:[f1, f2]
priority:5 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"2"
indices:"/"
handlers:[f1, f6]
priority:3 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"/p56"
indices:""
handlers:[f1, f7]
priority:2 + 1
nType:static
maxParams:1
wildChild:false
children:[
{
path:"/"
indices:""
handlers:[]
priority:1 + 1
nType:static
maxParams:1
wildChild:true
children:[
{
path:":id"
indices:""
handlers:[f1, f8]
priority:1 + 1
nType:param
maxParams:1
wildChild:false
children:[
{
path:"/p78"
indices:""
handlers:[f1, f9]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
]
}
]
}
]
}
{
path:"/p34"
indices:""
handlers:[f1, f5]
priority:1
nType:static
maxParams:0
wildChild:false
children:[]
}
]
}
]
}
]
}
到这里,整棵树已经构造完成了,就等请求了,接下来来看看路由匹配
3.路由匹配
前言:gin只是实现了一个自己的路由器,所以,只要看他的ServeHTTP方法即可。
代码在gin.go:439
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)//获取一个Context
// 这三步都是Context的内容处理,因为从复用池中获取需要处理一下,清理旧数据,加入新数据
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)//实际的逻辑处理函数
engine.pool.Put(c)//使用后放回,使用pool提交效率
}
接下来看他实际内部处理(这里主要看重点,其他略过先,细节自行深究)
func (engine *Engine) handleHTTPRequest(c *Context) {
......这里一些赋值操作,以及一些路由判定
// Find root of the tree for the given HTTP method,这里获取到匹配到的树
//核心就是这个
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
//主要看下面这句,这是获取实际内容,核心中的核心
//这个方法内部实现下面单独说,这里一步步来,先把流程走通
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()// 到这里context已经获取了所有的属性,在这个方法中去执行逻辑。
c.writermem.WriteHeaderNow()
return
}
。。。。。。一些其他匹配
break
}
。。。。。。一些其他判断,err或者无匹配等等问题的处理
}
接下来看看Next怎么做
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {//直接循环,就是遍历所有handlers元素
c.handlers[c.index](c)//执行对应方法,这一步,就是执行了您写在path后面的那个方法
c.index++
}
}
接下来说说上面那个最核心的root.getValue
代码里都有,自己慢慢理解吧,说的太麻烦,也没有意义,就是数据结构的实际体现。
好了所有的都说完了,谢谢阅读,如有指导,请速联系或留言,万福
网友评论