您现在的位置是:首页 >技术杂谈 >GoWeb之静态路由查找(三)网站首页技术杂谈

GoWeb之静态路由查找(三)

思远久安 2026-03-22 12:01:04
简介GoWeb之静态路由查找(三)

书接上回,我们成功实现了一个简单的Server,将里面的路由树成功定义出来,并且实现了注册功能,那么今天,我们就继续来学习,如何进行静态路由查找。

一、路由查找

路由查找是当我们注册了路由之后,如果我们要执行对应的 handleFunc 就需要进入到叶子节点中,找到对应的方法并执行。

所以,我们一般都是确保深度优先,一直向下搜索

1. 代码实现

在上节内容中,我们定义了 addRoute 方法,这次我们要定义 FindRoute 进行查找

话不多说,代码如下

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 nil, false
	}
	child, ok := n.children[path]
	return child, ok
}

需要注意的是:

  • 根节点因为我们有切割和替换功能,所以需要单独进行处理
  • 前面 add 的时候就已经进行过校验,所以查找的时候,不需要进行多余的校验,默认都是合法的

2. 测试用例

这里采用单元测试的方式,不想看可以直接无视

func (n *node) equal(y *node) (string, bool) {
	if n.path != y.path {
		return fmt.Sprintf("节点路径不匹配"), false
	}

	if len(n.children) != len(y.children) {
		return fmt.Sprintf("子节点数量不匹配"), false
	}

	nHandler := reflect.ValueOf(n.handler)
	yHandler := reflect.ValueOf(y.handler)
	if nHandler != yHandler {
		return fmt.Sprintf("handlerFunc 不匹配"), false
	}

	for k, c := range n.children {
		dst, ok := y.children[k]
		if !ok {
			return fmt.Sprintf("子节点 %s 不存在", k), false
		}
		msg, ok := c.equal(dst)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func TestRouter_FindRoute(t *testing.T) {
	testRoutes := []struct {
		method string
		path   string
	}{
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
	}

	r := newRouter()
	var mockHandler HandleFunc = func(ctx Context) {}
	for _, route := range testRoutes {
		r.addRoute(route.method, route.path, mockHandler)
	}

	testCases := []struct {
		name   string
		method string
		path   string

		// 预期有没有找到
		wantFound bool
		// 预期返回的node
		wantNode *node
	}{
		{
			// 完全命中
			// 我们这个查找只要叶子节点,也就是最末端的
			name:      "user home",
			method:    http.MethodGet,
			path:      "/user/home",
			wantFound: true,
			wantNode: &node{
				path:    "home",
				handler: mockHandler,
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			n, found := r.FindRoute(tc.method, tc.path)
			assert.Equal(t, tc.wantFound, found)
			if !found {
				return
			}
			// node 里面有方法,所以不能直接用 assert.Equal 来比较
			equal, ok := n.equal(tc.wantNode)
			assert.True(t, ok, equal)
		})
	}
}

要自己定义一个 关于 node 的equal 函数是因为,在 node  里面包含 handlefunc,函数无法进行比较,只能用反射进行对比,所以我们自己定义一个 equal 函数,而不是使用assert

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。