Hello,
I'm encountering unexpected behavior with net/http routing in Go 1.24.1 (amd64, ubuntu linux) when using http.StripPrefix to delegate to a nested http.ServeMux which uses the Go 1.22+ METHOD /path routing syntax.
Instead of the nested ServeMux executing its registered handlers for the stripped path, it consistently returns a 301 Moved Permanently redirect to the stripped path itself.
```go
package main
import (
"fmt"
"log"
"net/http"
"runtime"
)
func handleInnerTest(w http.ResponseWriter, r *http.Request) {
log.Printf("HANDLER HIT: handleInnerTest (GET /test) | Received Path: %s\n", r.URL.Path)
fmt.Fprintln(w, "OK - GET /test")
}
func handleInnerLogin(w http.ResponseWriter, r *http.Request) {
log.Printf("HANDLER HIT: handleInnerLogin (POST /login) | Received Path: %s\n", r.URL.Path)
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed (Handler expected POST)", http.StatusMethodNotAllowed)
return
}
fmt.Fprintln(w, "OK - POST /login")
}
func main() {
log.Printf("Go Version: %s\n", runtime.Version())
innerMux := http.NewServeMux()
innerMux.HandleFunc("GET /test", handleInnerTest)
innerMux.HandleFunc("POST /login", handleInnerLogin)
innerMux.HandleFunc("GET /api2/test2", handleInnerTest)
outerMux := http.NewServeMux()
// This should remove "/api/" before innerMux sees the request.
outerMux.Handle("/api/", http.StripPrefix("/api/", innerMux))
outerMux.Handle("/api2/", innerMux)
outerMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Printf("HANDLER HIT: Outer Root Handler | Received Path: %s\n", r.URL.Path)
http.NotFound(w, r)
})
port := ":8090"
log.Printf("Starting server on %s...\n", port)
log.Println("----\nEXPECTED BEHAVIOR:")
log.Println(" GET /api/test -> 200 OK ('OK - GET /test')")
log.Println(" GET /api/nonexistent -> 404 Not Found (from innerMux)")
log.Println(" POST /api/login -> 200 OK ('OK - POST /login')")
log.Println("----")
log.Println("Run tests like:")
log.Println(" curl -v http://localhost:8090/api/test") // is HTTP/1.1 301 Moved Permanently && no log print
log.Println(" curl -v http://localhost:8090/api/nonexistent") // is HTTP/1.1 301 Moved Permanently && no log print
log.Println(" curl -v -X POST http://localhost:8090/api/login") // is HTTP/1.1 301 Moved Permanently && no log print
log.Println(" curl -v http://localhost:8090/test/") // is HTTP/1.1 404 Not Found && prints HANDLER HIT: Outer Root Handler | Received Path: /test/
log.Println(" curl -v http://localhost:8090/api2/test2") // is HTTP/1.1 200 OK && prints HANDLER HIT: handleInnerTest (GET /test) | Received Path: /api2/test2
log.Println("----")
err := http.ListenAndServe(port, outerMux)
if err != nil {
log.Fatalf("Server failed: %v\n", err)
}
}
```
It seems counter-intuitive that after http.StripPrefix modifies the path, the inner ServeMux doesn't seem to use that stripped path to match its handlers, instead issuing a redirect, like curl -v http://localhost:8090/api/test -> `<a href="/test">Moved Permanently</a>`
Have I misunderstood how these components should interact?
Thanks for any insights.