代码编写
确定模块划分和目录结构
将项目分为两个部分
gee包:存放核心代码(如路由注册、请求分发、服务启动等)。
main 程序:用户编写的应用程序,调用 gee 包提供的接口。
gee/
|--gee.go
|--go.mod
main.go
go.mod
解读:一个成熟的框架通常会将核心功能与用户业务代码分离。这里,我们希望用户在 main.go 中调用我们的框架(比如 gee.New()、r.GET()、r.Run()),而具体的实现都放在 gee 包中。
编写 go.mod 文件进行模块依赖管理
确保 main.go 能正确引用你的 gee 包
// 定义模块名
module gee-demo
go 1.23
// 添加对gee的依赖
require (
gee v0.0.0
)
replace (
gee => ./gee
)
定义函数类型
我们希望框架用户能用简单的方式定义处理函数,因此需要定义一个函数类型。
// 定义一个类型 HandlerFunc
// 本质上是一个函数签名,与标准库中的 http.HandlerFunc 类似。
type HandlerFunc func(http.ResponseWriter, *http.Request)
作用:使框架用户能够用一致的方式来定义请求处理函数
设计函数结构体engine
Engine 将作为整个框架的入口,它需要存储路由和对应的处理函数。
使用 map 类型存储路由映射
map 的 key:考虑既有 HTTP 方法也有路由地址,例如 “GET-/hello”。
// 定义一个结构体(字段的集合),命名为Engine
// Engine 实现了 http.Handler 接口,是框架的核心
type Engine struct {
// 路由映射表:key 为 "METHOD-PATH"(例如 "GET-/"),value 为对应的处理函数
// 字段router,类型为 map[string]HandlerFunc。
router map[string]HandlerFunc
}
设计构造函数——方便用户创建 Engine 实例
写一个函数 New(),返回一个指向 Engine 的指针,并确保路由映射表已初始化。
func New() *Engine {
// 初始化 router 字段
return &Engine{router: make(map[string]HandlerFunc)} // 创建了一个空的 map,用来保存路由
}
功能:注册路由和对应的处理函数
设计一个内部函数(例如 addRoute)来将方法、路径和处理函数组合为一个 key,然后存入 map
// 使用 method + "-" + pattern 这种方式构造 key,并存入 router
func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
key := method + "-" + pattern
engine.router[key] = handler
}
分别为 GET 和 POST 设计公开方法,内部调用 addRoute
// 用于注册 GET method请求的路由
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
engine.addRoute("GET", pattern, handler)
}
// 用于注册 POST method请求的路由
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
engine.addRoute("POST", pattern, handler)
}
GET 和 POST 分别是用户注册不同请求方法的封装,内部调用 addRoute,增强了 API 的可读性与友好性。
功能:服务启动
设计一个 Run(addr string) 方法,将 Engine 自身作为 Handler 传入 ListenAndServe
// 包装http.ListenAndServe,用于启动HTTP服务
func (engine *Engine) Run(addr string) (err error) {
// 将 Engine 自身作为 Handler 传入 ListenAndServe
// Go 的 http.ListenAndServe 需要这个参数为实现了 http.Handler 接口的对象
return http.ListenAndServe(addr, engine)
}
实现 ServeHTTP 方法
// 只要一个对象实现了 ServeHTTP(http.ResponseWriter, *http.Request) 方法
// 就可以称为实现了http.Handler 接口
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 构造 key:由请求方法和 URL.Path 拼接而成,例如 "GET-/" 或 "GET-/hello"
key := req.Method + "-" + req.URL.Path
if handler, ok := engine.router[key]; ok {
// 如果找到了匹配的路由,则调用用户注册的处理函数
handler(w, req)
} else {
// 没有匹配的路由,返回 404 信息
fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
}
}
所有请求最终都会调用ServeHTTP 方法方法。
构造 key 并在路由映射表中查找相应的处理函数?如果找到了,则调用它;如果找不到,则返回 404 错误。
使用gee框架编写main.go
通过调用框架提供的 API(例如 New(), GET(), Run())来编写业务代码。(对框架设计的一个验证)
- 在 main.go 中导入 “gee” 包
import (
"fmt"
"gee"
"net/http"
)
- 在 main 函数中,调用 gee.New() 得到一个实例
// 调用 gee.New() 获取一个 Engine 的实例(底层对象)
r := gee.New()
- 注册路由和对应的处理函数
r.GET("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
})
r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
})
- 调用 Run() 启动服务器
// 调用 r.Run() 启动服务器,底层实际上会调用 http.ListenAndServe
r.Run(":9999")
用户无需了解内部的路由映射或请求分发细节,只需要关注如何注册路由和编写业务逻辑。
运行与验证
- 运行:
- 使用 go run main.go 启动服务器。
- 测试:
- 使用 curl 或浏览器访问:
- http://localhost:9999/ 得到响应:URL.Path = “/”。
- http://localhost:9999/hello 得到响应:请求头信息。
- 访问不存在的路由,如 http://localhost:9999/world 得到响应:404 NOT FOUND: /world。
- 使用 curl 或浏览器访问:
这些测试结果验证了我们的路由映射和请求分发机制正常工作
总结
将路由和请求分发解耦出来
将路由映射和请求处理集中在一个统一的入口(ServeHTTP)中处理,方便以后添加中间件、动态路由、分组等功能。
面向接口编程:
只要实现了 http.Handler 接口,就能利用 Go 的标准库能力。
思考
目前只实现了静态路由映射,未来可以考虑添加动态路由、中间件、路由分组等功能。
- 如何修改路由映射表的设计,使其支持动态路由?
- 在 ServeHTTP 中添加中间件处理应该放在哪里?