直接实现 http.handler 就能当路由器用,因为 go http 服务器只依赖 servehttp 方法,不关心内部路由逻辑;需自行解析路径、匹配参数并分发请求,且应通过 context 传递路径参数而非篡改 url。

为什么直接实现 http.Handler 就能当路由器用
因为 Go 的 HTTP 服务器只认 http.Handler 接口,不认“路由”这个概念。只要你实现了 ServeHTTP 方法,它就认为你能处理请求——至于你是查 map、跑 trie 还是正则匹配,它不管。
常见错误是写个 RegisterRoute 方法然后期待 http.ListenAndServe(":8080", myRouter) 自动识别它。不会。你传进去的必须是满足 http.Handler 的东西。
最简可行路径就是定义一个结构体,带个 map[string]func(http.ResponseWriter, *http.Request),在 ServeHTTP 里按 req.URL.Path 查表分发。但注意:req.URL.Path 是未解码的(含 %20),/user/123%20abc 不会匹配 /user/:id,得先调 url.PathUnescape。
- 别在
ServeHTTP里panic:HTTP 服务器不会 recover,连接可能静默断开 - 注册时别用
http.HandleFunc:它默认往http.DefaultServeMux写,会触发默认的路径清理和 301 重定向 - 想禁用末尾斜杠自动跳转或双斜杠合并?必须绕过
DefaultServeMux,自己实现Handler
路径参数匹配本质是逐段比对,不是字符串 ==
真正要做的不是“支持 :id”,而是把路径按 / 拆成段,再一段段比对:
- 字面量段(如
"users")必须完全相等 -
:param段接受任意非空、不含/的字符串(/user//posts中的空段不合法) -
*段只能出现在末尾,吞掉后面所有剩余段(/static/*filepath合法,/a/*/b不合法) - 连续参数如
/a/:x/:y没问题;但/a/:x/b/:y要求中间的b必须字面匹配
别预编译每条路由为正则——性能差、难调试、路径编码(%2F)容易出错。静态结构 + 运行时逐段比对更可控。示例:/api/v1/users/:id/posts 匹配 /api/v1/users/123/posts → 拆成 ["api","v1","users","123","posts"],第 4 段填入 id="123"。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
GET /users 和 POST /users 必须分离路径匹配与方法分发
很多人把路由和 handler 绑死,结果 GET /users 和 POST /users 得写两个注册语句,重复一堆逻辑。正确做法是:路由只负责路径匹配 + 参数提取,再把 http.ResponseWriter 和 *http.Request 交给按方法分发的内层处理器。
- 注册时用
r.HandleFunc("GET", "/users", listUsers),而不是r.Handle("GET /users", ...) - 请求进来后,先走 trie 找到匹配节点,拿到参数
map和一组method→handler映射 - 再查
req.Method对应哪个 handler;没匹配上就返回405 Method Not Allowed,不是404 - 别漏掉
OPTIONS预检请求——如果你没显式注册,又没 fallback,前端 CORS 就会静默失败
参数值别塞进 req.URL.Query() 或改 req.URL.Path
req.URL.Query() 是给查询参数(?id=123)用的。/user/:id 提出来的 id 是路径参数,该放哪?常见错误是往 req.URL.RawQuery 里拼,或者改 req.URL.Path,结果下游中间件(比如日志、鉴权)看到的路径全是伪造的,排查时一头雾水。
正确做法:用 context.WithValue(req.Context(), key, value) 把参数注入上下文。key 建议用自定义类型(避免字符串冲突),比如:
type paramKey string const ParamID paramKey = "id" ... ctx := context.WithValue(req.Context(), ParamID, "123") req = req.WithContext(ctx)
这样既不影响原始 URL 结构,又能让 handler 和中间件安全取值。注意:别用 interface{} 当 key,也别用普通字符串——跨包时极易冲突。










