前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零开始写一个web服务到底有多难?(二)

从零开始写一个web服务到底有多难?(二)

原创
作者头像
4cos90
发布2023-12-24 02:47:41
1170
发布2023-12-24 02:47:41
举报

路由树

上一章最后我们写到了处理restful风格的api。但是实现的太简陋了。自然我们需要一个路由树来处理请求。

那么我们从最简单的情况开始。

最简单的路由树

1.不考虑路径参数问题。

2.不考虑路由冲突。

3.不考虑性能问题。

那么我们自然就可以得到树的结构定义,Tree记录根节点,path表示节点本身的路径,children表示子节点的路径,handler表示该路由下有没有注册的handlerFunc,如果有则执行,如果没有则执行父节点的。

考虑到我们注册的函数定义func(ctx *Context)声明的地方比较多,这边也可以给个类型handlerFunc,方便理解。

代码语言:go
复制
type handlerFunc func(ctx *Context)

type Tree struct {
	root *node
}

type node struct {
	path     string
	children []*node
	handler  handlerFunc
	method   string
}

新增路由

那么当我们声明一个新的路由时,我们的新增逻辑是这样的。

新增一条 temp/user/hello。

将temp作为根节点出发,查找user。

如果children里有user则将user作为当前节点。

如果没有则创建一个新节点user,加入children。并将新节点作为当前节点。

由于不考虑路由冲突,在hello节点直接将handlerFunc赋值给handler即可。

代码语言:go
复制
func (t *Tree) Route(method string, pattern string, hundler handlerFunc) {
	pattern = strings.Trim(pattern, "/")
	paths := strings.Split(pattern, "/")

	cur := t.root

	matchChild, ok := cur.findMatchChild(paths)
	if ok {
		matchChild.handler = hundler
		matchChild.method = method
	}
}

func (r *node) findMatchChild(paths []string) (*node, bool) {
	if len(paths) == 0 {
		return r, true
	}

	for _, childNode := range r.children {
		if childNode.path == paths[0] {
			return childNode.findMatchChild(paths[1:])
		}
	}

	newChildNode := r.createNode(paths[0])
	r.children = append(r.children, newChildNode)
	return newChildNode.findMatchChild(paths[1:])
}

func (r *node) createNode(path string) *node {
	return &node{
		path:     path,
		children: make([]*node, 0, 2),
	}
}

支持*匹配路由

刚才我们的路由只能所有的路径都完全匹配。我们可以尝试扩展一下,例如注册temp/user/*,那么temp/user/other1,temp/user/other2都能匹配上。我们一般会用 /*来匹配所有没有注册的路由,并在handlerfunc里返回404。

匹配路由和新增路由其实方法是一样的。直接复用即可。

代码语言:go
复制
func (t *Tree) FindRoute(method string, pattern string) (*node, bool) {
	pattern = strings.Trim(pattern, "/")
	paths := strings.Split(pattern, "/")
	cur := t.root

	return cur.findMatchChild(paths)
}

那么我们要修改的,其实只有这个地方。

代码语言:go
复制
if childNode.path == paths[0] {
	return childNode.findMatchChild(paths[1:])
}

修改一下

代码语言:go
复制
if childNode.path == paths[0] || paths[0] == "*" {
	return childNode.findMatchChild(paths[1:])
}

但是有这么简单吗?有两个问题:

1.比如我们注册了两个路由,temp/user/hello,temp/user/*,当我们访问temp/user/hello时,两个路由都能匹配上。

这么写会按照注册路由的顺序返回。但是我们希望的肯定是优先匹配具体的路由,都匹配不上时再匹配通配符。即优先挑选匹配path最多的路由。

2.比如我注册了 temp/* ,temp/*/hello两个路由,这时候我请求了 temp/user/greet,我应该匹配上 temp/*吗?

我认为我们不能允许注册第二种路由,因为如果要支持第二种路由,那么我们在匹配路由失败时,又要回溯路由树,看看有没有其他的通配符可以重新匹配。

那么我们重新新增两条规则。

1.校验*,如果*存在,必须在路由的最后一个。即我们只接受/*结尾。

2.优先匹配具体的路由,都匹配不上再匹配通配符。

那么我们创建路由和匹配路由时的方法也有了些许不同。

代码语言:go
复制
func (t *Tree) Route(method string, pattern string, hundler handlerFunc) {
	pattern = strings.Trim(pattern, "/")

	//校验通配符的使用是否合法,非法时直接返回。
	if strings.Contains(pattern, "*") && !strings.HasSuffix(pattern, "/*") {
		return
	}

	paths := strings.Split(pattern, "/")
	cur := t.root

	matchChild, ok := cur.findMatchChild(paths)
	if ok {
		matchChild.handler = hundler
		matchChild.method = method
	}
}
代码语言:go
复制
func (t *Tree) FindRoute(method string, pattern string) (*node, bool) {
	pattern = strings.Trim(pattern, "/")
	paths := strings.Split(pattern, "/")
	cur := t.root

	return cur.matchChild(paths)
}
代码语言:go
复制
func (r *node) matchChild(paths []string) (*node, bool) {
	if len(paths) == 0 {
		return r, true
	}

	var wildCardNode *node

	//有匹配时优先返回能匹配上的,如果都匹配不上再看是否有通配符的。
	for _, childNode := range r.children {
		if childNode.path == paths[0] && childNode.path != "*" {
			return childNode.matchChild(paths[1:])
		}

		if childNode.path == "*" {
			wildCardNode = childNode
		}
	}

	return wildCardNode, wildCardNode != nil
}

还有一个问题

temp/* 能否匹配 temp ?

这是一个典型的设计问题,因为无论是否匹配,似乎都可以说出合理的理由。

这里我们的实现没有支持,仅仅是因为我认为用户注册temp/*是期望temp之后有存在子路由的。如果要处理temp这个路由本身,那么用户可以直接注册一个temp的路由。

题外

很多时候写代码,难题并不在于代码本身,而在于设计上的取舍。而这种取舍很多时候又仅代表作者本人的偏好。当我们有时候遇到一些写法不被支持的时候,不妨思考一下这样写真的是不好的实践,还是仅仅是作者本身的偏好。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 路由树
  • 最简单的路由树
  • 新增路由
  • 支持*匹配路由
  • 还有一个问题
  • 题外
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档