# 4.02 课后复习-Route

本节课工程结构:

```
(base) yanglei@yuanhong v4-rc % tree ./
./
├── context.go
├── handle_func.go
├── server.go
├── server_interface.go
└── server_test.go

0 directories, 5 files
```

## PART1. 定义路由森林与节点

### 1.1 定义节点

新建文件`node.go`:

```go
package v4_rc

// node 路由树中的节点
type node struct {
	// pattern 路由路径
	pattern  string
	// children 子节点 key为子节点的路由路径 value为路径对应子节点
	children map[string]*node
	// HandleFunc 路由对应的处理函数
	HandleFunc
}
```

### 1.2 定义路由森林

新建文件`router.go`:

```go
package v4_rc

// router 路由森林
type router struct {
	// trees 路由森林 key为HTTP动词 value为HTTP对应路由树的根节点
	trees map[string]*node
}
```

### 1.3 定义添加路由的操作

`router.go`:

```go
package v4_rc

// router 路由森林
type router struct {
	// trees 路由森林 key为HTTP动词 value为HTTP对应路由树的根节点
	trees map[string]*node
}

// addRoute 添加路由
func (r *router) addRoute(method string, pattern string, handle HandleFunc) {
	panic("implement me")
}
```

## PART2. 组合路由森林与Server

`server_interface.go`:

```go
package v4_rc

import "net/http"

// ServerInterface 服务器实体接口
// 用于定义服务器实体的行为
type ServerInterface interface {
	// Handler 组合http.Handler接口
	http.Handler
	// Start 启动服务器
	Start(addr string) error
}
```

`server.go`:

```go
package v4_rc

import (
	"net"
	"net/http"
)

type Server struct {
	*router
}

// ServeHTTP 是http.Handler接口的方法 此处必须先写个实现 不然Server不是http.Handler接口的实现
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	s.serve(ctx)

	panic("implement me")
}

// serve 查找路由树并执行匹配到的节点所对应的处理函数
func (s *Server) serve(ctx *Context) {
	panic("implement me")
}

// Start 启动服务器
func (s *Server) Start(addr string) error {
	listener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	return http.Serve(listener, s)
}

// GET 注册GET路由
func (s *Server) GET(path string, handler HandleFunc) {
	s.addRoute(http.MethodGet, path, handler)
}

// POST 注册POST路由
func (s *Server) POST(path string, handler HandleFunc) {
	s.addRoute(http.MethodPost, path, handler)
}
```

## PART3. 静态路由注册

### 3.1 编写测试方法

#### 3.1.1 构造路由树

`router_test.go`:

```go
package v4_rc

import (
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}
}
```

#### 3.1.2 断言路由森林

* 若2个路由森林中的路由树数量不同,则不相等
* 如果目标router中没有对应HTTP动词的路由树(例:目标路由森林中有GET/POST/PUT这3棵路由树,而期望路由森林中有GET/POST/DELETE这3棵路由树),则不相等

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg := fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// TODO: implement me
	return "", false
}
```

#### 3.1.3 断言路由树

* 若2个节点中有一个为nil,则不相等
* 若2个节点的path不同,则不相等
* 若2个节点的子节点数量不同,则不相等
* 若2个节点的HandleFunc类型不同,则不相等
* 比对2个节点的子节点映射:
  * 若源节点的子节点映射中,存在目标节点中没有的子节点,则不相同
    * 注:因为上边已经比对过2个节点的子节点数量了,所以只要能通过这个步骤的比对,那么2个节点的子节点必然是一一对应的
  * 两个path相同的节点递归比对

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg := fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点的处理函数为: %v, 目标节点的处理函数为: %v", wantHandler, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

### 3.2 添加测试用例

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg := fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点的处理函数为: %v, 目标节点的处理函数为: %v", wantHandler, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

### 3.3 实现addRoute方法

#### 3.3.1 根据method查找路由树,不存在则创建

`router.go`:

```go
package v4_rc

import (
	"strings"
)

// router 路由森林
type router struct {
	// trees 路由森林 key为HTTP动词 value为HTTP对应路由树的根节点
	trees map[string]*node
}

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

// addRoute 添加路由
func (r *router) addRoute(method string, path string, handle HandleFunc) {
	// step1. 查找路由树,不存在则创建
	root, exist := r.trees[method]
	if !exist {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}
}
```

#### 3.3.2 对根节点做特殊处理

此处要特殊处理根节点的原因在于:后续按`/`分割path后,根节点就直接被分割没了(变成"")了.所以要特殊处理根节点

`router.go`:

```go
package v4_rc

import (
	"strings"
)

// router 路由森林
type router struct {
	// trees 路由森林 key为HTTP动词 value为HTTP对应路由树的根节点
	trees map[string]*node
}

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

// addRoute 添加路由
func (r *router) addRoute(method string, path string, handle HandleFunc) {
	// step1. 查找路由树,不存在则创建
	root, exist := r.trees[method]
	if !exist {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}

	// step2. 在根节点上查找子节点 不存在则创建
	// step2.1 由于按/切割后 第一个元素为"" 也就是说如果传入的path为"/" 需要特殊处理
	if path == "/" {
		root.HandleFunc = handle
		return
	}
}
```

#### 3.3.3 从根节点开始逐层查找目标节点,找到目标节点后添加HandleFunc

`node.go`:

```go
package v4_rc

// node 路由树中的节点
type node struct {
	// path 路由路径
	path string
	// children 子节点 key为子节点的路由路径 value为路径对应子节点
	children map[string]*node
	// HandleFunc 路由对应的处理函数
	HandleFunc
}

// findOrCreate 本方法用于根据给定的path值 在当前节点的子节点中查找path为给定path值的节点
// 找到则返回 未找到则创建
func (n *node) findOrCreate(segment string) *node {
	if n.children == nil {
		n.children = make(map[string]*node)
	}

	target, exist := n.children[segment]
	if !exist {
		// 当前节点的子节点映射中不存在目标子节点 则创建目标子节点 将子节点加入当前节点的子节点映射后返回
		target = &node{
			path: segment,
		}
		n.children[segment] = target
		return target
	}

	// 当前节点的子节点映射中存在目标子节点 则直接返回
	return target
}
```

`router.go`:

```go
package v4_rc

import (
	"strings"
)

// router 路由森林
type router struct {
	// trees 路由森林 key为HTTP动词 value为HTTP对应路由树的根节点
	trees map[string]*node
}

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

// addRoute 添加路由
func (r *router) addRoute(method string, path string, handle HandleFunc) {
	// step1. 查找路由树,不存在则创建
	root, exist := r.trees[method]
	if !exist {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}

	// step2. 在根节点上查找子节点 不存在则创建
	// step2.1 由于按/切割后 第一个元素为"" 也就是说如果传入的path为"/" 需要特殊处理
	if path == "/" {
		root.HandleFunc = handle
		return
	}

	// step2.2 从根节点开始 逐层查找
	target := root
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	for _, pathSegment := range pathSegments {
		// 在当前节点上查找子节点
		child := target.findOrCreate(pathSegment)
		target = child
	}

	// 为目标节点创建HandleFunc
	target.HandleFunc = handle
}
```

此时运行测试用例即可顺利通过测试

### 3.4 测试用例

#### 3.4.1 对根节点的测试用例

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
		{
			method: http.MethodGet,
			path:   "/",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandleFunc,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg := fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

#### 3.4.2 前导/user节点的测试用例

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/",
		},
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: mockHandleFunc,
					},
				},
				HandleFunc: mockHandleFunc,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg = fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

#### 3.4.3 /order/detail节点的测试用例

为测试路由树中间有不存在的节点时,是否符合预期

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/",
		},
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: mockHandleFunc,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:       "detail",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandleFunc,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg = fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

#### 3.4.4 其他HTTP动词的测试用例

**a. /login**

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/",
		},
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
		{
			method: http.MethodPost,
			path:   "/login",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: mockHandleFunc,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:       "detail",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandleFunc,
			},
			http.MethodPost: &node{
				path: "/",
				children: map[string]*node{
					"login": &node{
						path:       "login",
						children:   nil,
						HandleFunc: mockHandleFunc,
					},
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg = fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

**b. /order/create**

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/",
		},
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
		{
			method: http.MethodPost,
			path:   "/login",
		},
		{
			method: http.MethodPost,
			path:   "/order/create",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: mockHandleFunc,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:       "detail",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandleFunc,
			},
			http.MethodPost: &node{
				path: "/",
				children: map[string]*node{
					"login": &node{
						path:       "login",
						children:   nil,
						HandleFunc: mockHandleFunc,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"create": &node{
								path:       "create",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg = fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

#### 3.4.5 非法用例

**a. path为空字符串**

`router.go`:

```go
package v4_rc

import (
	"strings"
)

// router 路由森林
type router struct {
	// trees 路由森林 key为HTTP动词 value为HTTP对应路由树的根节点
	trees map[string]*node
}

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

// addRoute 添加路由
func (r *router) addRoute(method string, path string, handle HandleFunc) {
	msg, ok := r.checkPath(path)
	if !ok {
		panic(msg)
	}

	// step1. 查找路由树,不存在则创建
	root, exist := r.trees[method]
	if !exist {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}

	// step2. 在根节点上查找子节点 不存在则创建
	// step2.1 由于按/切割后 第一个元素为"" 也就是说如果传入的path为"/" 需要特殊处理
	if path == "/" {
		root.HandleFunc = handle
		return
	}

	// step2.2 从根节点开始 逐层查找
	target := root
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	for _, pathSegment := range pathSegments {
		// 在当前节点上查找子节点
		child := target.findOrCreate(pathSegment)
		target = child
	}

	// 为目标节点创建HandleFunc
	target.HandleFunc = handle
}

// checkPath 检测路由是否合法
// 此处没有返回error 是因为设计上如果路由不合法 直接panic而非报错
// 所以此方法只返回 表示是否合法的标量以及表示不合法原因的字符串即可
func (r *router) checkPath(path string) (msg string, ok bool) {
	if path == "" {
		return "web: 路由不能为空字符串", false
	}

	return "", true
}
```

`router_test.go`:

```go
package v4_rc

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"reflect"
	"testing"
)

type TestNode struct {
	method string
	path   string
}

func TestRouter_AddRoute(t *testing.T) {
	// step1. 构造路由森林
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/",
		},
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/user/home",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
		{
			method: http.MethodPost,
			path:   "/login",
		},
		{
			method: http.MethodPost,
			path:   "/order/create",
		},
	}
	targetRouter := newRouter()
	mockHandleFunc := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		targetRouter.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path: "user",
						children: map[string]*node{
							"home": &node{
								path:       "home",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: mockHandleFunc,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:       "detail",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandleFunc,
			},
			http.MethodPost: &node{
				path: "/",
				children: map[string]*node{
					"login": &node{
						path:       "login",
						children:   nil,
						HandleFunc: mockHandleFunc,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"create": &node{
								path:       "create",
								children:   nil,
								HandleFunc: mockHandleFunc,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(targetRouter)
	assert.True(t, ok, msg)
}

func (r *router) equal(target *router) (msg string, ok bool) {
	// step1. 比较路由森林中的路由树数量
	wantLen := len(r.trees)
	targetLen := len(target.trees)

	if wantLen != targetLen {
		msg = fmt.Sprintf("路由森林中的路由树数量不等, 期望路由树的数量为: %d, 目标路由树的数量为: %d", wantLen, targetLen)
		return msg, false
	}

	// step2. 比对2个路由森林中的路由树HTTP动词是否相同
	for method, tree := range r.trees {
		dstTree, ok := target.trees[method]
		if !ok {
			msg = fmt.Sprintf("目标路由森林中不存在HTTP动词为: %s 的路由树", method)
			return msg, false
		}

		// step3. 比对2个路由树中的结构是否相同
		msg, ok = tree.equal(dstTree)
		if !ok {
			return msg, false
		}
	}

	return "", true
}

func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step5. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step5.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step5.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}

func TestRouter_Illegal_Path(t *testing.T) {
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	nilPathFunc := func() { r.addRoute(http.MethodGet, "", mockHandle) }
	assert.Panicsf(t, nilPathFunc, "web: 路由不能为空字符串")
}
```

TODO:assert.Panicsf

**b. path不是以`/`开头**

`router.go`:

```go
// checkPath 检测路由是否合法
// 此处没有返回error 是因为设计上如果路由不合法 直接panic而非报错
// 所以此方法只返回 表示是否合法的标量以及表示不合法原因的字符串即可
func (r *router) checkPath(path string) (msg string, ok bool) {
	if path == "" {
		return "web: 路由不能为空字符串", false
	}

	if path[0] != '/' {
		return "web: 路由必须以/开头", false
	}

	return "", true
}
```

`router_test.go`:

```go
func TestRouter_Illegal_Path(t *testing.T) {
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	// 路由为空的测试用例
	nilPathFunc := func() {
		r.addRoute(http.MethodGet, "", mockHandle)
	}
	assert.Panicsf(t, nilPathFunc, "web: 路由不能为空字符串")

	// 路由不是以`/`开头的测试用例
	incorrectFirstCharacter := func() {
		r.addRoute(http.MethodGet, "login", mockHandle)
	}
	assert.Panicsf(t, incorrectFirstCharacter, "web: 路由必须以/开头")
}
```

**c. path以`/`结尾**

`router.go`:

```go
// checkPath 检测路由是否合法
// 此处没有返回error 是因为设计上如果路由不合法 直接panic而非报错
// 所以此方法只返回 表示是否合法的标量以及表示不合法原因的字符串即可
func (r *router) checkPath(path string) (msg string, ok bool) {
	if path == "" {
		return "web: 路由不能为空字符串", false
	}

	if path[0] != '/' {
		return "web: 路由必须以/开头", false
	}

	if path != "/" && path[len(path)-1] == '/' {
		return "web: 路由不能以/结尾", false
	}

	return "", true
}
```

`router_test.go`:

```go
func TestRouter_Illegal_Path(t *testing.T) {
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	// 路由为空的测试用例
	nilPathFunc := func() {
		r.addRoute(http.MethodGet, "", mockHandle)
	}
	assert.Panicsf(t, nilPathFunc, "web: 路由不能为空字符串")

	// 路由不是以`/`开头的测试用例
	incorrectFirstCharacter := func() {
		r.addRoute(http.MethodGet, "login", mockHandle)
	}
	assert.Panicsf(t, incorrectFirstCharacter, "web: 路由必须以/开头")

	// 路由以`/`结尾的测试用例
	incorrectLastCharacter := func() {
		r.addRoute(http.MethodGet, "/login/", mockHandle)
	}
	assert.Panicsf(t, incorrectLastCharacter, "web: 路由不能以/结尾")
}
```

**d. path中包含连续的`/`**

`router.go`:

```go
// checkPath 检测路由是否合法
// 此处没有返回error 是因为设计上如果路由不合法 直接panic而非报错
// 所以此方法只返回 表示是否合法的标量以及表示不合法原因的字符串即可
func (r *router) checkPath(path string) (msg string, ok bool) {
	if path == "" {
		return "web: 路由不能为空字符串", false
	}

	if path[0] != '/' {
		return "web: 路由必须以/开头", false
	}

	if path != "/" {
		if path[len(path)-1] == '/' {
			return "web: 路由不能以/结尾", false
		}

		path = strings.TrimLeft(path, "/")
		pathSegments := strings.Split(path, "/")
		for _, pathSegment := range pathSegments {
			if pathSegment == "" {
				return "web: 路由中不能出现连续的/", false
			}
		}
	}

	return "", true
}
```

`router_test.go`:

```go
func TestRouter_Illegal_Path(t *testing.T) {
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	// 路由为空的测试用例
	nilPathFunc := func() {
		r.addRoute(http.MethodGet, "", mockHandle)
	}
	assert.Panicsf(t, nilPathFunc, "web: 路由不能为空字符串")

	// 路由不是以`/`开头的测试用例
	incorrectFirstCharacter := func() {
		r.addRoute(http.MethodGet, "login", mockHandle)
	}
	assert.Panicsf(t, incorrectFirstCharacter, "web: 路由必须以/开头")

	// 路由以`/`结尾的测试用例
	incorrectLastCharacter := func() {
		r.addRoute(http.MethodGet, "/login/", mockHandle)
	}
	assert.Panicsf(t, incorrectLastCharacter, "web: 路由不能以/结尾")

	// 路由中出现了连续的/
	continuousSeparator := func() {
		r.addRoute(http.MethodGet, "/a//b", mockHandle)
	}
	assert.Panicsf(t, continuousSeparator, "web: 路由不能出现多个连续的/")
}
```

**e. 路由重复注册**

**e1. 根节点路由重复注册**

`router.go`:

```go
// addRoute 添加路由
func (r *router) addRoute(method string, path string, handle HandleFunc) {
	msg, ok := r.checkPath(path)
	if !ok {
		panic(msg)
	}

	// step1. 查找路由树,不存在则创建
	root, exist := r.trees[method]
	if !exist {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}

	// step2. 在根节点上查找子节点 不存在则创建
	// step2.1 由于按/切割后 第一个元素为"" 也就是说如果传入的path为"/" 需要特殊处理
	if path == "/" {
		if root.HandleFunc != nil {
			msg = fmt.Sprintf("web: 路由冲突,重复注册路由 [%s]", path)
			panic(msg)
		}
		root.HandleFunc = handle
		return
	}

	// step2.2 从根节点开始 逐层查找
	target := root
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	for _, pathSegment := range pathSegments {
		// 在当前节点上查找子节点
		child := target.findOrCreate(pathSegment)
		target = child
	}

	// 为目标节点创建HandleFunc
	target.HandleFunc = handle
}
```

`router_test.go`:

```go
func TestRouter_Illegal_Path(t *testing.T) {
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	// 路由为空的测试用例
	nilPathFunc := func() {
		r.addRoute(http.MethodGet, "", mockHandle)
	}
	assert.Panicsf(t, nilPathFunc, "web: 路由不能为空字符串")

	// 路由不是以`/`开头的测试用例
	incorrectFirstCharacter := func() {
		r.addRoute(http.MethodGet, "login", mockHandle)
	}
	assert.Panicsf(t, incorrectFirstCharacter, "web: 路由必须以/开头")

	// 路由以`/`结尾的测试用例
	incorrectLastCharacter := func() {
		r.addRoute(http.MethodGet, "/login/", mockHandle)
	}
	assert.Panicsf(t, incorrectLastCharacter, "web: 路由不能以/结尾")

	// 路由中出现了连续`/`的测试用例
	continuousSeparator := func() {
		r.addRoute(http.MethodGet, "/a//b", mockHandle)
	}
	assert.Panicsf(t, continuousSeparator, "web: 路由不能出现多个连续的/")

	// 路由重复注册的测试用例
	r.addRoute(http.MethodGet, "/", mockHandle)
	repeatRegisterRoute := func() {
		r.addRoute(http.MethodGet, "/", mockHandle)
	}
	assert.Panicsf(t, repeatRegisterRoute, "web: 路由冲突,重复注册路由 [/]")
}
```

**e2. 普通节点路由重复注册**

`router.go`:

```go
// addRoute 添加路由
func (r *router) addRoute(method string, path string, handle HandleFunc) {
	msg, ok := r.checkPath(path)
	if !ok {
		panic(msg)
	}

	// step1. 查找路由树,不存在则创建
	root, exist := r.trees[method]
	if !exist {
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}

	// step2. 在根节点上查找子节点 不存在则创建
	// step2.1 由于按/切割后 第一个元素为"" 也就是说如果传入的path为"/" 需要特殊处理
	if path == "/" {
		if root.HandleFunc != nil {
			msg = fmt.Sprintf("web: 路由冲突,重复注册路由 [%s]", path)
			panic(msg)
		}
		root.HandleFunc = handle
		return
	}

	// step2.2 从根节点开始 逐层查找
	target := root
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	for _, pathSegment := range pathSegments {
		// 在当前节点上查找子节点
		child := target.findOrCreate(pathSegment)
		target = child
	}

	// 为目标节点创建HandleFunc
	if target.HandleFunc != nil {
		msg = fmt.Sprintf("web: 路由冲突,重复注册路由 [%s]", path)
		panic(msg)
	}
	target.HandleFunc = handle
}
```

`router_test.go`:

```go
func TestRouter_Illegal_Path(t *testing.T) {
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	// 路由为空的测试用例
	nilPathFunc := func() {
		r.addRoute(http.MethodGet, "", mockHandle)
	}
	assert.Panicsf(t, nilPathFunc, "web: 路由不能为空字符串")

	// 路由不是以`/`开头的测试用例
	incorrectFirstCharacter := func() {
		r.addRoute(http.MethodGet, "login", mockHandle)
	}
	assert.Panicsf(t, incorrectFirstCharacter, "web: 路由必须以/开头")

	// 路由以`/`结尾的测试用例
	incorrectLastCharacter := func() {
		r.addRoute(http.MethodGet, "/login/", mockHandle)
	}
	assert.Panicsf(t, incorrectLastCharacter, "web: 路由不能以/结尾")

	// 路由中出现了连续`/`的测试用例
	continuousSeparator := func() {
		r.addRoute(http.MethodGet, "/a//b", mockHandle)
	}
	assert.Panicsf(t, continuousSeparator, "web: 路由不能出现多个连续的/")

	// 路由重复注册的测试用例
	// 根节点路由重复注册
	r.addRoute(http.MethodGet, "/", mockHandle)
	repeatRegisterRoute := func() {
		r.addRoute(http.MethodGet, "/", mockHandle)
	}
	assert.Panicsf(t, repeatRegisterRoute, "web: 路由冲突,重复注册路由 [/]")

	// 普通节点路由重复注册
	r.addRoute(http.MethodGet, "/user/login", mockHandle)
	repeatRegisterRoute = func() {
		r.addRoute(http.MethodGet, "/user/login", mockHandle)
	}
	assert.Panicsf(t, repeatRegisterRoute, "web: 路由冲突,重复注册路由 [/user/login]")
}
```

## PART4. 静态路由查找

### 4.1 编写测试函数

`router_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}
}
```

### 4.2 定义测试用例的类型

我们需要这个类型能够告知我们如下信息:

* 在给定HTTP动词和path的前提下,是否找到了节点?
* 在给定HTTP动词和path的前提下,找到的节点和预期的节点是否相同?

```go
type TestCaseNode struct {
	name     string // name 子测试用例的名称
	method   string // method HTTP动词
	path     string // path 路由路径
	isFound  bool   // isFound 是否找到了节点
	wantNode *node  // wantNode 期望的路由节点
}
```

### 4.3 定义测试的过程

* step1. 判断在路由树中是否找到了节点
* step2. 判断找到的节点和预期的节点是否相同

`route_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user",
		},
	}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		// name 子测试用例的名称
		name string
		// method HTTP动词
		method string
		// path 路由路径
		path string
		// isFound 是否找到了节点
		isFound bool
		// wantNode 期望的路由节点
		wantNode *node
	}{}

	// step3. 测试是否找到节点
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			// 3.1 判断在路由树中是否找到了节点
			if !found {
				return
			}

			// 3.2 判断找到的节点和预期的节点是否相同
			msg, equal := testCase.wantNode.equal(foundNode)
			assert.True(t, equal, msg)
		})
	}
}
```

### 4.4 构造测试用例

* HTTP动词对应的路由树不存在
* 完全命中
* 命中了path对应的节点,但HandleFunc为nil
* 根节点(特殊处理)
* path对应的节点不存在

### 4.5 根据测试用例开发findRoute()

#### 4.5.1 HTTP动词对应的路由树不存在

**a. 实现**

`route.go`:

```go
// findRoute 根据给定的HTTP动词和path 在路由树中查找匹配的节点
func (r *router) findRoute(method string, path string) (*node, bool) {
	_, ok := r.trees[method]
	if !ok {
		return nil, false
	}
	panic("implement me")
}
```

**b. 测试**

`route_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user",
		},
	}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		// name 子测试用例的名称
		name string
		// method HTTP动词
		method string
		// path 路由路径
		path string
		// isFound 是否找到了节点
		isFound bool
		// wantNode 期望的路由节点
		wantNode *node
	}{
		{
			name:     "Method not found",
			method:   http.MethodDelete,
			path:     "/user",
			isFound:  false,
			wantNode: nil,
		},
	}

	// step3. 测试是否找到节点
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			// 3.1 判断在路由树中是否找到了节点
			if !found {
				return
			}

			// 3.2 判断找到的节点和预期的节点是否相同
			msg, equal := testCase.wantNode.equal(foundNode)
			assert.True(t, equal, msg)
		})
	}
}
```

#### 4.5.2 完全命中

**a. 实现**

* step1. 按`/`切割path
* step2. 从路由树的根节点开始,按"层次"查找节点
* step3. 没找到则返回`nil, false`即可

这里我一开始的实现是这样的:

`route.go`:

```go
// findRoute 根据给定的HTTP动词和path 在路由树中查找匹配的节点
func (r *router) findRoute(method string, path string) (*node, bool) {
	// HTTP动词对应的路由树不存在 直接返回nil false即可
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	//
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	target := root
	for _, pathSegment := range pathSegments {
		target, ok = target.children[pathSegment]
		if !ok {
			return nil, false
		}
	}
	return target, true
}
```

有2个问题:

1. 没有对`target.children`判空
2. "在当前节点下查找子节点"是一个独立完整的功能,应该是`node`结构体的方法

修改后的实现:

`node.go`:

```go
// childOf 本方法用于根据给定的path值 在当前节点的子节点映射中查找path为给定path值的节点
// 找到则返回节点 否则返回 nil, false
func (n *node) childOf(path string) (*node, bool) {
	if n.children == nil {
		return nil, false
	}

	child, found := n.children[path]
	if !found {
		return nil, false
	}

	return child, true
}
```

`route.go`:

```go
// findRoute 根据给定的HTTP动词和path 在路由树中查找匹配的节点
func (r *router) findRoute(method string, path string) (*node, bool) {
	// HTTP动词对应的路由树不存在 直接返回nil false即可
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	// 在路由树中逐层查找节点
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	target := root
	for _, pathSegment := range pathSegments {
		target, ok = target.children[pathSegment]
		if !ok {
			return nil, false
		}
	}
	return target, true
}
```

**b. 测试**

`route_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
	}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		// name 子测试用例的名称
		name string
		// method HTTP动词
		method string
		// path 路由路径
		path string
		// isFound 是否找到了节点
		isFound bool
		// wantNode 期望的路由节点
		wantNode *node
	}{
		{
			name:     "Method not found",
			method:   http.MethodDelete,
			path:     "/user",
			isFound:  false,
			wantNode: nil,
		},
		{
			name:    "completely match",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:       "detail",
				children:   nil,
				HandleFunc: mockHandle,
			},
		},
	}

	// step3. 测试是否找到节点
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			// 3.1 判断在路由树中是否找到了节点
			if !found {
				return
			}

			// 3.2 判断找到的节点和预期的节点是否相同
			msg, equal := testCase.wantNode.equal(foundNode)
			assert.True(t, equal, msg)
		})
	}
}
```

#### 4.5.3 命中了path对应的节点,但HandleFunc为nil

这个case不需要做特殊的边缘条件检测.因为在`addRoute()`时我们也没有检测HandleFunc是否为空.换言之就是:既然在注册路由时允许使用者传入空的HandleFunc,那么在查找时命中了一个HandleFunc为nil的节点就不算是错误

**a. 测试**

`route_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
	}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		// name 子测试用例的名称
		name string
		// method HTTP动词
		method string
		// path 路由路径
		path string
		// isFound 是否找到了节点
		isFound bool
		// wantNode 期望的路由节点
		wantNode *node
	}{
		{
			name:     "Method not found",
			method:   http.MethodDelete,
			path:     "/user",
			isFound:  false,
			wantNode: nil,
		},
		{
			name:    "completely match",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:       "detail",
				children:   nil,
				HandleFunc: mockHandle,
			},
		},
		{
			name:    "nil handle func",
			method:  http.MethodGet,
			path:    "/order",
			isFound: true,
			wantNode: &node{
				path: "order",
				children: map[string]*node{
					"detail": &node{
						path:       "detail",
						children:   nil,
						HandleFunc: mockHandle,
					},
				},
				HandleFunc: nil,
			},
		},
	}

	// step3. 测试是否找到节点
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			// 3.1 判断在路由树中是否找到了节点
			if !found {
				return
			}

			// 3.2 判断找到的节点和预期的节点是否相同
			msg, equal := testCase.wantNode.equal(foundNode)
			assert.True(t, equal, msg)
		})
	}
}
```

#### 4.5.4 根节点

**a. 实现**

`route.go`:

```go
// findRoute 根据给定的HTTP动词和path 在路由树中查找匹配的节点
func (r *router) findRoute(method string, path string) (*node, bool) {
	// HTTP动词对应的路由树不存在 直接返回nil false即可
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	target := root
	// 对根节点做特殊处理
	if path == "/" {
		return target, true
	}

	// 在路由树中逐层查找节点
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	for _, pathSegment := range pathSegments {
		target, ok = target.children[pathSegment]
		if !ok {
			return nil, false
		}
	}
	return target, true
}
```

**b. 测试**

`route_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
		{
			method: http.MethodGet,
			path:   "/",
		},
	}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		// name 子测试用例的名称
		name string
		// method HTTP动词
		method string
		// path 路由路径
		path string
		// isFound 是否找到了节点
		isFound bool
		// wantNode 期望的路由节点
		wantNode *node
	}{
		{
			name:     "Method not found",
			method:   http.MethodDelete,
			path:     "/user",
			isFound:  false,
			wantNode: nil,
		},
		{
			name:    "completely match",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:       "detail",
				children:   nil,
				HandleFunc: mockHandle,
			},
		},
		{
			name:    "nil handle func",
			method:  http.MethodGet,
			path:    "/order",
			isFound: true,
			wantNode: &node{
				path: "order",
				children: map[string]*node{
					"detail": &node{
						path:       "detail",
						children:   nil,
						HandleFunc: mockHandle,
					},
				},
				HandleFunc: nil,
			},
		},
		{
			name:    "root node",
			method:  http.MethodGet,
			path:    "/",
			isFound: true,
			wantNode: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path:       "user",
						children:   nil,
						HandleFunc: mockHandle,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:       "detail",
								children:   nil,
								HandleFunc: mockHandle,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandle,
			},
		},
	}

	// step3. 测试是否找到节点
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			// 3.1 判断在路由树中是否找到了节点
			if !found {
				return
			}

			// 3.2 判断找到的节点和预期的节点是否相同
			msg, equal := testCase.wantNode.equal(foundNode)
			assert.True(t, equal, msg)
		})
	}
}
```

#### 4.5.5 path对应的节点不存在

**a. 测试**

`route_test.go`:

```go
func TestRouter_FindRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/user",
		},
		{
			method: http.MethodGet,
			path:   "/order/detail",
		},
		{
			method: http.MethodGet,
			path:   "/",
		},
	}
	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		// name 子测试用例的名称
		name string
		// method HTTP动词
		method string
		// path 路由路径
		path string
		// isFound 是否找到了节点
		isFound bool
		// wantNode 期望的路由节点
		wantNode *node
	}{
		{
			name:     "Method not found",
			method:   http.MethodDelete,
			path:     "/user",
			isFound:  false,
			wantNode: nil,
		},
		{
			name:    "completely match",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:       "detail",
				children:   nil,
				HandleFunc: mockHandle,
			},
		},
		{
			name:    "nil handle func",
			method:  http.MethodGet,
			path:    "/order",
			isFound: true,
			wantNode: &node{
				path: "order",
				children: map[string]*node{
					"detail": &node{
						path:       "detail",
						children:   nil,
						HandleFunc: mockHandle,
					},
				},
				HandleFunc: nil,
			},
		},
		{
			name:    "root node",
			method:  http.MethodGet,
			path:    "/",
			isFound: true,
			wantNode: &node{
				path: "/",
				children: map[string]*node{
					"user": &node{
						path:       "user",
						children:   nil,
						HandleFunc: mockHandle,
					},
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:       "detail",
								children:   nil,
								HandleFunc: mockHandle,
							},
						},
						HandleFunc: nil,
					},
				},
				HandleFunc: mockHandle,
			},
		},
		{
			name:     "path not found",
			method:   http.MethodGet,
			path:     "/login",
			isFound:  false,
			wantNode: nil,
		},
	}

	// step3. 测试是否找到节点
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			foundNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			// 3.1 判断在路由树中是否找到了节点
			if !found {
				return
			}

			// 3.2 判断找到的节点和预期的节点是否相同
			msg, equal := testCase.wantNode.equal(foundNode)
			assert.True(t, equal, msg)
		})
	}
}
```

## PART5. route集成至Server

### 5.1 实现

`server.go`:

```go
// ServeHTTP 是http.Handler接口的方法 此处必须先写个实现 不然Server不是http.Handler接口的实现
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	s.serve(ctx)
}

// serve 查找路由树并执行匹配到的节点所对应的处理函数
func (s *HTTPServer) serve(ctx *Context) {
	method := ctx.Req.Method
	path := ctx.Req.URL.Path
	targetNode, found := s.router.findRoute(method, path)
	if !found || targetNode.HandleFunc == nil {
		ctx.Resp.WriteHeader(http.StatusNotFound)
		_, _ = ctx.Resp.Write([]byte("not found"))
		return
	}
	targetNode.HandleFunc(ctx)
}
```

### 5.2 测试

`server_test.go`:

```go
package v4_rc

import (
	"net/http"
	"testing"
)

// TestServer_Start 测试服务器启动
func TestServer_Start(t *testing.T) {
	s := NewHTTPServer()
	handleFunc := func(ctx *Context) {
		ctx.Resp.Write([]byte("hello order detail"))
	}
	s.addRoute(http.MethodGet, "/order/detail", handleFunc)
	err := s.Start(":8081")
	if err != nil {
		t.Fatal(err)
	}
}
```

## PART6. 通配符路由的注册与查找

### 6.1 通配符路由的定义与设计

#### 6.1.1 通配符路由的定义

通配符路由:用`*`表达匹配任何路径

#### 6.1.2 通配符路由的设计

1. 一个`*`只能表达1段路由
2. 不做可回溯的路由匹配机制

### 6.2 通配符路由的注册

#### 6.2.1 修改`node`的结构

`node.go`:

```go
// node 路由树中的节点
type node struct {
	// path 路由路径
	path string
	// children 子节点 key为子节点的路由路径 value为路径对应子节点
	children map[string]*node
	// wildcardChild 通配符子节点
	wildcardChild *node
	// HandleFunc 路由对应的处理函数
	HandleFunc
}
```

#### 6.2.2 定义测试用例

`route_test.go`:

```go
func TestRouter_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		// 普通节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 断言路由树
	wantRouter := &router{trees: map[string]*node{
		"/": &node{
			path: "/",
			children: map[string]*node{
				"order": &node{
					path:     "order",
					children: nil,
					wildcardChild: &node{
						path:          "*",
						children:      nil,
						wildcardChild: nil,
						HandleFunc:    mockHandleFunc,
					},
					HandleFunc: nil,
				},
			},
			wildcardChild: nil,
			HandleFunc:    nil,
		},
	}}

	msg, ok := wantRouter.equal(r)
	assert.True(t, ok, msg)
}
```

根据debug的结果可知:应该把`*`创建在`wildcardChild`字段上,现在则是创建在了`children`字段上

#### 6.2.3 修改创建子节点的逻辑

正确的逻辑是:当路径为`*`时,将节点创建在`wildcardChild`字段上

`node.go`:

```go
// findOrCreate 本方法用于根据给定的path值 在当前节点的子节点中查找path为给定path值的节点
// 找到则返回 未找到则创建
func (n *node) findOrCreate(segment string) *node {
	// 若路径为* 则查找或创建通配符子节点
	if segment == "*" {
		if n.wildcardChild == nil {
			n.wildcardChild = &node{
				path: "*",
			}
		}
		return n.wildcardChild
	}

	if n.children == nil {
		n.children = make(map[string]*node)
	}

	target, exist := n.children[segment]
	if !exist {
		// 当前节点的子节点映射中不存在目标子节点 则创建目标子节点 将子节点加入当前节点的子节点映射后返回
		target = &node{
			path: segment,
		}
		n.children[segment] = target
		return target
	}

	// 当前节点的子节点映射中存在目标子节点 则直接返回
	return target
}
```

#### 6.2.4 修改判断子节点相等的逻辑

由于新增了通配符子节点字段,所以对于2个节点,也要比对各自的通配符子节点是否相同

`route_test.go`:

```go
func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比较2个节点的通配符子节点是否相同
	if n.wildcardChild != nil {
		if target.wildcardChild == nil {
			msg = fmt.Sprintf("目标节点的通配符子节点为空")
			return msg, false
		}
		_, equal := n.wildcardChild.equal(target.wildcardChild)
		if !equal {
			msg = fmt.Sprintf("期望节点 %s 的通配符子节点与目标节点 %s 的通配符子节点不等", n.path, target.path)
			return msg, false
		}
	}

	// step5. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step6. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step6.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step6.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

此时再跑测试用例,就可以跑通了

#### 6.2.5 添加其他测试用例

**6.2.5.1 根节点的通配符子节点**

`route_test.go`:

```go
func TestRouter_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		// 普通节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
		// 根节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 断言路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"order": &node{
						path:     "order",
						children: nil,
						wildcardChild: &node{
							path:          "*",
							children:      nil,
							wildcardChild: nil,
							HandleFunc:    mockHandleFunc,
						},
						HandleFunc: nil,
					},
				},
				wildcardChild: &node{
					path:          "*",
					children:      nil,
					wildcardChild: nil,
					HandleFunc:    mockHandleFunc,
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(r)
	assert.True(t, ok, msg)
}
```

**6.2.5.2 通配符子节点的通配符子节点**

`route_test.go`:

```go
func TestRouter_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		// 普通节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
		// 根节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*",
		},
		// 通配符子节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*/*",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 断言路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"order": &node{
						path:     "order",
						children: nil,
						wildcardChild: &node{
							path:          "*",
							children:      nil,
							wildcardChild: nil,
							HandleFunc:    mockHandleFunc,
						},
						HandleFunc: nil,
					},
				},
				wildcardChild: &node{
					path:     "*",
					children: nil,
					wildcardChild: &node{
						path:          "*",
						children:      nil,
						wildcardChild: nil,
						HandleFunc:    mockHandleFunc,
					},
					HandleFunc: mockHandleFunc,
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(r)
	assert.True(t, ok, msg)
}
```

**6.2.5.3 通配符子节点的普通子节点**

`route_test.go`:

```go
func TestRouter_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		// 普通节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
		// 根节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*",
		},
		// 通配符子节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*/*",
		},
		// 通配符子节点的普通子节点
		{
			method: http.MethodGet,
			path:   "/*/get",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 断言路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"order": &node{
						path:     "order",
						children: nil,
						wildcardChild: &node{
							path:          "*",
							children:      nil,
							wildcardChild: nil,
							HandleFunc:    mockHandleFunc,
						},
						HandleFunc: nil,
					},
				},
				wildcardChild: &node{
					path: "*",
					children: map[string]*node{
						"get": &node{
							path:          "get",
							children:      nil,
							wildcardChild: nil,
							HandleFunc:    mockHandleFunc,
						},
					},
					wildcardChild: &node{
						path:          "*",
						children:      nil,
						wildcardChild: nil,
						HandleFunc:    mockHandleFunc,
					},
					HandleFunc: mockHandleFunc,
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(r)
	assert.True(t, ok, msg)
}
```

**6.2.5.4 通配符子节点的普通子节点的通配符子节点**

`route_test.go`:

```go
func TestRouter_wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		// 普通节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
		// 根节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*",
		},
		// 通配符子节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*/*",
		},
		// 通配符子节点的普通子节点
		{
			method: http.MethodGet,
			path:   "/*/get",
		},
		// 通配符子节点的普通子节点的通配符子节点
		{
			method: http.MethodGet,
			path:   "/*/order/*",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 断言路由树
	wantRouter := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"order": &node{
						path:     "order",
						children: nil,
						wildcardChild: &node{
							path:          "*",
							children:      nil,
							wildcardChild: nil,
							HandleFunc:    mockHandleFunc,
						},
						HandleFunc: nil,
					},
				},
				wildcardChild: &node{
					path: "*",
					children: map[string]*node{
						"get": &node{
							path:          "get",
							children:      nil,
							wildcardChild: nil,
							HandleFunc:    mockHandleFunc,
						},
						"order": &node{
							path:     "order",
							children: nil,
							wildcardChild: &node{
								path:          "*",
								children:      nil,
								wildcardChild: nil,
								HandleFunc:    mockHandleFunc,
							},
							HandleFunc: nil,
						},
					},
					wildcardChild: &node{
						path:          "*",
						children:      nil,
						wildcardChild: nil,
						HandleFunc:    mockHandleFunc,
					},
					HandleFunc: mockHandleFunc,
				},
				HandleFunc: nil,
			},
		},
	}

	msg, ok := wantRouter.equal(r)
	assert.True(t, ok, msg)
}
```

### 6.3 通配符路由的匹配

#### 6.3.1 实现

思路比较简单:

* 若当前节点的children映射为空,则有可能匹配到通配符子节点
* 若在当前节点的children映射中没有找到path对应的子节点,则有可能匹配到通配符子节点
  * 此处说"有可能匹配到",是因为还有一种可能是通配符子节点为空,这种情况就属于没匹配到了

`node.go`:

```go
// childOf 本方法用于根据给定的path值 在当前节点的子节点映射中查找path为给定path值的节点
// 找到则返回节点 否则返回 nil, false
func (n *node) childOf(path string) (*node, bool) {
	if n.children == nil {
		return n.wildcardChild, n.wildcardChild != nil
	}

	child, found := n.children[path]
	if !found {
		return n.wildcardChild, n.wildcardChild != nil
	}

	return child, true
}
```

#### 6.3.2 测试

**a. 匹配普通节点的通配符子节点**

`route_test.go`:

```go
func TestRouter_FindRoute_Wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
	}

	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		name     string
		method   string
		path     string
		isFound  bool
		wantNode *node
	}{
		{
			name:    "普通节点的通配符子节点",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:          "*",
				children:      nil,
				wildcardChild: nil,
				HandleFunc:    mockHandle,
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			targetNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			if !found {
				return
			}

			msg, equal := testCase.wantNode.equal(targetNode)
			assert.True(t, equal, msg)
		})
	}
}
```

**b. 匹配普通节点下普通子节点和通配符子节点共存**

```go
func TestRouter_FindRoute_Wildcard(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/order/*",
		},
		{
			method: http.MethodGet,
			path:   "/order/create",
		},
	}

	r := newRouter()
	mockHandle := func(ctx *Context) {}

	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		name     string
		method   string
		path     string
		isFound  bool
		wantNode *node
	}{
		{
			name:    "普通节点的通配符子节点",
			method:  http.MethodGet,
			path:    "/order/detail",
			isFound: true,
			wantNode: &node{
				path:          "*",
				children:      nil,
				wildcardChild: nil,
				HandleFunc:    mockHandle,
			},
		},
		{
			name:    "普通节点下通配符子节点和普通子节点共存",
			method:  http.MethodGet,
			path:    "/order/create",
			isFound: true,
			wantNode: &node{
				path:          "create",
				children:      nil,
				wildcardChild: nil,
				HandleFunc:    mockHandle,
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			targetNode, found := r.findRoute(testCase.method, testCase.path)
			assert.Equal(t, testCase.isFound, found)
			if !found {
				return
			}

			msg, equal := testCase.wantNode.equal(targetNode)
			assert.True(t, equal, msg)
		})
	}
}
```

**c. server组合route时的通配符匹配**

`server_test.go`:

```go
package v4_rc

import (
	"net/http"
	"testing"
)

// TestServer_Start 测试服务器启动
func TestServer_Start(t *testing.T) {
	s := NewHTTPServer()

	wildcardHandleFunc := func(ctx *Context) {
		ctx.Resp.Write([]byte("hello order wildcard"))
	}
	s.addRoute(http.MethodGet, "/order/*", wildcardHandleFunc)

	handleFunc := func(ctx *Context) {
		ctx.Resp.Write([]byte("hello order detail"))
	}
	s.addRoute(http.MethodGet, "/order/detail", handleFunc)

	err := s.Start(":8081")
	if err != nil {
		t.Fatal(err)
	}
}
```

## PART7. 参数路由的注册与查找

### 7.1 参数路由的定义与设计

#### 7.1.1 参数路由的定义

参数路由:就是指在路由中带上参数,同时这些参数对应的值可以被业务取出来使用.在我们的设计中用`:参数名`的形式表示路由参数

例:`/user/:id`,如果输入路径`/user/123`,则会命中这个路由`/user/:id`,并且在业务函数中可以取到变量`id = 123`

#### 7.1.2 参数路径的设计

**是否允许同样的参数路由和通配符路由一起注册?**

例如同时注册`/user/:id`和`/user/*`一起注册?

可以允许,但没必要,且用户也不该设计这种路由

### 7.2 实现参数路由节点的创建

#### 7.2.1 修改node的结构

`node.go`

```go
// node 路由树中的节点
type node struct {
	path          string           // path 路由路径
	children      map[string]*node // children 子节点 key为子节点的路由路径 value为路径对应子节点
	wildcardChild *node            // wildcardChild 通配符子节点
	paramChild    *node            // paramChild 参数路由子节点
	HandleFunc                     // HandleFunc 路由对应的处理函数
}
```

#### 7.2.2 定义测试用例

`router_test.go`

```go
func TestRouter_addParamRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []TestNode{
		{
			method: http.MethodGet,
			path:   "/order/detail/:id",
		},
	}

	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandleFunc)
	}

	// step2. 验证路由树
	wantRoute := &router{
		trees: map[string]*node{
			http.MethodGet: &node{
				path: "/",
				children: map[string]*node{
					"order": &node{
						path: "order",
						children: map[string]*node{
							"detail": &node{
								path:          "detail",
								children:      nil,
								wildcardChild: nil,
								paramChild: &node{
									path:          ":id",
									children:      nil,
									wildcardChild: nil,
									paramChild:    nil,
									HandleFunc:    mockHandleFunc,
								},
								HandleFunc: nil,
							},
						},
						wildcardChild: nil,
						paramChild:    nil,
						HandleFunc:    nil,
					},
				},
				wildcardChild: nil,
				paramChild:    nil,
				HandleFunc:    nil,
			},
		},
	}

	msg, equal := wantRoute.equal(r)
	assert.True(t, equal, msg)
}
```

#### 7.2.3 修改创建子节点的逻辑

`node.go`:

```go
// findOrCreate 本方法用于根据给定的path值 在当前节点的子节点中查找path为给定path值的节点
// 找到则返回 未找到则创建
func (n *node) findOrCreate(segment string) *node {
	// 若路径以:开头 则查找或创建参数子节点
	if strings.HasPrefix(segment, ":") {
		if n.paramChild == nil {
			n.paramChild = &node{
				path: segment,
			}
		}
		return n.paramChild
	}

	// 若路径为* 则查找或创建通配符子节点
	if segment == "*" {
		if n.wildcardChild == nil {
			n.wildcardChild = &node{
				path: "*",
			}
		}
		return n.wildcardChild
	}

	if n.children == nil {
		n.children = make(map[string]*node)
	}

	target, exist := n.children[segment]
	if !exist {
		// 当前节点的子节点映射中不存在目标子节点 则创建目标子节点 将子节点加入当前节点的子节点映射后返回
		target = &node{
			path: segment,
		}
		n.children[segment] = target
		return target
	}

	// 当前节点的子节点映射中存在目标子节点 则直接返回
	return target
}
```

#### 7.2.4 修改判断子节点相等的逻辑

`router_test.go`:

```go
func (n *node) equal(target *node) (msg string, ok bool) {
	// step1. 目标节点为空 则必然不等
	if target == nil {
		msg = "目标节点为nil"
		return msg, false
	}

	// step2. 比较节点的路径是否相同
	if n.path != target.path {
		msg = fmt.Sprintf("节点的路径不等, 期望节点的路径为: %s, 目标节点的路径为: %s", n.path, target.path)
		return msg, false
	}

	// step3. 比较2个节点的子节点数量是否相同
	if len(n.children) != len(target.children) {
		msg = fmt.Sprintf("节点的子节点数量不等, 期望节点的子节点数量为: %d, 目标节点的子节点数量为: %d", len(n.children), len(target.children))
		return msg, false
	}

	// step4. 比对2个节点的参数子节点是否相同
	if n.paramChild != nil {
		if target.paramChild == nil {
			msg = fmt.Sprintf("目标节点的参数节点为空")
			return msg, false
		}
		_, equal := n.paramChild.equal(target.paramChild)
		if !equal {
			msg = fmt.Sprintf("期望节点 %s 的参数子节点与目标节点 %s 的参数子节点不等", n.path, target.path)
			return msg, false
		}
	}

	// step5. 比较2个节点的通配符子节点是否相同
	if n.wildcardChild != nil {
		if target.wildcardChild == nil {
			msg = fmt.Sprintf("目标节点的通配符子节点为空")
			return msg, false
		}
		_, equal := n.wildcardChild.equal(target.wildcardChild)
		if !equal {
			msg = fmt.Sprintf("期望节点 %s 的通配符子节点与目标节点 %s 的通配符子节点不等", n.path, target.path)
			return msg, false
		}
	}

	// step5. 比较2个节点的处理函数是否相同
	wantHandler := reflect.ValueOf(n.HandleFunc)
	targetHandler := reflect.ValueOf(target.HandleFunc)
	if wantHandler != targetHandler {
		msg = fmt.Sprintf("节点的处理函数不等, 期望节点 %s 的处理函数为: %v, 目标节点 %s 的处理函数为: %v", n.path, wantHandler, target.path, targetHandler)
		return msg, false
	}

	// step6. 比较2个节点的子节点是否相同
	for path, child := range n.children {
		// step6.1 比对2个节点的子节点的路径是否相同
		dstChild, exist := target.children[path]
		if !exist {
			msg = fmt.Sprintf("目标节点中不存在路径为: %s 的子节点", path)
			return msg, false
		}

		// step6.2 对路径相同的子节点递归比对
		msg, equal := child.equal(dstChild)
		if !equal {
			return msg, false
		}
	}

	return "", true
}
```

### 7.3 参数路由的校验

#### 7.3.1 实现校验逻辑

设计中是不准备支持同样的参数路由和通配符路由一起注册的(即`/user/*`和`/user/:id`).

所以从逻辑上来讲,只需要实现:**注册参数路由时检测通配符路由是否存在;注册通配符路由时检测参数路由是否存在.若对方存在,则不允许注册即可**.

`node.go`

```go
// findOrCreate 本方法用于根据给定的path值 在当前节点的子节点中查找path为给定path值的节点
// 找到则返回 未找到则创建
func (n *node) findOrCreate(segment string) *node {
	// 若路径以:开头 则查找或创建参数子节点
	if strings.HasPrefix(segment, ":") {
		if n.wildcardChild != nil {
			msg := fmt.Sprintf("web: 非法路由,节点 %s 已有通配符路由.不允许同时注册通配符路由和参数路由", n.path)
			panic(msg)
		}

		if n.paramChild == nil {
			n.paramChild = &node{
				path: segment,
			}
		}
		return n.paramChild
	}

	// 若路径为* 则查找或创建通配符子节点
	if segment == "*" {
		if n.paramChild != nil {
			msg := fmt.Sprintf("web: 非法路由,节点 %s 已有参数路由.不允许同时注册通配符路由和参数路由", n.path)
			panic(msg)
		}
		
		if n.wildcardChild == nil {
			n.wildcardChild = &node{
				path: "*",
			}
		}
		return n.wildcardChild
	}

	if n.children == nil {
		n.children = make(map[string]*node)
	}

	target, exist := n.children[segment]
	if !exist {
		// 当前节点的子节点映射中不存在目标子节点 则创建目标子节点 将子节点加入当前节点的子节点映射后返回
		target = &node{
			path: segment,
		}
		n.children[segment] = target
		return target
	}

	// 当前节点的子节点映射中存在目标子节点 则直接返回
	return target
}
```

#### 7.3.2 测试

`router_test.go`:

```go
func TestRouter_findRoute_param_and_wildcard_coexist(t *testing.T) {
	// step1. 注册通配符路由
	r := newRouter()
	mockHandleFunc := func(ctx *Context) {}
	r.addRoute(http.MethodGet, "/user/*", mockHandleFunc)

	// step2. 断言非法注册
	panicFunc := func() {
		r.addRoute(http.MethodGet, "/user/:id", mockHandleFunc)
	}

	assert.Panicsf(t, panicFunc, "web: 非法路由,节点 detail 已有通配符路由.不允许同时注册通配符路由和参数路由")
}
```

### 7.4 参数路由的查找

#### 7.4.1 编写测试用例

`router_test.go`

```go
func TestRouter_findParamRoute(t *testing.T) {
	// step1. 构造路由树
	testRoutes := []*TestNode{
		{
			method: http.MethodGet,
			path:   "/order/:id",
		},
		{
			method: http.MethodGet,
			path:   "/user/:id/detail",
		},
	}

	r := newRouter()
	mockHandle := func(ctx *Context) {}
	for _, testRoute := range testRoutes {
		r.addRoute(testRoute.method, testRoute.path, mockHandle)
	}

	// step2. 构造测试用例
	testCases := []struct {
		name     string
		method   string
		path     string
		isFound  bool
		wantNode *node
	}{
		{
			name:    "param route",
			method:  http.MethodGet,
			path:    "/order/5",
			isFound: true,
			wantNode: &node{
				path:          ":id",
				children:      nil,
				wildcardChild: nil,
				paramChild:    nil,
				HandleFunc:    mockHandle,
			},
		},
		{
			name:    "param route",
			method:  http.MethodGet,
			path:    "/user/1/detail",
			isFound: true,
			wantNode: &node{
				path:          "detail",
				children:      nil,
				wildcardChild: nil,
				paramChild:    nil,
				HandleFunc:    mockHandle,
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			findNode, found := r.findRoute(testCase.method, testCase.path)
			assert.True(t, found, "节点未找到")
			if !found {
				return
			}
			msg, equal := testCase.wantNode.equal(findNode)
			assert.True(t, equal, msg)
		})
	}
}
```

此时运行测试用例报错节点未找到,因为此时没有查找参数子节点的逻辑

#### 7.4.2 实现参数路由的查找

`node.go`:

```go
// childOf 本方法用于根据给定的path值 在当前节点的子节点映射中查找path为给定path值的节点
// 找到则返回节点 否则返回 nil, false
func (n *node) childOf(path string) (*node, bool) {
	if n.children == nil {
		if n.paramChild != nil {
			return n.paramChild, true
		}

		return n.wildcardChild, n.wildcardChild != nil
	}

	child, found := n.children[path]
	if !found {
		if n.paramChild != nil {
			return n.paramChild, true
		}
		
		return n.wildcardChild, n.wildcardChild != nil
	}

	return child, true
}
```

此时测试用例即可通过

### 7.5 参数路由的参数值

#### 7.5.1 定义新类型

需要定义一个新的类型,该类型包含命中的节点,以及当该节点为参数路由节点时的参数名和参数值

`match_node.go`:

```go
package v4_rc

type matchNode struct {
	node       *node             // node 命中的节点
	pathParams map[string]string // pathParams 节点对应的路由参数 其中key为参数名 value为参数值
}
```

#### 7.5.2 修改`node.childOf()`方法

`node.childOf()`方法需要告知其调用者(也就是`router.findRoute()`方法)找到的节点是否为参数子节点.因为查找节点时,拿到的是参数值,而参数名是放在查找到的节点的path字段上的.

`node.go`:

```go
// childOf 本方法用于根据给定的path值 在当前节点的子节点映射中查找对应的子节点
// 若未在当前节点的子节点映射中查找到path对应的节点 则尝试查找当前节点的参数子节点
// 若未查找到当前节点的参数子节点 则尝试查找当前节点的通配符子节点
func (n *node) childOf(path string) (targetNode *node, isParamNode bool, isFound bool) {
	if n.children == nil {
		if n.paramChild != nil {
			return n.paramChild, true, true
		}

		return n.wildcardChild, false, n.wildcardChild != nil
	}

	child, found := n.children[path]
	if !found {
		if n.paramChild != nil {
			return n.paramChild, true, true
		}

		return n.wildcardChild, false, n.wildcardChild != nil
	}

	return child, false, true
}
```

#### 7.5.3 为`matchNode`结构体添加写入路由参数的方法

`match_node.go`:

```go
// addPathParam 用于添加路径参数
func (m *matchNode) addPathParam(key string, value string) {
	if m.pathParams == nil {
		m.pathParams = make(map[string]string)
	}
	m.pathParams[key] = value
}
```

#### 7.5.4 修改`router.findRoute()`方法

`router.findRoute()`方法则返回一个`*matchNode`类型的实例,其中包含了参数名和参数值(如果有的话).

`router.go`

```go
// findRoute 根据给定的HTTP动词和path 在路由树中查找匹配的节点
func (r *router) findRoute(method string, path string) (*matchNode, bool) {
	targetMatchNode := &matchNode{}
	// HTTP动词对应的路由树不存在 直接返回nil false即可
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	target := root
	// 对根节点做特殊处理
	if path == "/" {
		targetMatchNode.node = target
		return targetMatchNode, true
	}

	// 在路由树中逐层查找节点
	path = strings.TrimLeft(path, "/")
	pathSegments := strings.Split(path, "/")
	for _, pathSegment := range pathSegments {
		child, isParam, ok := target.childOf(pathSegment)
		if !ok {
			return nil, false
		}
		
		if isParam {
			key := strings.TrimPrefix(child.path, ":")
			value := pathSegment
			targetMatchNode.addPathParam(key, value)
		}
		
		target = child
	}

	targetMatchNode.node = target
	return targetMatchNode, true
}
```

#### 7.5.5 修改`Context`结构体

`context.go`

```go
// Context 路由处理函数的上下文
type Context struct {
	Req        *http.Request       // Req HTTP请求
	Resp       http.ResponseWriter // Resp HTTP响应
	PathParams map[string]string   // PathParams 参数路由的参数
}
```

#### 7.5.6 修改`HTTPServer.serve()`方法

`server.go`

```go
// serve 查找路由树并执行匹配到的节点所对应的处理函数
func (s *HTTPServer) serve(ctx *Context) {
	method := ctx.Req.Method
	path := ctx.Req.URL.Path
	targetNode, found := s.router.findRoute(method, path)
	if !found || targetNode.node.HandleFunc == nil {
		ctx.Resp.WriteHeader(http.StatusNotFound)
		_, _ = ctx.Resp.Write([]byte("not found"))
		return
	}
	ctx.PathParams = targetNode.pathParams
	targetNode.node.HandleFunc(ctx)
}
```

#### 7.5.7 测试HTTPServer

`server_test.go`:

```go
func TestServer_Start(t *testing.T) {
	s := NewHTTPServer()

	wildcardHandleFunc := func(ctx *Context) {
		ctx.Resp.Write([]byte("hello order wildcard"))
	}
	s.addRoute(http.MethodGet, "/order/*", wildcardHandleFunc)

	handleFunc := func(ctx *Context) {
		ctx.Resp.Write([]byte("hello order detail"))
	}
	s.addRoute(http.MethodGet, "/order/detail", handleFunc)

	paramFunc := func(ctx *Context) {
		ctx.Resp.Write([]byte(fmt.Sprintf("%s", ctx.PathParams)))
	}
	s.addRoute(http.MethodGet, "/user/:id", paramFunc)

	err := s.Start(":8081")
	if err != nil {
		t.Fatal(err)
	}
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go.sai.show/part04.-ke-hou-fu-xi/4.02-ke-hou-fu-xi-route.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
