Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"slices"
"strings"
"sync"
)
Expand Down Expand Up @@ -516,8 +517,12 @@ func (mx *Mux) updateRouteHandler() {

// methodNotAllowedHandler is a helper function to respond with a 405,
// method not allowed. It sets the Allow header with the list of allowed
// methods for the route.
// methods for the route. It deduplicates methods that may appear multiple
// times when overlapping wildcard routes match the same path.
func methodNotAllowedHandler(methodsAllowed ...methodTyp) func(w http.ResponseWriter, r *http.Request) {
slices.Sort(methodsAllowed)
methodsAllowed = slices.Compact(methodsAllowed)

return func(w http.ResponseWriter, r *http.Request) {
for _, m := range methodsAllowed {
w.Header().Add("Allow", reverseMethodMap[m])
Expand Down
33 changes: 33 additions & 0 deletions mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,39 @@ func TestMethodNotAllowed(t *testing.T) {
})
}

func TestMethodNotAllowedDuplicateAllow(t *testing.T) {
r := NewRouter()

// Register multiple overlapping wildcard routes with the same method.
// When a request uses an unsupported method, the Allow header should
// list each method only once, not once per matching route.
r.Post("/article/1-2-3", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
r.Post("/article/{a}", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
r.Post("/article/{b}-{c}", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
r.Post("/article/{b}-{c}-{d}", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})

ts := httptest.NewServer(r)
defer ts.Close()

resp, _ := testRequest(t, ts, "GET", "/article/1-2-3", nil)
if resp.StatusCode != 405 {
t.Fatalf("expected 405, got %d", resp.StatusCode)
}

allowedMethods := resp.Header.Values("Allow")
if len(allowedMethods) != 1 || allowedMethods[0] != "POST" {
t.Fatalf("Allow header should contain exactly one POST entry, got: %v", allowedMethods)
}
}

func TestMuxNestedMethodNotAllowed(t *testing.T) {
r := NewRouter()
r.Get("/root", func(w http.ResponseWriter, r *http.Request) {
Expand Down