bigywater.tech
Gin · 19

RemoveExtraSlash

客户端拼出了 /users//42 这样带双斜杠的 URL,Gin 默认给 404。RemoveExtraSlash 能让它直接命中,但它不只处理双斜杠。这篇列出打开前后的差别、*path 的值被怎样改写、和 RedirectFixedPath 的区别,以及代价。

404 → 200
RemoveExtraSlash 打开后 /users//42 的状态码
"/b"
打开后 /f/a/../b 的 *p 值——.. 被解析掉了
0 → 1 次
带 // 的请求每次多出的内存分配,规整的路径不分配

就地命中

实测 · 本机执行输出
    === RemoveExtraSlash = false ===
      /users/42      → 200 "id=42"
      /users//42     → 404 "404 page not found"
      //users/42     → 404 "404 page not found"
      /users/42//    → 404 "404 page not found"
      /a//b          → 404 "404 page not found"
      /a/./b         → 404 "404 page not found"
      /a/x/../b      → 404 "404 page not found"
      /f/x//y        → 200 "p=\"/x//y\""
      /users/42/     → 301 "<a href=\"/users/42\">Moved Permanently</a>." Location=/users/42
    === RemoveExtraSlash = true ===
      /users/42      → 200 "id=42"
      /users//42     → 200 "id=42"
      //users/42     → 200 "id=42"
      /users/42//    → 301 "<a href=\"/users/42/\">Moved Permanently</a>." Location=/users/42/
      /a//b          → 200 "a/b"
      /a/./b         → 200 "a/b"
      /a/x/../b      → 200 "a/b"
      /f/x//y        → 200 "p=\"/x/y\""
      /users/42/     → 301 "<a href=\"/users/42\">Moved Permanently</a>." Location=/users/42

打开 RemoveExtraSlash 之后,/users//42、//users/42、/a/./b、/a/x/../b 都直接命中,响应码是 200,不会有重定向。只有结尾斜杠的规则没变:/users/42// 仍然要先 301 到 /users/42/ 再 301 到 /users/42。

被清理的是匹配用的路径

实测 · 本机执行输出
    /f/a//b        → 200 p="/a/b"  URL.Path="/f/a//b"  FullPath="/f/*p"
    /f//a/b        → 200 p="/a/b"  URL.Path="/f//a/b"  FullPath="/f/*p"
    /f/a/../b      → 200 p="/b"  URL.Path="/f/a/../b"  FullPath="/f/*p"
    /f/a/./b/      → 200 p="/a/b/"  URL.Path="/f/a/./b/"  FullPath="/f/*p"

c.Request.URL.Path 保持请求里的原样,但 c.Param 取到的值来自清理后的路径:*p 里的 // 被合并,.. 与 . 被解析掉(/f/a/../b 的 p 是 /b)。如果业务依赖 *p 里的原始字节(比如对象存储的 key 里允许双斜杠),就不能打开这个开关。

与 RedirectFixedPath 的区别

实测 · 本机执行输出
    RemoveExtraSlash=false RedirectFixedPath=false:  /users//42 404  /dir 301→/dir/  //dir/ 404  /dir// 301→/dir/
    RemoveExtraSlash=true  RedirectFixedPath=false:  /users//42 200  /dir 301→/dir/  //dir/ 200  /dir// 200
    RemoveExtraSlash=false RedirectFixedPath=true :  /users//42 301→/users/42  /dir 301→/dir/  //dir/ 301→/dir/  /dir// 301→/dir/
    RemoveExtraSlash=true  RedirectFixedPath=true :  /users//42 200  /dir 301→/dir/  //dir/ 200  /dir// 200

两个开关都能“修好”双斜杠,方式不同:RemoveExtraSlash 直接在服务端用清理后的路径匹配,客户端看不到变化;RedirectFixedPath 让客户端 301/307 到规范路径(还能修正大小写)。两个都开时,前者先生效,请求直接 200。

规则与代价

实测 · 本机执行输出
  path.go 的 cleanPath 注释(Gin v1.12.0):
      10 | // cleanPath is the URL version of path.Clean, it returns a canonical URL path
      11 | // for p, eliminating . and .. elements.
      12 | //
      13 | // The following rules are applied iteratively until no further processing can
      14 | // be done:
      15 | //  1. Replace multiple slashes with a single slash.
      16 | //  2. Eliminate each . path name element (the current directory).
      17 | //  3. Eliminate each inner .. path name element (the parent directory)
      18 | //     along with the non-.. element that precedes it.
      19 | //  4. Eliminate .. elements that begin a rooted path:
      20 | //     that is, replace "/.." by "/" at the beginning of a path.
      21 | //
      22 | // If the result of this process is an empty string, "/" is returned.
    RemoveExtraSlash=false /users/42    0 allocs/请求
    RemoveExtraSlash=false /users//42   0 allocs/请求
    RemoveExtraSlash=true  /users/42    0 allocs/请求
    RemoveExtraSlash=true  /users//42   1 allocs/请求

清理规则与标准库 path.Clean 相同:合并重复斜杠、删除 .、解析 ..、根上的 /.. 变成 /。规整的路径不需要分配;带 // 的路径每次请求多 1 次内存分配。

分步演示

RemoveExtraSlash
本步的对象

    本文用到的工具与边界

    • 本机 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 直接调用 ServeHTTP;分配次数用 testing.AllocsPerRun(2000 次平均);每种情形各一次
    • 没有验证:耗时(只统计了分配次数,没有测纳秒);与 UseRawPath、UseEscapedPath 同时打开时的交互;包含 %2F 的路径在 cleanPath 前后的行为;Group 前缀里带多余斜杠的路由(那是注册时的 path.Join 处理的,不受这个开关影响,见 Group 一篇)
    京ICP备2026067502号-1京公网安备11010502063487号