美文网首页
golang源码解析(2),路由

golang源码解析(2),路由

作者: 哆啦在这A梦在哪 | 来源:发表于2021-05-19 18:55 被阅读0次

一。前言

前面说了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}

image.png
/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即可。根节点变成本次插入的节点

image.png
/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即可。此时,根节点就是当前这个节点。

image.png
/
/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即可。后面同理,先匹配前缀,只需要留本次的路径即可。

image.png
/
/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}

image.png
/
/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}

image.png
/
/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}

image.png
/
/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}

image.png
/
/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
代码里都有,自己慢慢理解吧,说的太麻烦,也没有意义,就是数据结构的实际体现。
好了所有的都说完了,谢谢阅读,如有指导,请速联系或留言,万福

回到第一章前言:https://www.jianshu.com/p/e2ffc43a7ca9

相关文章

网友评论

      本文标题:golang源码解析(2),路由

      本文链接:https://www.haomeiwen.com/subject/ybvadltx.html