为你自己学Go
  • README
  • PART01.Web框架概览
    • 1.01 Web框架概览:学习路线
    • 1.02 Web框架概览-Beego框架分析
    • 1.03 Web框架概览-GIN框架分析
    • 1.04 Web框架概览-Iris框架分析
    • 1.05 Web框架概览-Echo框架分析
  • PART02.Server
    • 2.01 Server详解与面试要点
  • PART03.路由树
    • 3.01 路由树-Beego&GIN&Echo实现与设计总结
    • 3.02 路由树-全静态匹配
    • 3.03 路由树-TDD起步
    • 3.04 路由树-静态匹配测试用例
    • 3.05 路由树-静态匹配之路由查找
    • 3.06 路由树-静态匹配之集成Server
    • 3.07 路由树-通配符匹配之路由注册
    • 3.08 路由树-通配符匹配之路由查找与测试
    • 3.09 路由树-参数路径之基本注册和查找
    • 3.10 路由树-参数路径之校验
    • 3.11 路由树-参数路径之参数值
    • 3.12 路由树-总结与面试要点
  • PART04.课后复习
    • 4.01 课后复习-Server
    • 4.02 课后复习-Route
  • PART05.Context
    • 5.01 Context-简介
    • 5.02 Context-Beego Context设计分析
    • 5.03 Context-Gin Context设计分析
    • 5.04 Context-Echo和Iris的Context设计分析
    • 5.05 Context-处理输入输出总结
    • 5.06 Context-处理输入之Body输入
    • 5.07 Context-处理输入之表单输入
    • 5.08 Context-处理输入之查询参数、路径参数和StringValue
    • 5.09 Context-处理输出
    • 5.10 Context-总结与面试要点
  • PART06.AOP
    • 6.01 AOP简介与不同框架设计概览
    • 6.02 AOP设计方案-Middleware
  • PART07.Middleware
    • 7.01 Middleware-AccessLog
    • 7.02 Middleware-Trace简介和OpenTelemetry
    • 7.03 Middleware-OpenTelemetry测试
    • 7.04 Middleware-OpenTelemetry总结
    • 7.05 Prometheus详解
    • 7.06 Middleware-Prometheus
    • 7.07 Middleware-错误页面
    • 7.08 Middleware-从panic中恢复
    • 7.09 Middleware总结和面试
  • PART08.Review
    • 8.01 课后复习-AOP
    • 8.02 课后复习-Context
    • 8.03 课后复习-Middleware-AccessLog
  • PART09.Appendix
    • 附录1.责任链模式
    • 附录2.生成器模式
    • 附录3.函数选项模式
  • xiaochengxu
    • 01.原力去水印
    • 02.KeePass密码管理:安全轻松的管理您的密码
Powered by GitBook
On this page
  • PART1. 接口设计
  • 1.1 带有子树的路由树结构
  • 1.2 路由森林直接指向树的根节点的路由树结构
  1. PART03.路由树

3.02 路由树-全静态匹配

Last updated 9 months ago

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

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

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

即为v1版本

1.1 带有子树的路由树结构

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

1.1.1 定义路由森林

创建文件router.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:

package designRouteWithChildTree

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

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

1.1.3 定义节点

创建文件node.go:

package designRouteWithChildTree

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

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

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

这里需要画图演示一下:

看到这样的代码组织,可以想象到大部分的操作还是会落在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:

package designRoute

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

1.2.2 定义节点

创建文件node.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:

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:

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:

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:

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
修改前代码
node节点图示