web框架-前缀路由树
2023-03-01
技术
极客兔兔的web框架,该框架是参考gin的造轮子项目。前缀路由树这篇注释太少,不易于理解,个人写了写自己的理解。
树结构
// 前缀路由树-树节点
type node struct {
pattern string // 待匹配路由 /p/:lang
part string // 路由的一部分 :lang,或者叫该节点存储的子路由好一点,在路由中是以 / 字符分隔的最小单位
children []*node // 子节点
isWild bool //是否精确匹配,part 含有 : 或 * 时为true,否则为false
}
注册路由
匹配子节点的方法,可以先看下面的insert方法再倒回来看
// 匹配成功的节点,用于插入;插入时只会有一个节点的part匹配
func (n *node) matchChild(part string) *node {
// 遍历节点的子节点,子节点的part与要匹配的part相同
for _, child := range n.children {
if child.part == part || child.isWild {
//返回匹配的节点
return child
}
}
return nil
}
插入节点的方法
// 注册路由规则
// 对pattern解析,以 / 为分隔符分解为 parts 数组,递归插入路由树,height为插入树的高度
// 例: pattern - "/p/:lang/doc"
// parts - []string{“p”,“lang”,“doc”}
func (n *node) insert(pattern string, parts []string, height int) {
// n节点所在树的高度与分解的part数是否相等,即判断 part 是否全部插入完毕
if len(parts) == height {
// 只有最后一个节点才会设置pattern,用于查询时匹配,这之前的节点该pattern字段均为空
// 确保只有相同层级的相同part才能匹配
n.pattern = pattern
return
}
part := parts[height]
// 在子节点中查找匹配 part 的第一个节点,然后判断是否存在匹配 part的节点
// 存在则将其作为子节点,继续下一步
child := n.matchChild(part)
if child == nil {
// 不存在,则构建新节点,将part写入,根据该part第一个字符是否有:和*,设置isWild
child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'}
// 将该节点作为n节点的子节点插入树中
n.children = append(n.children, child)
}
// 继续查找child的子节点,插入下一个part,直到把parts数组的part全部插入为止
child.insert(pattern, parts, height+1)
}
查询路由
同样,你可以先看下面的search方法再倒回来看这个
// 所有匹配成功的节点,用于查找;与插入时匹配不同的是,有可能存在多个part能匹配的节点,例如动态路由 /:xxx
func (n *node) matchChildren(part string) []*node {
nodes := make([]*node, 0)
// 遍历节点的子节点,子节点的part与要匹配的part相同
for _, child := range n.children {
if child.part == part || child.isWild {
nodes = append(nodes, child)
}
}
// 返回所有匹配的节点
return nodes
}
search方法
// 查询匹配路由
func (n *node) search(parts []string,height int)*node{
// n节点所在树的高度与分解的part数相等,或n节点的part字段以字符 * 开头,则匹配完毕
if len(parts)==height||strings.HasPrefix(n.part,"*"){
// 注册路由插入该节点的最大高度并不是该层级,pattern字段为空,也无法匹配(详见insert方法的第一个if语句)
if n.pattern==""{
return nil
}
// 匹配到了,返回该节点
return n
}
part:=parts[height]
// 在n节点的子节点中查找所有与part匹配的节点
chirldren:=n.matchChildren(part)
for _,child:=range chirldren{
// 继续递归查询子节点,匹配下一个part,直到parts数组的part完全匹配为止
result:=child.search(parts,height+1)
if result!=nil{
return result
}
}
return nil
}