# 3.02 路由树-全静态匹配

我们利用全静态匹配来构建路由树,后面再考虑**重构路由树**以支持通配符匹配、参数路由等复杂匹配

所谓的**静态匹配,就是路径的每一段都必须严格相等**

## PART1. 接口设计

这里我们按照上节课说的,设计一种比较符合常规认知的路由树.

如同Beego中的`HttpServer`结构体和`ControllerRegister`结构体不是一个结构体;GIN中的`Engine`结构体和`IRoutes`接口的实现不是一个结构体一样.在我们的框架中,表示路由树的抽象也不应该是`HTTPServer`结构体的一个字段,而应该是一个单独的结构体.

之前我们定义了`HttpServer.AddRoute()`方法,用于注册路由.但实际上注册路由并不是`HTTPServer`的职责.`HTTPServer`是代表服务器的抽象,而非是代表路由树的抽象.

* 修改前的工程结构:

```
(base) yanglei@yuanhong 05-designRoute % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
└── serverInterface.go

0 directories, 5 files
```

[修改前代码](https://github.com/rayallen20/GoInAction/tree/master/code/week1/server/v1)即为v1版本

### 1.1 带有子树的路由树结构

这里提前开个上帝视角给一个结论:这样设计的意义不大.因为即使设计成有子树结构的路由树,最终大部分的操作还是会落在节点上,而不会落在子树上.但还是演示一下这样的代码组织

#### 1.1.1 定义路由森林

创建文件`router.go`:

```go
package designRouteWithChildTree

// router 路由森林 用于支持对路由树的操作
type router struct {
	// trees 路由森林 按HTTP动词组织路由树
	// 该map中 key为HTTP动词 value为路由树
	// 即: 每个HTTP动词对应一棵路由树
	trees map[string]tree
}
```

这里路由森林的设计和GIN/Beego的一样,没什么可讲的

#### 1.1.2 定义子树

创建文件`tree.go`:

```go
package designRouteWithChildTree

// tree 路由树
type tree struct {
	// root 树的根节点
	root *node
}
```

这里也是一样,每棵树有一个指向根节点的指针,也没什么可讲的

#### 1.1.3 定义节点

创建文件`node.go`:

```go
package designRouteWithChildTree

// node 路由树的节点
type node struct {
	// path 当前节点的路由路径
	path string

	// children 子路由路径到子节点的映射
	children map[string]*node

	// HandleFunc 路由对应的业务逻辑
	HandleFunc
}
```

这里需要画图演示一下:

![node节点图示](/files/tyxrTG6M6I4gddj5n0S8)

看到这样的代码组织,可以想象到大部分的操作还是会落在`node`结构体上.因此在`node`和`routers`之间定义一层`tree`,意义并不大.

完整工程结构如下:

```
(base) yanglei@yuanhong 05-designRoute % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── node.go
├── router.go
├── serverInterface.go
└── tree.go

0 directories, 8 files
```

### 1.2 路由森林直接指向树的根节点的路由树结构

初态工程结构如下:

```
(base) yanglei@yuanhong 06-designRoute % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
└── serverInterface.go

0 directories, 5 files
```

还是v1的代码.

#### 1.2.1 定义路由森林

创建文件`router.go`:

```go
package designRoute

// router 路由森林 用于支持对路由树的操作
type router struct {
	// trees 路由森林 按HTTP动词组织路由树
	// 该map中 key为HTTP动词 value为路由树的根节点
	// 即: 每个HTTP动词对应一棵路由树 指向每棵路由树的根节点
	trees map[string]*node
}
```

#### 1.2.2 定义节点

创建文件`node.go`:

```go
package designRoute

// node 路由树的节点
type node struct {
	// path 当前节点的路径
	path string
	
	// children 子路由路径到子节点的映射
	children map[string]*node
	
	// HandleFunc 路由对应的业务逻辑
	HandleFunc
}
```

#### 1.2.3 定义注册路由的方法

该方法负责将路由注册到对应的路由树上,当然此时我们还没有实现这个功能.实际上这个方法应该定义在`router`结构体上,而非`HTTPServer`结构体上.因为这是`router`结构体的职责.

`router.go`:

```go
package designRoute

// router 路由森林 用于支持对路由树的操作
type router struct {
	// trees 路由森林 按HTTP动词组织路由树
	// 该map中 key为HTTP动词 value为路由树的根节点
	// 即: 每个HTTP动词对应一棵路由树 指向每棵路由树的根节点
	trees map[string]*node
}

// AddRoute 注册路由到路由森林中的路由树上
func (r *router) AddRoute(method string, path string, handleFunc HandleFunc) {
	// TODO: implement me
	panic("implement me")
}
```

#### 1.2.4 HTTPServer组合router

`httpServer.go`:

```go
package designRoute

import (
	"net"
	"net/http"
)

// 为确保HTTPServer结构体为Server接口的实现而定义的变量
var _ Server = &HTTPServer{}

// HTTPServer HTTP服务器
type HTTPServer struct {
	*router
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 构建上下文
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	// 查找路由树并执行命中的业务逻辑
	s.serve(ctx)

	// TODO implement me
	panic("implement me")
}

// serve 查找路由树并执行命中的业务逻辑
func (s *HTTPServer) serve(ctx *Context) {
	// TODO implement me
	panic("implement me")
}

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

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}

// GET 注册GET请求路由
func (s *HTTPServer) GET(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodGet, path, handleFunc)
}

// POST 注册POST请求路由
func (s *HTTPServer) POST(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodPost, path, handleFunc)
}
```

此处做了2处修改:

1. `HTTPServer`结构体添加了一个匿名字段`*router`
2. `HTTPServer`结构体删除了`AddRoute()`方法
   * 但此时`HTTPServer`结构体仍然是`Server`接口的实现,因为`HTTPServer`结构体组合了`router`结构体,`router`结构体实现了`AddRoute()`方法
   * Tips:如果想要在`HTTPServer`结构体中使用命名字段来组合`router`,则还是需要实现`HTTPServer`结构体的`AddRoute()`方法,只不过是在该方法中调用`router.`AddRoute()\`

#### 1.2.5 定义创建路由森林的函数

这里其实返回指针还是实例无所谓,因为数据最终放在了一个map里边.无论你通过实例访问该map还是通过指针访问该map,因为map是引用类型,所以最终二者效果一致.

`router.go`:

```go
package designRoute

// router 路由森林 用于支持对路由树的操作
type router struct {
	// trees 路由森林 按HTTP动词组织路由树
	// 该map中 key为HTTP动词 value为路由树的根节点
	// 即: 每个HTTP动词对应一棵路由树 指向每棵路由树的根节点
	trees map[string]*node
}

// newRouter 创建路由森林
func newRouter() *router {
	return &router{
		trees: map[string]*node{},
	}
}

// AddRoute 注册路由到路由森林中的路由树上
func (r *router) AddRoute(method string, path string, handleFunc HandleFunc) {
	// TODO: implement me
	panic("implement me")
}
```

#### 1.2.6 定义创建HTTPServer的函数

`httpServer.go`:

```go
package designRoute

import (
	"net"
	"net/http"
)

// 为确保HTTPServer结构体为Server接口的实现而定义的变量
var _ Server = &HTTPServer{}

// HTTPServer HTTP服务器
type HTTPServer struct {
	*router
}

// NewHTTPServer 创建HTTP服务器
func NewHTTPServer() *HTTPServer {
	return &HTTPServer{
		router: newRouter(),
	}
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 构建上下文
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	// 查找路由树并执行命中的业务逻辑
	s.serve(ctx)

	// TODO implement me
	panic("implement me")
}

// serve 查找路由树并执行命中的业务逻辑
func (s *HTTPServer) serve(ctx *Context) {
	// TODO implement me
	panic("implement me")
}

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

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}

// GET 注册GET请求路由
func (s *HTTPServer) GET(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodGet, path, handleFunc)
}

// POST 注册POST请求路由
func (s *HTTPServer) POST(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodPost, path, handleFunc)
}
```

此处做了1处修改:

* 新增函数`NewHTTPServer()`

完整的工程结构如下:

```
(base) yanglei@yuanhong 06-designRoute % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── node.go		// 定义节点
├── router.go		// 定义路由森林
└── serverInterface.go

0 directories, 7 files
```


---

# 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/part03.-lu-you-shu/3.02-lu-you-shu-quan-jing-tai-pi-pei.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.
