GoWeb之路由树的基本实现(二)

在上一节内容中,我们讨论了如何定义一个简单的WebServer,我们参考了Gin框架,来搭建了属于我们的Server。

可以看到Gin中的 Engine 的关键在于RouterGroup,这节内容,我们来学习一下,如何完善我们的路由树。

一、路由树内容

路由树能做什么,这就不得不再次去参考Gin了,现在 好用的Web框架层出不穷,我们学习这些的主要目的就是为了更加深入的理解,方便后续的应用。

可以看到,在RouterGroup 里面也组合了 engine,还有一个 Handlers 我们可以猜测,这个大概率是用来存放我们的路由代码,一个 path 作为路径

底下会发现,RouterGroup 是实现了 IRouter 的接口,所以,我们跳去 IRoute 看看这个接口主要做了什么

可以看到,IRoutes 就是包装了 我们要向http发送请求的构造的路由树,这么看来,Gin 的路由树 应该是 每一个method,也就是一个请求方法作为一棵树,然后路径向下延生。

二、实现简单路由树

路由树的实现,不可避免就要考虑到一个问题,那就是匹配问题,例如用户输入: " / user / a ",我们需要准确的识别出,根节点为 "/",子节点为 user,子节点的子节点为 a。

如此这般一层一层,像树一样延生下去

所以,首先我们需要,在 自己定义的 router 下 定义一颗树,树的组成这里我们选用map,因为Gin里面是一个http请求方法,一颗树,而http请求方法是 string 类型,因此,我们还得定义一个专门用于管理节点的结构体

话不多说,看看代码

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

	handler HandleFunc
}

func newRouter() *router {
	return &router{
		trees: map[string]*node{},
	}
}

func (r *node) childOrCreate(seg string) *node {
	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) 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
}

看得出来是比较简单的,关键在于,我们切割了 / 后,用循环(也可以考虑递归)将子节点的内容不断创建出来。

其次就是一些校验,防止用户输入错误的路径导致崩溃等等

最后,这只是最基础的静态匹配,后续还有更多匹配方法更新,不断完善这个路由树。

关于校验部分,是否要做的详尽一切,关键就在于 权衡 ,你是否愿意花费大量成本去进行测试,编写和思考用户的行为,这里我就简单粗暴的校验一些。

欢迎关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值