0

I'm using Gorilla Mux to handle my HTTP routes for my REST API. I'm using the following binding for my API:

r := mux.NewRouter()
r.Host(baseUrl)

api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/users", APIUsers).Methods("GET", "POST")

http.Handle("/", r)
http.ListenAndServe(":8083", nil)

Then I do a CURL like this:

$ curl -i -X PUT http://127.0.0.1:8083/api/users

The Method APIUsers doesn't get called, as expected. I do get a 200 OK back tho:

HTTP/1.1 200 OK
Date: Tue, 30 Dec 2014 19:03:59 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8

Should I do this different with Gorilla to, let's say, bind all failing functions to a designated function? Also I wonder what's the typical HTTP code to return on non-supported functions. I'm a assuming a code in the 400 range, but I'm not sure which to use. I don't want it to be 200 tho, as the request was not "OK".

Any ideas and tips on this subject?

  • You should be getting a 404. mux is setup to use `http.NotFoundHandler()` when a route is not matched. Have you specified a NotFoundHandler for the `Router`? – jmaloney Dec 30 '14 at 22:24

2 Answers2

2

The status code you should return is "405 MethodNotAllowed" or http.StatusMethodNowAllowed in go.

From wikipedia:

A request was made of a resource using a request method not supported by that resource; for example, using GET on a form which requires data to be presented via POST, or using PUT on a read-only resource.

http://en.wikipedia.org/wiki/List_of_HTTP_status_codes

Arjan
  • 19,957
  • 2
  • 55
  • 48
  • Thanks for the answer Arjan. Is this common to use for REST API's too? Sadly the Methods constraint would be useless if I can't direct the 405 to a disallowed method :P –  Dec 30 '14 at 19:44
  • It is generally a good idea to return the appropriate HTTP response code when possible. – Arjan Dec 31 '14 at 11:28
2

You can set a custom NotFoundHandler but that will apply to all unmatched routes.

If you want a specific response returned you have to define the route explicitly.

Example:

func main() {
    r := mux.NewRouter().PathPrefix("/api").Subrouter()

    // custom not found handler used for unmatched routes
    var notFound NotFound
    r.NotFoundHandler = notFound

    r.HandleFunc("/users", APIUsers).Methods("GET", "POST")

    // return 405 for PUT, PATCH and DELETE
    r.HandleFunc("/users", status(405, "GET", "POST")).Methods("PUT", "PATCH", "DELETE")

    http.Handle("/", r)

    http.ListenAndServe(":8083", nil)
}

type NotFound func(w http.ResponseWriter, req *http.Request)

func (NotFound) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    w.WriteHeader(404)
    w.Write([]byte(`{"message": "Not Found"}`))
}

// status is used to set a specific status code
func status(code int, allow ...string) func(w http.ResponseWriter, req *http.Request) {
    return func(w http.ResponseWriter, req *http.Request) {
        w.WriteHeader(code)
        if len(allow) > 0 {
            w.Write([]byte(`Allow: ` + strings.Join(allow, ", ")))
        }
    }
}

func APIUsers(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("hello"))
}
jmaloney
  • 11,580
  • 2
  • 36
  • 29
  • Thanks for the answer @jmaloney. Any idea how I could access the Methods from the HandleFunc in status(405)? What I want to achieve is to set the `Allow` resulting in `Allow: GET,POST` as the info of the 405 response. The current way I lose the route info (containing which Methods are set). Should I keep a global buffer variable to track this or can I detect it in some other way? –  Jan 01 '15 at 19:38
  • @Allender you can just pass the allowed methods you want into the status function. I've updated my answer to illustrate. – jmaloney Jan 02 '15 at 02:24
  • Thanks @jmaloney. I finally ended up with an extra helper function on top of the normal route-binding, like so; `addAPIRoute("/users/{id:[0-9]+}/", apiUsers, "GET", "POST", "PUT")`. This way I could also make more parents to like determine if Auth/TLS/Etc. is needed per route. Thanks again :) –  Jan 02 '15 at 07:27