7days-golang/gee-web/day01 Gee 框架雏形-学习笔记

代码编写

确定模块划分和目录结构

将项目分为两个部分
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())来编写业务代码。(对框架设计的一个验证)

  1. 在 main.go 中导入 “gee” 包
import (
	"fmt"
	"gee"
	"net/http"
)
  1. 在 main 函数中,调用 gee.New() 得到一个实例
// 调用 gee.New() 获取一个 Engine 的实例(底层对象)
	r := gee.New()
  1. 注册路由和对应的处理函数
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)
		}
	})
  1. 调用 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。

这些测试结果验证了我们的路由映射和请求分发机制正常工作

总结

将路由和请求分发解耦出来

将路由映射和请求处理集中在一个统一的入口(ServeHTTP)中处理,方便以后添加中间件、动态路由、分组等功能。

面向接口编程:

只要实现了 http.Handler 接口,就能利用 Go 的标准库能力。

思考

目前只实现了静态路由映射,未来可以考虑添加动态路由、中间件、路由分组等功能。
- 如何修改路由映射表的设计,使其支持动态路由?
- 在 ServeHTTP 中添加中间件处理应该放在哪里?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值