-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix #271: Returning 405 instead of 404 when request has method mismatch with route #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
mux.go
Outdated
@@ -545,3 +555,12 @@ 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this needs to be exported
mux.go
Outdated
|
||
// MethodNotAllowedHandler returns a simple request handler | ||
// that replies to each request with a status code 405. | ||
func MethodNotAllowedHandler() http.Handler { return http.HandlerFunc(MethodNotAllowed) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably doesn't need to be exported either
mux.go
Outdated
|
||
// MethodMismatch flag is set to true if a route is matched but there | ||
// is a mismatch in the request method and route method | ||
MethodMismatch bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be better to have a MatchError error
field instead. Then there could be a ErrMethodMismatch
value used there in the case of methodMatcher, and then it could be extended with other error types in the future.
Overall approach looks good to me, I left some comments about possible improvements. What do you think? |
All changes suggested by you make perfect sense @kisielk . I will make the change ASAP. |
@kisielk I have made the changes. Please let me know if anymore improvement is required. |
@elithrar can you give this a second look ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See suggested changes & commentary.
mux.go
Outdated
@@ -13,6 +13,10 @@ import ( | |||
"strings" | |||
) | |||
|
|||
var ( | |||
ErrMethodMismatch = errors.New("Method is Not Allowed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we don't use the text for this, we should either:
- Lowercase this as convention - e.g.
method is not allowed
- Use an enum to reflect the type of error it is.
String errors are inflexible and fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will Change it to lower case.
mux_test.go
Outdated
@@ -1871,3 +1871,29 @@ func newRequest(method, url string) *http.Request { | |||
} | |||
return req | |||
} | |||
|
|||
func TestNoMatchMethodErrors(t *testing.T) { | |||
func1 := func(w http.ResponseWriter, r *http.Request) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call it handler
old_test.go
Outdated
} else { | ||
t.Errorf("Should not match.") | ||
} | ||
t.Errorf("Match Expectation: %v\nGot: %v\nRequest: %v %v", shouldMatch, matched, request.Method, url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow the format used elsewhere in this file - e.g. Expected: %v Got: %v
// MatchErr is set to appropriate matching error | ||
// It is set to ErrMethodMismatch if there is a mismatch in | ||
// the request method and route method | ||
MatchErr error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't feel right: if you provide an error
here you can't are extremely limited in how you can customize 405 responses.
Should we not offer a MethodNotAllowedHandler
field on the Router
akin to NotFoundHandler
that a package user can customize? See https://godoc.org/github.com/gorilla/mux#Router
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this is not to customize the response, but a way for the match to signal to the router the reason why it failed to match anything. The other option is to have a flag on RouteMatch specifically for the method not matching, but that feels less extensible to me.
I agree that the router should ultimately have a way to customize the handler, but that's a separate bit of logic than this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that router should have MethodNotAllowedHandler. We can default to local methodNotAllowedHandler
if it is not set, same as we do with http.NotFoundHandler
. Should we make those change in a current fix #271 is the question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maknahar If you can add that as well, we can merge - that way it's feature-complete :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
@elithrar I have added custom |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor documentation change, but otherwise LGTM. @kisielk for final approval/merge.
mux.go
Outdated
@@ -39,6 +43,10 @@ func NewRouter() *Router { | |||
type Router struct { | |||
// Configurable Handler to be used when no route matches. | |||
NotFoundHandler http.Handler | |||
|
|||
// Configurable Handler to be used request has method mismatch with route |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to "Configurable Handler to be used when the request method does not match the route."
LGTM after the requested change. |
@kisielk I have done the requested change. |
Thanks a lot! |
t.Errorf("Expecting code %v", 405) | ||
} | ||
|
||
//Add matching route now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor style nit. Unlike all others, this comment is missing a space between //
and the text.
This commit breaks the possibility to have a "catch-all" route, as it will always be called instead of any specific route with a different method. Consider the following code: package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
get := router.Methods("GET").Subrouter()
post := router.Methods("POST").Subrouter()
// Specific POST route without parameters
post.HandleFunc("/something", func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintln(rw, "post")
})
// Catch-all route that should match any GET request, and only GET requests
get.HandleFunc("/{anything}", func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintln(rw, "get")
})
http.Handle("/", router)
http.ListenAndServe(":8888", nil)
} The expected behaviour (and the actual behaviour before this commit) is: curl localhost:8888/something -X POST # -> post
curl localhost:8888/foobar # -> get The actual behaviour as of this commit is that both requests are answered with GET. This is especially problematic with routes that have the same path but different methods, as they'll all be answered with the method of the catch-all route. The problem disappears when lines 61-64 are commented out or removed from |
Even worse: post.HandleFunc("/some/{thing}", func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintln(rw, "post")
})
// Same route but different method
get.HandleFunc("/some/{thing}", func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintln(rw, "get")
}) curl localhost:8888/some/thing -X POST # -> get |
I opened an issue for this. |
This is my attempt to handle the Issue #271 . All existing tests are passing and I have added an additional test for current feature.
This is my first major contribution to a big open source project. Please feel free to suggest any change.