Golang 1.22中net/http子路由末尾斜杠重定向问题
问题:Golang net/http子路由兼容末尾斜杠问题
我用Golang标准库net/http实现简易API的子路由,遇到一个问题:用户注册路由是POST /v1/users/,但发送POST /v1/users请求时会返回404错误,必须加末尾斜杠才能正常访问。我希望这个路由能同时兼容有无末尾斜杠的请求,求修复方案。另外根据官方文档,POST /v1/users应该被重定向到POST /v1/users/,这个理解对吗?
代码示例
main.go
package main import ( "log/slog" "net/http" "os" routes "go-url-shortener-api/api/v1" "go-url-shortener-api/internal/config" "go-url-shortener-api/pkg/logger" ) func main() { config, err := config.LoadConfig() if err != nil { slog.Error("Error loading config", "error", err) os.Exit(1) } router := routes.RegisterV1Routes() server := http.Server{ Addr: ":" + config.Port, Handler: router, } logger.Logger.Info("Starting server", slog.String("port", config.Port)) server.ListenAndServe() }
api/v1.go
package routes import "net/http" func RegisterV1Routes() http.Handler { router := http.NewServeMux() router.Handle("/v1/", http.StripPrefix("/v1", RegisterRoutes())) return router }
api/routes.go
package routes import "net/http" func RegisterRoutes() http.Handler { router := http.NewServeMux() router.Handle("/users/", http.StripPrefix("/users", RegisterUserRoutes())) return router }
api/user_routes.go
package routes import "net/http" func RegisterUserRoutes() http.Handler { router := http.NewServeMux() router.HandleFunc("POST /", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<p>POST /users</p>")) }) router.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<p>POST /users/login</p>")) }) router.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<p>POST /users/logout</p>")) }) return router }
解答
关于重定向的理解
不对。Golang的http.ServeMux默认仅对GET请求的无末尾斜杠路径执行重定向(到带斜杠的版本),POST、PUT等其他方法的请求不会自动触发重定向,直接返回404,这是标准库的既定行为。
修复方案
方案1:同时注册带/不带斜杠的路由
在对应的ServeMux中添加无斜杠路径的映射,让其指向同一个处理器。修改api/routes.go:
func RegisterRoutes() http.Handler { router := http.NewServeMux() userHandler := http.StripPrefix("/users", RegisterUserRoutes()) // 同时注册带斜杠和不带斜杠的路由 router.Handle("/users/", userHandler) router.Handle("/users", userHandler) return router }
此方法直接让两种路径都匹配到目标处理器,无需客户端跳转。
方案2:全局重定向中间件
如果需要批量处理所有类似场景,可以编写中间件,自动将无末尾斜杠的非GET请求重定向到带斜杠的路径(用307状态码保留原请求方法):
import "strings" func TrailingSlashRedirect(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 排除根路径、已带斜杠的路径、以及带文件后缀的路径 if r.URL.Path != "/" && !strings.HasSuffix(r.URL.Path, "/") && !strings.Contains(r.URL.Path, ".") { newPath := r.URL.Path + "/" // 307临时重定向,保持原请求方法 http.Redirect(w, r, newPath, http.StatusTemporaryRedirect) return } next.ServeHTTP(w, r) }) }
然后在main.go中为服务器绑定中间件:
router := routes.RegisterV1Routes() server := http.Server{ Addr: ":" + config.Port, Handler: TrailingSlashRedirect(router), }
此方案全局生效,无需逐个修改路由注册。
方案3:直接在子路由中注册双路径
在user_routes.go中为注册逻辑添加无斜杠的路径匹配:
func RegisterUserRoutes() http.Handler { router := http.NewServeMux() registerHandler := func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<p>POST /users</p>")) } // 同时处理带斜杠和不带斜杠的根路径 router.HandleFunc("POST /", registerHandler) router.HandleFunc("POST", registerHandler) // 其他路由保持不变 router.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<p>POST /users/login</p>")) }) router.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<p>POST /users/logout</p>")) }) return router }
适合路由层级较少的场景,多层嵌套时推荐前两种方案。
内容的提问来源于stack exchange,提问作者Rohit Patil
相关产品推荐
相关产品推荐

