因为 http.ServeMux 仅做静态路径映射,不支持中间件链式调用,无法在路由匹配前后统一注入日志、鉴权等逻辑,必须手动嵌套包装 handler,导致代码重复、难以维护。

为什么直接用 http.ServeMux 不行
http.ServeMux 是 Go 标准库的路由分发器,但它不支持中间件链式调用——你无法在匹配前/后插入统一逻辑(如日志、鉴权、CORS)。它只做路径到 http.HandlerFunc 的静态映射,所有“中间”行为必须手动嵌套,导致代码重复或难以维护。
常见错误是试图在每个 handler 里重复写 log.Println 或 if !auth(r) { ... },结果路由表一多就失控。真正需要的是类似 Express.js 的 app.use() 或 Gin 的 Use() 机制。
用自定义 HandlerFunc 链实现中间件
核心思路:把 handler 和中间件都抽象为 func(http.Handler) http.Handler 类型,然后按顺序组合。最终注册到 http.ServeMux 的是一个被层层包装的 http.Handler。
实操建议:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 定义中间件类型:
type Middleware func(http.Handler) http.Handler - 写一个组合函数,从右往左应用中间件(符合 Go 惯例):
func Chain(h http.Handler, m ...Middleware) http.Handler { for i := len(m) - 1; i >= 0; i-- { h = m[i](h) } return h } - 典型中间件示例(日志):
func Logger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s", r.Method, r.URL.Path) next.ServeHTTP(w, r) }) } - 使用时:
http.Handle("/api/", Chain(apiHandler, Logger, Auth))
路由匹配阶段如何注入中间件
标准 http.ServeMux 不支持 per-route 中间件,所以得自己实现路由树,或者用轻量第三方库(如 gorilla/mux)。但如果你坚持不用外部依赖,最简方案是封装一个支持中间件的 Router 结构:
关键点:
- 每个路由条目(
Route)保存自己的中间件切片,而非全局共享 - 匹配成功后,用
Chain(handler, route.Middlewares...)动态组装 handler - 注意中间件执行顺序:越靠近 handler 的越先执行(即最后注册的 middleware 最先运行)
- 避免在中间件里修改
http.ResponseWriter后再调用next.ServeHTTP——比如提前w.WriteHeader(401)又继续往下走,会 panic
net/http 的 ResponseWriter 包装陷阱
很多中间件需要读取或修改响应体(如压缩、加 header),这时必须包装 http.ResponseWriter。但标准库没有提供可写的 wrapper,容易踩坑:
- 直接类型断言
w.(http.Hijacker)可能 panic,需先检查if hj, ok := w.(http.Hijacker); ok { ... } - 响应体捕获要用
bytes.Buffer+ 自定义ResponseWriter实现,但注意:一旦调用w.WriteHeader(),header 就不可再改;一旦写入超过 512 字节,部分 HTTP 服务器会自动发送 header - 推荐做法:只对 header 做无副作用操作(如
w.Header().Set("X-Trace-ID", uuid)),复杂逻辑交给专门的中间件(如github.com/gorilla/handlers.CompressHandler)
中间件真正的复杂点不在组合逻辑,而在于对 ResponseWriter 和 Request 生命周期的理解——比如 r.Context() 被 cancel 后,中间件里启动的 goroutine 是否还安全,这点常被忽略。


















