Description
Go version
go 1.24.1
Output of go env
in your module/workspace:
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/hb/1n61v0yj11zc1rnwkbm974tm0000gn/T/go-build1330795007=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/***/go.mod'
GOMODCACHE='/Users/**/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/**/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/**/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.1.darwin-arm64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/***/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/chin/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.1.darwin-arm64/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.1'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
ServeMux Pattern Matching Behavior with Grouped Routes
Problem Description
I'm trying to organize my HTTP routes into logical groups using nested ServeMux
, but I'm encountering unexpected behavior with pattern matching.
API Structure
I have the following API endpoints:
GET /v1/users
POST /v1/users
GET /v1/users/attributes
GET /v1/users/{id}
Implementation Attempt
I'm trying to organize the code by grouping user-related handlers:
func mainHandler() {
mux := http.NewServeMux()
userHandler := userHandler(mux)
}
func userHandler(mux *http.ServeMux) {
userMux := http.NewServeMux()
userMux.Handle("GET /users", getUserLogic())
userMux.Handle("POST /users", postUserLogic())
userMux.Handle("GET /users/attributes", getUserAttributesLogic())
userMux.Handle("GET /users/{id}", getUserByIdLogic())
}
What did you see happen?
Observed Behavior
I've tried three different approaches to register the user routes:
Scenario 1: Without trailing slash
mux.Handle("/v1/users", userMux)
Result: Only matches /v1/users
endpoints, returns 404 for /v1/users/attributes
and /v1/users/{id}
Scenario 2: With trailing slash
mux.Handle("/v1/users/", userMux)
Result: Only matches /v1/users/attributes
and /v1/users/{id}
, returns 404 for /v1/users
endpoints
Scenario 3: Using StripPrefix
mux.Handle("/v1/users/", http.StripPrefix("/v1/users", userMux))
With modified handlers:
userMux.Handle("GET ", getUserLogic())
userMux.Handle("POST ", postUserLogic())
userMux.Handle("GET /attributes", getUserAttributesLogic())
userMux.Handle("GET /{id}", getUserByIdLogic())
Result: Error - "Pattern matching fails because host/path missing /"
What did you expect to see?
Question
Why does ServeMux
exhibit this behavior with grouped routes, and is there a way to achieve the desired routing structure without requiring trailing slashes? The current pattern matching seems to force us to choose between handling root-level routes (/v1/users
) OR nested routes (/v1/users/*
), but not both simultaneously.
I'd prefer not to add trailing slashes to all my routes. Is this a limitation of ServeMux
, or am I missing something in the implementation?