必须实现http.Handler接口,管理路由映射(推荐Trie支持动态路径),封装请求响应上下文;中间件需闭包嵌套而非slice循环,Context仅提供Request、Writer和Param方法,避免过度封装。

直接上手写一个能跑通、可调试、不依赖第三方路由库的轻量框架,核心就三件事:实现 http.Handler、管理路由映射、封装请求响应上下文。别被“框架”二字吓住——Go 标准库已经提供了全部底座,你只是在它上面加一层薄薄的胶水。
必须实现 http.Handler 接口,否则 ListenAndServe 拒绝启动
Go 的 http.ListenAndServe 只接受实现了 http.Handler 接口的类型。这意味着你的框架入口必须有 ServeHTTP(http.ResponseWriter, *http.Request) 方法,不能只靠 GET() 或 Run() 这类封装函数蒙混过关。
- 常见错误:定义了
Engine.GET()却没实现ServeHTTP,运行时报cannot use engine (type *Engine) as type http.Handler - 正确做法:让框架结构体(比如
Engine)显式实现该接口,内部做方法+路径匹配,找不到则调用http.NotFound - 注意:
http.Serve和http.ListenAndServe都走同一套逻辑,别试图绕过接口契约
路由匹配别硬写 map[string]func,优先用前缀树(Trie)
用 map[string]HandlerFunc 做路由看似简单,但无法支持 /user/:id 或 /file/*filepath 这类动态路径。一旦业务稍有增长,就得重写整个匹配逻辑。
- 真实场景:API 要支持
GET /api/v1/users/123和GET /api/v1/users/123/posts,静态 key 匹配会爆炸式增长 - 推荐方案:手写简易 Radix Tree(基数树),或直接复用
github.com/julienschmidt/httprouter——它只有 1 个 .go 文件,无外部依赖,性能远超 map 查找 - 坑点:自己实现时容易忽略路径结尾斜杠处理(
/usersvs/users/),建议统一 normalize 后再匹配
中间件链要用闭包嵌套,别用 slice 循环执行
把中间件存成 []MiddlewareFunc 然后 for-range 执行,会导致上下文丢失、panic 捕获失效、response 写入顺序错乱。真正的洋葱模型必须靠函数嵌套来保证调用栈可控。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 典型错误:中间件里调用
next()前就写了 response body,后续中间件和 handler 仍会执行,造成 “http: multiple response.WriteHeader calls” - 正确模式:每个中间件返回一个
func(http.ResponseWriter, *http.Request),层层包裹最终 handler,像log( auth( recover(handler) ) ) - 关键细节:必须传递自定义的
ResponseWriter实现(如WrapResponseWriter),才能在中间件里读取 status code 和 content-length
Context 封装要克制,别照搬 Gin 的 *gin.Context
新手常以为“框架必须有个 Context 对象”,于是堆砌一堆方法:GetQuery、BindJSON、SetCookie……结果发现全是 *http.Request 和 http.ResponseWriter 的薄包装,徒增心智负担。
- 建议起点:只提供
Request、Writer两个字段,外加一个Param(key string) string用于提取路由参数(从 trie 匹配结果里拿) - 避免陷阱:不要在 Context 里缓存解析结果(如重复解析 JSON body),除非明确需要多次读取;标准库的
io.ReadCloser只能读一次 - 性能提示:
context.Context(来自context包)和框架 Context 是两回事,前者用于 cancel/deadline,后者纯属业务便利封装,别混用
最易被忽略的点是:所有中间件和 handler 共享同一个 http.ResponseWriter 实例,而它的 WriteHeader 行为不可逆。一旦某个中间件提前写了状态码或 body,后续环节就只能追加内容,无法修正——这决定了错误处理必须前置,而不是靠 defer 捕获后重写响应。

















