bigywater.tech
Gin · 7

:id 与 *path 的匹配差异

:id 和 *path 看起来只是“少一段”和“多一段”的区别,但它们的节点类型、冲突规则、值的格式都不一样。这篇用同一批请求打在两种路由上,再把能不能共存的十种组合逐个注册一遍,把 panic 原文列出来。

8 / 10
十种注册组合里会 panic 的个数
"/a/b.txt"
*p 取到的值,开头带斜杠
%2F
默认 404;开 UseRawPath 后 name="dir/a.txt"

:id 与 *p 各自匹配什么

实测 · 本机执行输出
    GET  /u/1                       → 200 "id=\"1\""
    GET  /u/1/2                     → 404 "404 page not found"
    GET  /u/                        → 404 "404 page not found"
    GET  /u                         → 404 "404 page not found"
    GET  /u/a%20b                   → 200 "id=\"a b\""
    GET  /u/中文                      → 200 "id=\"中文\""
    GET  /s/                        → 200 "p=\"/\""
    GET  /s/a                       → 200 "p=\"/a\""
    GET  /s/a/b/c                   → 200 "p=\"/a/b/c\""
    GET  /s/a/b/                    → 200 "p=\"/a/b/\""
    GET  /s                         → 301 "<a href=\"/s/\">Moved Permanently</a>.\n\n" Location=/s/
    GET  /s//x                      → 200 "p=\"//x\""

:id 只匹配到下一个斜杠为止,而且不能为空:/u/1/2、/u/ 都是 404。*p 把斜杠之后的所有内容当成一个值,哪怕只是一个 /。URL 里的 %20 和中文在取值时已经解码。

注意 /s 没有匹配而是被 301 重定向到 /s/:*p 前面的斜杠是路由的一部分。

冲突:什么情况下注册会 panic

实测 · 本机执行输出
    [:id 与 *p 同位置] panic: '*p' in new path '/x/*p' conflicts with existing wildcard ':id' in existing prefix '/x/:id'
    [*p 与 :id 同位置(顺序反过来)] panic: '/:id' in new path '/x/:id' conflicts with existing wildcard '/*p' in existing prefix '/x/*p'
    [:id 与 :name 同位置] panic: ':name' in new path '/x/:name' conflicts with existing wildcard ':id' in existing prefix '/x/:id'
    [:id 与 :name 后面还有静态段] panic: ':name' in new path '/x/:name/b' conflicts with existing wildcard ':id' in existing prefix '/x/:id'
    [同名 :id 后面不同的静态段] 注册成功: [/x/:id/a /x/:id/b]
    [*p 之后还有路径] panic: catch-all routes are only allowed at the end of the path in path '/x/*p/more'
    [*p 与静态 /x/ 同位置] panic: '/' in new path '/x/' conflicts with existing wildcard '/*p' in existing prefix '/*p'
    [*p 与静态 /x/a 同位置] panic: '/a' in new path '/x/a' conflicts with existing wildcard '/*p' in existing prefix '/x/*p'
    [静态 /x/a 先注册、*p 后注册] panic: catch-all wildcard '*p' in new path '/x/*p' conflicts with existing path segment 'a' in existing prefix '/x/a'
    [:id 与静态 /x/new 共存] 注册成功: [/x/new /x/:id]

同一位置只能有一种通配符::id 与 *p 不能并存,:id 与 :name 也不行(即使它们后面跟着不同的静态段)。catch-all 只能出现在路径末尾,而且不能与同一位置的静态路径并存,先后顺序都一样。能共存的是:同名的 :id 后面接不同的静态段,以及静态路径与 :id 并存(下一篇会逐个验证)。

*p 的值与路径穿越

实测 · 本机执行输出
    GET  /files/a/b.txt             → 200 "p=\"/a/b.txt\" 去掉开头斜杠=\"a/b.txt\""
    GET  /files/                    → 200 "p=\"/\" 去掉开头斜杠=\"\""
    GET  /files                     → 301 "<a href=\"/files/\">Moved Permanently</a>.\n\n" Location=/files/
    GET  /files/a//b                → 200 "p=\"/a//b\" 去掉开头斜杠=\"a//b\""
    GET  /files/%2e%2e/x            → 200 "p=\"/../x\" 去掉开头斜杠=\"../x\""
    GET  /files/../x                → 200 "p=\"/../x\" 去掉开头斜杠=\"../x\""

*p 的值总是以 / 开头,所以用它拼文件路径时常写 p[1:],并且要处理空串。/files/../x 和 /files/%2e%2e/x 的 p 都是 "/../x"——Gin 的路由不会清理或拒绝 ..,把它拼进文件路径之前必须自己处理。

带斜杠的参数

实测 · 本机执行输出
    GET  /files/a.txt               → 200 "name=\"a.txt\""
    GET  /files/dir/a.txt           → 404 "404 page not found"
    GET  /files/dir%2Fa.txt         → 404 "404 page not found"
    --- UseRawPath=true + UnescapePathValues=true 之后 ---
    GET  /files/a.txt               → 200 "name=\"a.txt\""
    GET  /files/dir/a.txt           → 404 "404 page not found"
    GET  /files/dir%2Fa.txt         → 200 "name=\"dir/a.txt\""

:name 永远是一段。默认情况下 Gin 用已经解码的 URL.Path 匹配,%2F 解码成斜杠后就变成了两段,所以 404;把 UseRawPath 和 UnescapePathValues 都设为 true,就改用 RawPath 匹配、再对取出的值解码,这时 dir%2Fa.txt 能命中,值是 dir/a.txt。

分步演示

:id 与 *path
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew)/ Gin v1.12.0(go.mod 里 require,源码在模块缓存里);用 go run / go test 直接编译运行,需要库时经代理从 proxy.golang.org 下载;HTTP 实验用 httptest 或本机回环端口(9700 以上);计时用 time.Now / testing.B,数字每次运行会有小幅抖动。请求都用 httptest 直接调用 Engine.ServeHTTP,没有经过真实的 http.Server(所以没有 http.ServeMux 的路径清理);每种情形各一次
    • 没有验证:真实 http.Server 对 %2F、.. 的预处理(我只用 httptest 构造请求);UseRawPath 对其它特殊字符的影响;catch-all 值里的非 UTF-8 字节;Gin 其他版本里冲突规则的差异(只测了 v1.12.0)
    京ICP备2026067502号-1京公网安备11010502063487号