一、通配符匹配
书接上回,我们已经成功实现了如何进行静态匹配,主要就是不断循环直到我们的子节点为nil,就停止。
那么有了之前的基础,我们来学习一下如何实现通配符匹配。
所谓通配符匹配就是 当我注册 /user/* 的时候,如果我们网页输入 /user/adc 就会帮我们命中通配符匹配。所以,我们的路由首先会优先进行静态匹配,然后匹配不到了,再进行通配符匹配,这就要求我们需要在 route 里面多添加一个用于通配符匹配的字段。
二、代码实现
话不多说直接上代码,该代码是在上一期静态匹配的基础上进行修改,可以说会了静态匹配,通配符也是手到擒来,如若不熟练,可以去上一期学习
package serv
import (
"fmt"
"github.com/gin-gonic/gin"
"strings"
)
type router struct {
trees map[string]*node
}
type node struct {
path string
children map[string]*node
// 带通配符匹配的
startChild *node
handler HandleFunc
}
func newRouter() *router {
return &router{
trees: map[string]*node{},
}
}
func (r *router) addRoute(method string, path string, handler HandleFunc) {
if path == "" {
panic("路径不能为空")
}
gin.Default()
if path[0] != '/' {
panic("路径只能以 / 开头")
}
if path != "/" && path[len(path)-1] == '/' {
panic("路径不能以 / 结尾")
}
root, ok := r.trees[method]
if !ok {
// 不ok说明没有根节点,所以我们新建一个
root = &node{
path: "/",
}
r.trees[method] = root
}
if path == "/" {
// 根节点重复注册
if root.handler != nil {
panic(fmt.Sprintf("路径 [%s] 重复注册", path))
}
root.handler = handler
return
}
// 因为我们的path是 “/xxx/xxx”,这样第一个/会切割出一个空字符串
// 所以我们需要刨除第一个 /
path = path[1:]
segs := strings.Split(path, "/")
for _, seg := range segs {
// 防止中间出现多个 /
if seg == "" {
panic("路径不能出现连续多个 / ")
}
child := root.childOrCreate(seg)
root = child
}
// 普通路径重复注册,主要判断依据就是,这个路径下是否依旧存在handlerFunc了
if root.handler != nil {
panic(fmt.Sprintf("路径 [%s] 重复注册", path))
}
root.handler = handler
}
func (r *node) childOrCreate(seg string) *node {
if seg == "*" {
r.startChild = &node{
path: seg,
}
return r.startChild
}
if r.children == nil {
// 如果children为nil的话,下面的逻辑将会崩溃,所以需要建立一个新的node
r.children = map[string]*node{}
}
res, ok := r.children[seg]
if !ok {
res = &node{
path: seg,
}
r.children[seg] = res
}
return res
}
func (r *router) findRoute(method string, path string) (*node, bool) {
// 查找是否有该请求方法,有的话就拿到整条链路
root, ok := r.trees[method]
if !ok {
return nil, false
}
if path == "/" {
return root, true
}
// 前面要求 path必须是 /xxx/xxx
// 把前置和后置的斜杠都去掉
path = strings.Trim(path, "/")
segs := strings.Split(path, "/")
for _, seg := range segs {
// 查找的过程
c, found := root.childOf(seg)
if !found {
return nil, false
}
// 不断深入查找
root = c
}
return root, true
}
func (n *node) childOf(path string) (*node, bool) {
if n.children == nil {
return n.startChild, n.startChild != nil
}
child, ok := n.children[path]
if !ok {
return n.startChild, n.startChild != nil
}
return child, ok
}
主要的两个关键点在于:
- addRoute的时候,在 childOfCreate 中追加一个对于通配符匹配的校验
- findRoute的时候,在 childOf 中也同样追加一个,因为我们是如果子节点的children 不存在,则有可能是通配符的 startChild