gin web 是一个 go 的开源框架 他在保持简洁小巧的设计 的同时又保持了不错的性能 着其中也得益于 他在解析路由的时候用到了 httprouter 这个开源的路由解析框架 gin框架将get set 请求分配到不同的rpouter 数上进行解析。
type methodTree struct {
//GET SET
method string //GET SET 方法
root *node //路由tree
}
以下是树的结构
//节点类型
const (
static nodeType = iota // default //静态节点 比如这个节点只是 分离出来的 本身并不承载路由 当两个节点有公共的前缀的时候 就会分裂 出一个静态节点
root //第一个节点 所有节点的父节点
param//普通的带有处理器的节点
catchAll//模糊匹配的节点
)
type node struct {
path string // 例如: /a
indices string //分裂分支的第一个字符 假设 /abc 和 /adb 那么 此时 就会是 bd
children []*node //子节点
handlers HandlersChain //请求处理器
priority uint32 //优先级
nType nodeType //节点类型
maxParams uint8 //路径上的最大个参数
wildChild bool //是否模糊节点
fullPath string //全路径
}
Gin框架中的 GET PUT 都会绑定不同的路有树node 来处理路由请求
//取最长公共前缀
func longestCommonPrefix(a, b string) int {
i := 0
max := min(len(a), len(b))
for i < max && a[i] == b[i] {
i++
}
return i
}
// Search for a wildcard segment and check the name for invalid characters.
// Returns -1 as index, if no wildcard was found.
//返回 通配符 假设 test:tongpeifu 那么 wilcard 就是 tongpeifu i 返回通配符开始的索引 valid 返回 比如caomao:1233:是否符合规范
// 这样就是不对的
func findWildcard(path string) (wilcard string, i int, valid bool) {
// Find start
for start, c := range []byte(path) {
// A wildcard starts with ':' (param) or '*' (catch-all)
//如果 不是以 : 或者 * 开头的 字符 则 返回 -1 false
if c != ':' && c != '*' {
continue
}
// Find end and check for invalid characters
valid = true
// 寻找 通配符 满足上面条件 以 : 或 * 开头 循环遍历下面的字符
//如果遇到:asdasdad 或者 :12312321* 这样的结尾 代表最后一级菜单 开始结束
//如果遇到:asdasd/ 或者*sadsad/ 这样的 就要截断 到/之前 部分
for end, c := range []byte(path[start+1:]) {
switch c {
case '/':
return path[start : start+1+end], start, valid
//如果遇到 *123123* :12313* *213123: :213123: 这样的 就通不过验证
case ':', '*':
valid = false
}
}
return path[start:], start, valid
}
//如果没有找到 在如 /adasd 这样的 路由里面 没找到 * 或者 :这样的通配符 就直接返回
return "", -1, false
}
func countParams(path string) uint16 {
var n uint
for i := range []byte(path) {
switch path[i] {
case ':', '*':
n++
}
}
return uint16(n)
}
type nodeType uint8
const (
static nodeType = iota // default
root
param
catchAll
)
type node struct {
path string
indices string
wildChild bool
nType nodeType
priority uint32
children []*node
handle Handle
}
// 并不影响 核心功能 但是 可以加快 孩子节点 查找速度 对应priority 字段 优先级越高
//那么子节点数量就越多 那么把 调整下 数组索引 使它比优先级小的索引 更小 数组在循环的时候遍历
//的速度就变快了
func (n *node) incrementChildPrio(pos int) int {
cs := n.children
cs[pos].priority++
prio := cs[pos].priority
// Adjust position (move to front)
newPos := pos
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
// Swap node positions
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
}
// Build new index char string
//对子节点进行排序后需要 把 indices这个字段同时更新下 因为节点位置 发生了交换
// 比如 原来 /a childrens[ /basd /ca /dew /ecs ] indices = 各孩子首字母 = bcde => [ /ca /basd /dew /ecs ] = cbde
//
if newPos != pos {
n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
n.indices[pos:pos+1] + // The index char we move
n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
}
return newPos
}
// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handle Handle) {
fullPath := path
n.priority++
// Empty tree
//当树是没有被初始化时
if len(n.path) == 0 && len(n.indices) == 0 {
n.insertChild(path, fullPath, handle)
n.nType = root
return
}
walk:
for {
// 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 := longestCommonPrefix(path, n.path)
// Split edge
//因为 包含公共前缀 所以需要 分割
//这里要考虑两种情况 假设 先有n.path = /a 再有 path = /abc 那么 i = len(取公共前缀("/a","/abc")) = 2 len(n.path) =2 那么 不会产生分裂节点
//第二种情况 假设 先有n.path = /abc 再有 path = /a 那么 i = len(取公共前缀("/a","/abc")) = 2 len(n.path) = 4 此时 i< len(n.path) 那么便会产生分裂的节点
if i < len(n.path) {
child := node{
path: n.path[i:],//公共前缀后面的部分
wildChild: n.wildChild,
nType: static,//静态节点
indices: n.indices,
children: n.children,//把当前节点的孩子节点给分割出来的节点
handle: n.handle,//处理函数
priority: n.priority - 1, //优先级减1
}
//上面这段代码 假设已 有 路由/abc 如果这时添加了一个 路由/asd 那么 就会把 /abc 这个节点的
//信息拷贝出来 然后传建一个新的 节点并且 节点的路径 是 bc
//把 当前节点的子节点赋值为 新创建出来的 复制了原有节点信息的节点
n.children = []*node{&child}
// 记录分裂节点的 第一个字 /abc /asd => b
n.indices = string([]byte{n.path[i]})
//节点的路劲 变为 /a
n.path = path[:i]
//由于静态节点不存在 处理器 所以 nil
n.handle = nil
//静态节点 不是模糊匹配节点
n.wildChild = false
}
//这里当他们 不能分裂出静态节点时 这里假设/ 本身 不会分裂
//当 n.path = /abc path = /a 时 i = 2 有公共前缀 此时 i = len(path)
//当 n.path = /a path = /abc 时 i = 2 有公共前缀 此时 i < len(path)
//当 n.path = /asd path = /bcx 时 其实和上面一种情况是一样的 i = 1 此时 i < len(path)
// Make new node a child of this node
if i < len(path) {
//取出节点的不包含公共前缀的部分
path = path[i:]
//如果是* 这样模糊匹配的节点
if n.wildChild {
//取出第一个孩子节点
n = n.children[0]
//优先级加1
n.priority++
// Check if the wildcard matches
//如果 加入的path 长度 大于 本节点 的长度 并且 本节点 path 和 path路径 符合
if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
// Adding a child to a catchAll is not possible
//增加的是 模糊匹配的节点
n.nType != catchAll &&
// Check for longer wildcard, e.g. :name and :names
//模糊匹配节点 或者带 /的节点
(len(n.path) >= len(path) || path[len(n.path)] == '/') {
continue walk
} else {
// Wildcard conflict
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")
}
}
//取出第一个字符
idxc := path[0]
// 查找 '/' 节点下 第一个节点
//如果当前节点是 param 类型节点 并且 第一个字符是 "/" 并且 只有一个子节点
if n.nType == param && idxc == '/' && len(n.children) == 1 {
//当前节点等于 子节点
n = n.children[0]
//优先级加1
n.priority++
//跳转到walk
continue walk
}
// Check if a child with the next path byte exists
//循环查找 如果记录的子节点的第一个 字符 和当前要插入节点 的 以一个字符相符
for i, c := range []byte(n.indices) {
if c == idxc {
//优先级排序
i = n.incrementChildPrio(i)
//当前节点为 子节点
n = n.children[i]
//跳转walk
continue walk
}
}
// 如果 添加的 节点 既不是 * 也不是: 这样的 通配 节点 就执行插入
if idxc != ':' && idxc != '*' {
// []byte for proper unicode char conversion, see #65
//把药加入的节点首字符添加进当前节点
n.indices += string([]byte{idxc})
child := &node{}
//添加孩子节点
n.children = append(n.children, child)
//刚加入的节点 优先级 +1
n.incrementChildPrio(len(n.indices) - 1)
//当前节点更新为 新节点
n = child
}
//添加节点
n.insertChild(path, fullPath, handle)
return
}
// 如果当前节点 一斤有了一个处理函数 那么就报错
if n.handle != nil {
panic("a handle is already registered for path '" + fullPath + "'")
}
// 添加 处理函数
n.handle = handle
return
}
}
func (n *node) insertChild(path, fullPath string, handle Handle) {
for {
// 找到通配符 并返回 通配符
wildcard, i, valid := findWildcard(path)
//如果没有 通配符 就是跳出这个 for 循环
if i < 0 { // No wilcard found
break
}
// The wildcard name must not contain ':' and '*'
//找到的 含有通配符的router 是否 符合 规则 :123123: 类似这样的就会报错
if !valid {
panic("only one wildcard per path segment is allowed, has: '" +
wildcard + "' in path '" + fullPath + "'")
}
//通配符 必须要有名字 如果只是 : * 的话 那没名字也会报错 必须至少带有一个后缀 如 : *d :a
// Check if the wildcard has a name
if len(wildcard) < 2 {
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
}
// Check if this node has existing children which would be
// unreachable if we insert the wildcard here
//如果这个节点已经有子节点了 就不能插入了
if len(n.children) > 0 {
panic("wildcard segment '" + wildcard +
"' conflicts with existing children in path '" + fullPath + "'")
}
//这里处理 通配符 是 :的情况
if wildcard[0] == ':' { // param
// 对于 a:xxx 这样 要提取 n.path = a path = :xxx
if i > 0 {
// Insert prefix before the current wildcard
n.path = path[:i]
path = path[i:]
}
n.wildChild = true
//初始化 子节点 param类型 是通配符类型
child := &node{
nType: param,
path: wildcard,
}
//添加子节点
n.children = []*node{child}
//当前节点 置为子节点
n = child
//赋值优先级
n.priority++
// If the path doesn't end with the wildcard, then there
// will be another non-wildcard subpath starting with '/'
//当 path = :tool 时 那么 通配符 和 :tool 事相时的 那么就结束了
//如果不相等说明 后面 还没结束 :tool/12323
if len(wildcard) < len(path) {
//path去除掉 已经添加的部分
path = path[len(wildcard):]
//下面是新建一个节点再循环 上面的步骤
child := &node{
priority: 1,
}
n.children = []*node{child}
n = child
continue
}
// Otherwise we're done. Insert the handle in the new leaf
n.handle = handle
return
} else { // catchAll
// path 以 *sadsad/123结尾 表示还没结束 *只能出现在结尾 asd/123* asd/12*3 这样
if i+len(wildcard) != len(path) {
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
}
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
}
// Currently fixed width 1 for '/'
i--
if path[i] != '/' {
panic("no / before catch-all in path '" + fullPath + "'")
}
n.path = path[:i]
//匹配路由
// First node: catchAll node with empty path
child := &node{
wildChild: true,
nType: catchAll,
}
n.children = []*node{child}
n.indices = string('/')
n = child
n.priority++
//存放变量
// Second node: node holding the variable
child = &node{
path: path[i:],
nType: catchAll,
handle: handle,
priority: 1,
}
n.children = []*node{child}
return
}
}
// If no wildcard was found, simply insert the path and handle
n.path = path
n.handle = handle
}