在上一篇结束的时候我们留下了两个问题,他们分别是:
1.关于Gin每次请求都是下标式请求,如何实现的?
2.关于路由注册时Gin是如何组装这些请求的并且最终实现下标式请求?
今天我们将针对这两个问题进行深入学习:
首先我们先思考一下如果实现上面的方式我们需要如下步骤:
1.当我们在注册路由的时候,针对于我们的路由地址,我们需要一个handlefunc数组
2.当我们的服务再接收到HTTP请求的时候,把我们请求的地址解析,并对应到我们设置的路由中
3.并得到handlefunc的数组,依次执行数组中的方法
当这里我们有一个初步的概念了,那么接下来我们看一下Gin是怎么实现的。
通过RouterGroup 结构体 的方法我们可以找到其中有一个addRoute的方法,依次寻找原点我们可以快速定位在 Gin中存在一个tree行结构并且树的结构体下有个addRoute的方法,用于填充树,这个方法也是我们今天的重点学习目标!
首先我们先简单看一下这个树的结构中包含了哪些属性:
type node struct { path string //用于存放设置的路由,当然这里是经过解析后的路由,并不是和设置路由完全对应 wildChild bool //当为param时为true,意可以任意搭配 nType nodeType maxParams uint8 indices string //用来存放每个子节点第一个字符拼接成的字符串。 children []*node handlers HandlersChain //用来存储每个节点所需要执行func priority uint32 }
现在我们我们开始攻破这个树复杂的解析方法
node.addRoute方法解析
先贴上代码
func (n *node) addRoute(path string, handlers HandlersChain) { fullPath := path n.priority++ numParams := countParams(path) // non-empty tree if len(n.path) > 0 || len(n.children) > 0 { walk: for { // Update maxParams of the current node if numParams > n.maxParams { n.maxParams = numParams } // Find the longest common prefix. // This also implies that the common prefix contains no ':' or '*' // since the existing key can't contain those chars. i := 0 max := min(len(path), len(n.path)) for i < max && path[i] == n.path[i] { i++ } // Split edge if i < len(n.path) { child := node{ path: n.path[i:], wildChild: n.wildChild, indices: n.indices, children: n.children, handlers: n.handlers, priority: n.priority - 1, } // Update maxParams (max of all children) for i := range child.children { if child.children[i].maxParams > child.maxParams { child.maxParams = child.children[i].maxParams } } n.children = []*node{&child} // [] byte for proper unicode char conversion, see #65 n.indices = string([]byte{n.path[i]}) n.path = path[:i] n.handlers = nil n.wildChild = false } // Make new node a child of this node if i < len(path) { path = path[i:] if n.wildChild { n = n.children[0] n.priority++ // Update maxParams of the child node if numParams > n.maxParams { n.maxParams = numParams } numParams-- // Check if the wildcard matches if len(path) >= len(n.path) && n.path == path[:len(n.path)] { // check for longer wildcard, e.g. :name and :names if len(n.path) >= len(path) || path[len(n.path)] == '/' { continue walk } } panic("path segment '" + path + "' conflicts with existing wildcard '" + n.path + "' in path '" + fullPath + "'") } c := path[0] // slash after param if n.nType == param && c == '/' && len(n.children) == 1 { n = n.children[0] n.priority++ continue walk } // Check if a child with the next path byte exists for i := 0; i < len(n.indices); i++ { if c == n.indices[i] { i = n.incrementChildPrio(i) n = n.children[i] continue walk } } // Otherwise insert it if c != ':' && c != '*' { // []byte for proper unicode char conversion, see #65 n.indices += string([]byte{c}) child := &node{ maxParams: numParams, } n.children = append (n.children, child) n.incrementChildPrio(len(n.indices) - 1) n = child } n.insertChild(numParams, path, fullPath, handlers) return } else if i == len(path) { // Make node a (in-path) leaf if n.handlers != nil { panic("handlers are already registered for path ''" + fullPath + "'") } n.handlers = handlers } return } } else { // Empty tree n.insertChild(numParams, path, fullPath, handlers) n.nType = root } }
关于AddRoute方法的学习主要为大家整理几条思路。因为这中间有很多细节可能需要大家自己去学习。我梳理的思路如下:
1.此时methodsTree中不存在对应的method时,会直接进入insertChild方法保存第一个子节点
2.当对应的methodTree存在时,会进入一个walk循环,首先会进行相同部分判断并用相同作为子节点。也就是我们设置的路由在树结构中会被父子节点
下面我们来看一个简单的图例,相信大家在看到这个图例的时候就会大概明白中间的道理了。
图1为:路由注册
图2是图1按照顺序添加后的变化过程
总结
今天我们为大家分析了gin在注册完路由后是如何保存在自己的树结构中的,这里建议大家还是详细的进入Gin的源码中仔细的阅读一下,个人觉得这个树结构的写法是封装好的值得学习一下。
方法入口:进入gin源码文件,打开tree.go,搜索AddRoute方法