/users/new 和 /users/:id 能放在同一个 Gin 里吗?哪些组合会 panic、panic 的原文长什么样、静态和参数共存后请求怎么选?这篇把 18 种组合逐个注册,再用请求验证回溯。
✓ 静态与参数共存(静态先) 注册 [/users/new /users/:id] → 路由表 [/users/new /users/:id]
✓ 静态与参数共存(参数先) 注册 [/users/:id /users/new] → 路由表 [/users/new /users/:id]
✓ 参数后面接不同静态段 注册 [/users/:id/a /users/:id/b] → 路由表 [/users/:id/a /users/:id/b]
✗ 同位置参数改名 ':uid' in new path '/users/:uid' conflicts with existing wildcard ':id' in existing prefix '/users/:id'
✓ 根上的参数与静态 注册 [/about /:lang] → 路由表 [/about /:lang]
✓ 根上的参数与静态(反序) 注册 [/:lang /about] → 路由表 [/about /:lang]
✗ 根上的 :a 与 :b/x ':b' in new path '/:b/x' conflicts with existing wildcard ':a' in existing prefix '/:a'
✓ 参数与更长的静态路径 注册 [/a/:id /a/b/c] → 路由表 [/a/b/c /a/:id]
✗ 通配符 :id 与 *p 同位置 '*p' in new path '/a/*p' conflicts with existing wildcard ':id' in existing prefix '/a/:id'
✗ *p 与静态 /a/b 同位置 '/b' in new path '/a/b' conflicts with existing wildcard '/*p' in existing prefix '/a/*p'
✗ 同一路由注册两次 handlers are already registered for path '/ping'
✗ 同一路由带不同参数名 handlers are already registered for path '/u/:id'
✗ 参数名为空 /u/: wildcards must be named with a non-empty name in path '/u/:'
✗ 一段里两个通配符 only one wildcard per path segment is allowed, has: ':a:b' in path '/u/:a:b'
✗ *p 后面还有路径 catch-all routes are only allowed at the end of the path in path '/s/*p/x'
✗ 没有斜杠的 *p no / before catch-all in path '/s*p'
✓ 路径不以 / 开头 注册 [users] → 路由表 [/users]
✓ 转义冒号 /u/\:id 注册 [/u/\:id] → 路由表 [/u/\:id]可以共存的:静态与 :id(两个顺序)、:id 后接不同静态段、根上的 /about 与 /:lang、/a/:id 与更长的 /a/b/c。会 panic 的:同一位置换一个通配符(名字不同也算)、通配符种类不同、与 catch-all 同位置的静态路径、同一路由注册两次、不合法的通配符写法。
另外 RouterGroup.GET("users") 虽然没有开头的斜杠,路由表里登记的是 /users——group 会把路径和 basePath 拼起来。
第一条路由注册完成,下一行会 panic
recover: '*p' in new path '/a/*p' conflicts with existing wildcard ':id' in existing prefix '/a/:id'
github.com/gin-gonic/gin.(*node).addRoute
github.com/gin-gonic/gin.(*Engine).addRoute
github.com/gin-gonic/gin.(*RouterGroup).handle
github.com/gin-gonic/gin.(*RouterGroup).GET(...)冲突检查在 addRoute 里,所以问题在启动阶段就暴露:栈顶是 (*node).addRoute,往上是 Engine.addRoute、RouterGroup.handle、RouterGroup.GET。这是个好事——路由冲突不会拖到线上流量到来才发现。
/files/a/b → 命中 /files/a/b Params=[]
/files/a/c → 命中 /files/:id/c Params=[{id a}]
/files/a → 命中 /files/:id Params=[{id a}]
/files/new → 命中 /files/new Params=[]
/files/newer → 命中 /files/:id Params=[{id newer}]
/files/x/c → 命中 /files/:id/c Params=[{id x}]
/files/new/c → 命中 /files/:id/c Params=[{id new}]
/files/a/d → 404静态节点优先:先在 indices 里找下一个字节,找到就进去;如果后面走不通,Gin 会回到之前记下的分叉点(源码里叫 skippedNodes),改走通配符子节点。所以 /files/a/c 虽然先进入了 /files/a/b 的静态分支,最终命中的是 /files/:id/c,id=a。没有任何路由匹配的 /files/a/d 才是 404。
tree.go: 232 | "' conflicts with existing wildcard '" + n.path +
tree.go: 355 | "' conflicts with existing path segment '" + pathSeg +
tree.go: 243 | panic("handlers are already registered for path '" + fullPath + "'")
tree.go: 298 | panic("only one wildcard per path segment is allowed, has: '" +
tree.go: 304 | panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
tree.go: 345 | panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
tree.go: 363 | panic("no / before catch-all in path '" + fullPath + "'")
gin.go: 365 | assert1(path[0] == '/', "path must begin with '/'")
gin.go: 366 | assert1(method != "", "HTTP method can not be empty")
gin.go: 367 | assert1(len(handlers) > 0, "there must be at least one handler")冲突与格式错误的文案集中在 tree.go 的 addRoute 与 insertChild;路径要以 / 开头、方法不能为空、至少一个 handler 是 gin.go 里的断言。