七天实现web框架--动态路由
2022-04-14 / 6 min read
之前看学长他们用过这个动态路由,但是以前也没有太注意,最近在学习框架的建立。第一次让我感受到了算法在写项目时候的应用,之前都觉得算法这个东西,就是刷题用的,在实战的时候很少用。今天学习这一块稍微有一点吃力,但是还是看懂了,很nice。
什么是动态路由呢
动态路由在开发的时候,就是说不是像原来写项目的时候,这个固定路由绑定固定的函数,而是变化的路由进行固定函数的绑定,这样就可以实现很多的功能。
动态路由的实例:/api/v1/:name
这里我们要知道的是,上面这个路由可以匹配很多的路由比如:
/api/v1/lzz /api/v1/ceshi ....等
这里第三个分隔开的字段,就是对name的赋值。这就是动态路由
实现动态路由需要准备什么
首先我们要用到的就是前缀树,这里我们看一下怎么实现前缀树。我们还是说明一下,比如说,在我们路由中我们可以通过/来分割节点,然后后面通过这些节点进行寻找正确的路由。、
上面的这个图就是展示了前缀树。然后下面我们就来实现一下这个前缀树。
首先定义前缀树的节点:
type node struct {
pattern string //待匹配的路由
part string //路由中的一部分
children []*node //子节点
isWild bool //这里如果是*或者:开头的就是true
}
然后我们建立一个注册函数,一个查找函数
func (n *node) insert(pattern string, parts []string, height int) {
//查看是不是最后一个,是的话就返回
if len(parts) == height {
n.pattern = pattern
return
}
//取出路径中的一部分
part := parts[height]
//查看现在的树中是不是有这个节点,没有的话就进行创建
child := n.matchChild(part)
//下面是创建的流程。也就是创建
if child == nil {
//如果是动态的就会设置为true
child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'}
n.children = append(n.children, child)
}
child.insert(pattern, parts, height+1)
}
查找函数:
func (n *node) search(parts []string, height int) *node {
if len(parts) == height || strings.HasPrefix(n.part, "*") {
if n.pattern == "" {
return nil
}
return n
}
part := parts[height]
children := n.matchChildren(part)
for _, child := range children {
result := child.search(parts, height+1)
if result != nil {
return result
}
}
return nil
}
上面两个函数中有两个关键的函数,就是对路由的匹配,这两个函数我还是看了很久才理解,也是在学习的时候出现的问题。
//创建节点插入的时候的判断函数
func (n *node) matchChild(part string) *node {
for _, child := range n.children {
if child.part == part || child.isWild {
return child
}
}
return nil
}
//查找路径
func (n *node) matchChildren(part string) []*node {
nodes := make([]*node, 0)
for _, child := range n.children {
if child.part == part || child.isWild {
nodes = append(nodes, child)
}
}
return nodes
}
两个函数的原理都是差不多的,传入需要判断的路径一部分,然后在节点里面去寻找是否有这个节点,然后找到了就返回,没有找到就返回nil,我认为的这两个函数,第一个就是判断有没有这个节点,如果没有就返回,然后建立新的节点进行存储,如果找到了就不做操作。
第二个函数,就是对没有部分的判断,然后进行一个添加。
前缀树完整的代码:
package gee
import (
"fmt"
"strings"
)
type node struct {
pattern string
part string
children []*node
isWild bool
}
func (n *node) String() string {
return fmt.Sprintf("node{pattern=%s, part=%s, isWild=%t}", n.pattern, n.part, n.isWild)
}
func (n *node) insert(pattern string, parts []string, height int) {
//查看是不是最后一个,是的话就返回
if len(parts) == height {
n.pattern = pattern
return
}
//取出路径中的一部分
part := parts[height]
//查看现在的树中是不是有这个节点,没有的话就进行创建
child := n.matchChild(part)
//下面是创建的流程。也就是创建
if child == nil {
//如果是动态的就会设置为true
child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'}
n.children = append(n.children, child)
}
child.insert(pattern, parts, height+1)
}
//路由的查找
func (n *node) search(parts []string, height int) *node {
if len(parts) == height || strings.HasPrefix(n.part, "*") {
if n.pattern == "" {
return nil
}
return n
}
part := parts[height]
children := n.matchChildren(part)
for _, child := range children {
result := child.search(parts, height+1)
if result != nil {
return result
}
}
return nil
}
func (n *node) travel(list *([]*node)) {
if n.pattern != "" {
*list = append(*list, n)
}
for _, child := range n.children {
child.travel(list)
}
}
//创建节点插入的时候的判断函数
func (n *node) matchChild(part string) *node {
for _, child := range n.children {
if child.part == part || child.isWild {
return child
}
}
return nil
}
//查找路径
func (n *node) matchChildren(part string) []*node {
nodes := make([]*node, 0)
for _, child := range n.children {
if child.part == part || child.isWild {
nodes = append(nodes, child)
}
}
return nodes
}