客户端拼出了 /users//42 这样带双斜杠的 URL,Gin 默认给 404。RemoveExtraSlash 能让它直接命中,但它不只处理双斜杠。这篇列出打开前后的差别、*path 的值被怎样改写、和 RedirectFixedPath 的区别,以及代价。
=== 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 里允许双斜杠),就不能打开这个开关。
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 次内存分配。