/dir 和 /dir/ 在 Gin 里是两条不同的路由,默认会互相重定向。这篇用状态码和 Location 把规则列清楚:什么时候是 301、什么时候是 307、query 和反向代理的前缀怎么处理,以及真实的 HTTP 客户端遇到这种重定向会怎样。
默认值 RedirectTrailingSlash = true ,RedirectFixedPath = false ,HandleMethodNotAllowed = false
GET /dir → 301 Location: /dir/
GET /dir/ → 200
GET /file → 200
GET /file/ → 301 Location: /file
GET /users/7 → 301 Location: /users/7/
GET /users/7/ → 200
GET /dir?x=1&y=2 → 301 Location: /dir/?x=1&y=2
GET /file/?x=1 → 301 Location: /file?x=1Engine 的默认值是 RedirectTrailingSlash=true,RedirectFixedPath=false,HandleMethodNotAllowed=false。注册 /dir/ 之后请求 /dir 会 301 到 /dir/,反过来 /file 注册了就请求 /file/ 则 301 去掉斜杠。参数路由同样适用,query 保持原样。
gin.go 的 redirectRequest(Gin v1.12.0):
820 | func redirectRequest(c *Context) {
821 | req := c.Request
822 | rPath := req.URL.Path
823 | rURL := req.URL.String()
824 |
825 | code := http.StatusMovedPermanently // Permanent redirect, request with GET method
826 | if req.Method != http.MethodGet {
827 | code = http.StatusTemporaryRedirect
828 | }
829 | debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
830 | http.Redirect(c.Writer, req, rURL, code)
831 | c.writermem.WriteHeaderNow()
832 | }
GET /x → 301 Location: /x/
HEAD /x → 307 Location: /x/
POST /x → 307 Location: /x/
PUT /x → 307 Location: /x/
PATCH /x → 307 Location: /x/
DELETE /x → 307 Location: /x/
OPTIONS /x → 307 Location: /x/
CONNECT /x → 404状态码只由方法决定:GET 用 301,其他方法用 307。这也包括 HEAD——HEAD 拿到的是 307。307 要求客户端用相同方法和 body 重发,所以 POST 到 /upload、而路由注册成 /upload/ 时,不会像 302 那样被降级成 GET。CONNECT 完全不做重定向。
GET /dir → 301 Location: /dir/
--- RedirectTrailingSlash = false ---
GET /dir → 404
--- 带 X-Forwarded-Prefix 头(反向代理挂在子路径下)---
GET /dir [X-Forwarded-Prefix: "/app"] → 301 Location: /app/dir/
GET /dir [X-Forwarded-Prefix: "/app/"] → 301 Location: /app/dir/
GET /dir [X-Forwarded-Prefix: "//evil.com"] → 301 Location: /evilcom/dir/
GET /dir [X-Forwarded-Prefix: "/a b;c"] → 301 Location: /abc/dir/关掉 RedirectTrailingSlash 后,结尾斜杠不一致就是普通的 404。挂在反向代理子路径下时,代理设置的 X-Forwarded-Prefix 会拼进 Location,Gin 先用 sanitizePathChars 只保留字母、数字、斜杠和连字符,所以带 . 的 //evil.com 变成 /evilcom/,不会产生开放重定向。
客户端被重定向到 /upload/,方法 POST
最终响应:200 "stored"
服务端的 handler 看到:[POST /upload/ body="hello"]
不跟随重定向时:301,Location=/old/Go 的 http.Client 遇到 307 会用同一方法重发,body 也被带上:服务端 handler 只被调用一次,拿到完整的 hello。GET 的 301 则被客户端当作普通重定向。是否缓存 301、其他语言客户端对 307 的处理不一定相同,本文没有验证。