Gin 路由树(radix tree)与路由冲突规则
结论先行
Gin 的路由表是按 HTTP 方法分开的多棵 radix tree(压缩前缀树):注册时构建、匹配时逐段比较,匹配开销接近 O(路径长度)。路由冲突在注册阶段直接 panic(fail fast),而不是运行期悄悄覆盖——这是刻意设计,避免线上路由语义被误改。
关键要点
| 主题 | 说明 |
|---|---|
| 树的组织 | engine.trees 里每种方法一棵树(GET/POST/PUT…),方法与路径都命中才算匹配 |
| 节点结构 | 每个节点保存静态段/参数段/通配段子节点,匹配时按优先级取段 |
| 静态与通配优先级 | 静态段 > 参数段(:name) > 通配段(*path),保证 /user/me 优先于 /user/:id |
| 冲突何时发生 | 路由注册阶段,同方法重复注册或通配符布局冲突会 panic |
| 404/405 | 未匹配走 NoRoute 处理;默认未开启方法不允许检测,可设 HandleMethodNotAllowed=true 返回 405 |
常见冲突(典型报错以当前版本源码为准)
- 同一方法下同一路径注册两次:报 “handlers are already registered for path ...”。
- 参数段与通配段相互冲突(如不同名字的同级参数、通配段后还有其他内容)。
- catch-all(*path)未放在最后一段。
- 已存在 *path 路由后,再注册其下的静态/参数子路由会产生“conflicts with existing wildcard”类 panic。
代码示例
r := gin.New()
r.GET("/user/:id", h1) // 正常
// r.GET("/user/:id", h2) // panic:同方法同路径重复注册
r.GET("/files/*filepath", h3) // catch-all 必须在末尾
// r.GET("/files/info", h4) // 与上方 *filepath 冲突,panic
r.HandleMethodNotAllowed = true // 可选:方法不匹配但路径存在时返回 405
常见追问 / 记忆点
- 追问:为什么路由冲突要 panic 而不是后注册覆盖先注册?答:覆盖会让“哪条生效”取决于注册顺序,改动顺序即改语义,极难排查;panic 把问题提前到启动期。
- 追问:参数段与静态段在同一层能不能共存、谁优先?答:Gin 匹配时静态段优先,与 httprouter 的行为存在差异,具体共存规则与各版本实现相关,以官方文档与源码为准。
- 记忆点:方法分树 + 压缩前缀树;冲突 = 启动即 panic = fail fast;静态优先于通配。