我们利用全静态匹配来构建路由树,后面再考虑重构路由树以支持通配符匹配、参数路由等复杂匹配
所谓的静态匹配,就是路径的每一段都必须严格相等
这里我们按照上节课说的,设计一种比较符合常规认知的路由树.
如同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版本
这里提前开个上帝视角给一个结论:这样设计的意义不大.因为即使设计成有子树结构的路由树,最终大部分的操作还是会落在节点上,而不会落在子树上.但还是演示一下这样的代码组织
创建文件router.go
:
package designRouteWithChildTree
// router 路由森林 用于支持对路由树的操作
type router struct {
// trees 路由森林 按HTTP动词组织路由树
// 该map中 key为HTTP动词 value为路由树
// 即: 每个HTTP动词对应一棵路由树
trees map[string]tree
}
这里路由森林的设计和GIN/Beego的一样,没什么可讲的
创建文件tree.go
:
package designRouteWithChildTree
// tree 路由树
type tree struct {
// root 树的根节点
root *node
}
这里也是一样,每棵树有一个指向根节点的指针,也没什么可讲的
创建文件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
初态工程结构如下:
(base) yanglei@yuanhong 06-designRoute % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
└── serverInterface.go
0 directories, 5 files
还是v1的代码.
创建文件router.go
:
package designRoute
// router 路由森林 用于支持对路由树的操作
type router struct {
// trees 路由森林 按HTTP动词组织路由树
// 该map中 key为HTTP动词 value为路由树的根节点
// 即: 每个HTTP动词对应一棵路由树 指向每棵路由树的根节点
trees map[string]*node
}
创建文件node.go
:
package designRoute
// node 路由树的节点
type node struct {
// path 当前节点的路径
path string
// children 子路由路径到子节点的映射
children map[string]*node
// HandleFunc 路由对应的业务逻辑
HandleFunc
}
该方法负责将路由注册到对应的路由树上,当然此时我们还没有实现这个功能.实际上这个方法应该定义在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")
}
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处修改:
HTTPServer
结构体添加了一个匿名字段*router
HTTPServer
结构体删除了AddRoute()
方法- 但此时
HTTPServer
结构体仍然是Server
接口的实现,因为HTTPServer
结构体组合了router
结构体,router
结构体实现了AddRoute()
方法 - Tips:如果想要在
HTTPServer
结构体中使用命名字段来组合router
,则还是需要实现HTTPServer
结构体的AddRoute()
方法,只不过是在该方法中调用router.
AddRoute()`
- 但此时
这里其实返回指针还是实例无所谓,因为数据最终放在了一个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")
}
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