Browse Source

Set the Allow header when ErrMethodMismatch is set

This changes RouteMatch so it contains a slice of the methods that would
have been accepted. That slice is then used to populate the Allow header
accordingly.

This makes the default behavior of mux when returning 405 Method Not
Allowed compliant with [RFC 7231§6.5.5][RFC7231].

[RFC7231]: https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.5
pull/652/head
Grégoire Duchêne 4 years ago
parent
commit
38b9e10ecf
  1. 19
      mux.go
  2. 7
      mux_test.go
  3. 7
      route.go

19
mux.go

@ -11,6 +11,7 @@ import ( @@ -11,6 +11,7 @@ import (
"net/http"
"path"
"regexp"
"strings"
)
var (
@ -202,7 +203,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -202,7 +203,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
if handler == nil && match.MatchErr == ErrMethodMismatch {
handler = methodNotAllowedHandler()
handler = methodNotAllowedHandler(match.AllowedMethods)
}
if handler == nil {
@ -417,6 +418,10 @@ type RouteMatch struct { @@ -417,6 +418,10 @@ type RouteMatch struct {
// It is set to ErrMethodMismatch if there is a mismatch in
// the request method and route method
MatchErr error
// AllowedMethods contains the list of methods allowed by a route when
// MatchErr is set to ErrMethodMismatch.
AllowedMethods []string
}
type contextKey int
@ -598,11 +603,11 @@ func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]s @@ -598,11 +603,11 @@ func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]s
return true
}
// methodNotAllowed replies to the request with an HTTP status code 405.
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}
// methodNotAllowedHandler returns a simple request handler
// that replies to each request with a status code 405.
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
func methodNotAllowedHandler(allowed []string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Allow", strings.Join(allowed, ","))
w.WriteHeader(http.StatusMethodNotAllowed)
})
}

7
mux_test.go

@ -2052,6 +2052,9 @@ func TestNoMatchMethodErrorHandler(t *testing.T) { @@ -2052,6 +2052,9 @@ func TestNoMatchMethodErrorHandler(t *testing.T) {
if resp.Code != http.StatusMethodNotAllowed {
t.Errorf("Expecting code %v", 405)
}
if hdr := resp.Header().Get("Allow"); hdr != "GET,POST" {
t.Errorf(`Expected Allow header to be "GET,POST" (got %q)`, hdr)
}
// Add matching route
r.HandleFunc("/", func1).Methods("PUT")
@ -2721,7 +2724,6 @@ func TestMethodNotAllowed(t *testing.T) { @@ -2721,7 +2724,6 @@ func TestMethodNotAllowed(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }
router := NewRouter()
router.HandleFunc("/thing", handler).Methods(http.MethodGet)
router.HandleFunc("/something", handler).Methods(http.MethodGet)
w := NewRecorder()
req := newRequest(http.MethodPut, "/thing")
@ -2731,6 +2733,9 @@ func TestMethodNotAllowed(t *testing.T) { @@ -2731,6 +2733,9 @@ func TestMethodNotAllowed(t *testing.T) {
if w.Code != http.StatusMethodNotAllowed {
t.Fatalf("Expected status code 405 (got %d)", w.Code)
}
if hdr := w.Header().Get("Allow"); hdr != http.MethodGet {
t.Fatalf(`Expected Allow header to be "GET" (got %q)`, hdr)
}
}
type customMethodNotAllowedHandler struct {

7
route.go

@ -44,12 +44,14 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { @@ -44,12 +44,14 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
}
var matchErr error
var allowedMethods []string
// Match everything.
for _, m := range r.matchers {
if matched := m.Match(req, match); !matched {
if _, ok := m.(methodMatcher); ok {
if m, ok := m.(methodMatcher); ok {
matchErr = ErrMethodMismatch
allowedMethods = m
continue
}
@ -71,6 +73,9 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool { @@ -71,6 +73,9 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
if matchErr != nil {
match.MatchErr = matchErr
if matchErr == ErrMethodMismatch {
match.AllowedMethods = allowedMethods
}
return false
}

Loading…
Cancel
Save