golang源码解析(2),路由

一。前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容