手把手和你一起实现一个Web框架实战——EzWeb框架(三)[Go语言笔记]Go项目实战

手把手和你一起实现一个Web框架实战——EzWeb框架(三)[Go语言笔记]Go项目实战

代码仓库:
github
gitee
中文注释,非常详尽,可以配合食用

本篇代码,请选择demo3

这一篇文章我们进行动态路由解析功能的设计,

如xxx/:id/xxx,xxx/xxx/*mrxuexi.md

实现这处理这两类模式的简单小功能,实现起来不简单,原有的map[path]HandlerFunc数据结构只能存储静态路由与方法对应,而无法处理动态路由,我们使用一种树结构来进行路由表的存储。

手把手和你一起实现一个Web框架实战——EzWeb框架(三)[Go语言笔记]Go项目实战

一、设计这个数据结构

1、节点结构体设计

type node struct { path string /* 需要匹配的整体路由 */ part string /* 路由中的一部分,例如 :lang */ children []*node /* 存储子节点们 */ isBlurry bool /* 如果模糊匹配则为true */ }

2、一个传入part后,通过遍历该节点的全部子节点们,找到拥有相同part的子节点的方法(返回首个)

func (n *node) matchChild(part string) *node { //遍历子节点们,对比子节点的part和part是否相同,是或者遍历到的子节点支持模糊匹配则返回该子节点 for _, child := range n.children { if child.part == part || child.isBlurry { return child } } return nil }

3、一个返回匹配的子节点们的方法(返回全部,包括动态路由的存储的部分)

func (n *node) matchChildren(part string) []*node { nodes := make([]*node, 0) //遍历选择满足条件的子节点,加入到nodes中,然后返回 for _, child := range n.children { if child.part == part || child.isBlurry { nodes = append(nodes, child) } } return nodes }

4、构造路由表的插入方法,parts[]存储的是根据路由path分解出来的part们,我们拿到part则取检索子节点是否存在这个part,不存在则新建一个子节点,不停的在这个树上深入,直到遍历完我们的全部part,然后递归返回。

//插入方法,用一个递归实现,找匹配的路径直到找不到匹配当前part的节点,新建 func (n *node) insert(path string, parts []string, height int) { //如果遍历到底部了,则将我们的path存入节点,开始返回。递归的归来条件。 if len(parts) == height{ n.path = path return } //获取这一节的part,并进行搜索 part := parts[height] child := n.matchChild(part) //若没有搜索到匹配的子节点,则根据目前的part构造一个子节点 if child == nil { child = &node{ part: part, isBlurry: part[0] == ':' || part[0] == '*', } n.children = append(n.children, child) } child.insert(path, parts, height+1) }

5、我们带着part们一个个在存储路由表的树中查找,我们拿到某个节点的全部子节点,找到满足part相同或者isBlurry:true的节点。通过递归再往深处挖,挖下去直到发现某一级节点的子节点们,没有对应匹配的part,又返回来,再去上一层的子节点看,这就是一个深度优先遍历的情况。

//搜索方法 func (n *node) search(parts []string, height int) *node { //如果节点到头,或者存在*前缀的节点,开始返回 if len(parts) == height || strings.HasPrefix(n.part,"*") { //如果此时遍历到的n没有存储对应的path,说明未到目标最底层,则返回空 if n.path == "" { return nil } return n } //搜索找到满足part的子节点们放入children part := parts[height] children := n.matchChildren(part) //接着遍历子节点们,递归调用获得下一级的子节点们,要走到头的同时,找到了对应的节点,才返回最终我们找到的result //这里为什么要遍历子节点们进行深入搜索,因为它还存在满足isBlurry:true的节点,我们也需要在其中深入搜索。 for _, child := range children { result := child.search(parts, height+1) if result != nil { //返回满足要求的节点 return result } } return nil } 二、更新路由表的存储结构和处理方法

1、其中roots中的第一层是roots[method]*node

type router struct { //用于存储相关方法 handlers map[string]HandlerFunc //用于存储每种请求方式的树的根节点 roots map[string]*node }

2、设计一个parsePath方法,对外部传入的路由根据"http://www.likecs.com/"进行分割,存入parts

// parsePath 用于处理传入的url,先将其分开存储到parts中,当然出现*前缀的部分就可以结束 func parsePath(path string) []string { vs := strings.Split(path, "http://www.likecs.com/") parts := make([]string, 0) for _, v := range vs { if v != "" { parts = append(parts, v) if v[0] == '*' { break } } } return parts }

3、router 中 addRoute 方法,在 handlers map[string]HandlerFunc 中存入路由对应处理方法,进行路由注册。存入形式为例如:{ "GET-/index" : 定义的处理方法 }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwgjxj.html