Search code examples
httpgoerror-handlingmuxcustom-error-handling

Golang HTTP custom error handling response


I was going through https://blog.golang.org/error-handling-and-go and at the end it gave a good example on how to handle returning errors in a cleaner way and just made something simple:

// Wrapper for handler functions.
type rootHandler func(http.ResponseWriter, *http.Request) error

// Implement the http.Handler interface.
func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    err := fn(w, r) // Call handler function.
    if err == nil {
        return
    }

    ...
}
// POST /api/test/
testRouter.
    Handle("/", rootHandler(create)).
    Methods("POST")

// GET /api/test/{id}/
testRouter.
    HandleFunc("/{id:[0-9]+}/", rootHandler(getByID)).
    Methods("GET")
func create(w http.ResponseWriter, r *http.Request) error {
    // CustomError implementes error
    return &CustomError{
        Kind:       EPARSE,
        Status:     http.StatusBadRequest,
        Message:    "some message",
        Op:         "create",
        Err:        err,
    }
}

This worked very well, but I would prefer not to wrap every controller method (create in this case) in rootHandler, and figured the best way is to figure out some sort of post-middleware. I've failed at trying to create a post-middlewhere which the router uses instead of each controller method, and wondering how you may go about implementing this. The closest answer on SO I could find was emmbee's answer on How can I combine Go middleware pattern with error returning request handlers? except for fn in AuthMiddleware would be a controller method.

So ideally, I would have the below handler which handles the CustomError if it exists

// POST /api/test
testRouter.
    Handle("/", create).
    Methods("POST")

For context, I'm using gorilla mux and negroni.

Any ideas are appreciated! Thank you very much.


Solution

  • Your ideal solution will not work. The mux API supports http.Handler and func(http.ResponseWriter, *http.Request) arguments. You have a func(http.ResponseWriter, *http.Request) error. A func(http.ResponseWriter, *http.Request) error cannot be passed as one of the argument types supported by the mux API.

    The only solution is to adapt each controller function to a type supported by the mux API using the rootHandler wrapper. The wrapper does not add overhead compared to the standard http.HandlerFunc wrapper.

    Use a helper function to reduce the amount of code required for the wrappers:

    func handle(r *mux.Router, p string, fn func(http.ResponseWriter, *http.Request) error) *mux.Route {
        return r.Handle(path, rootHandler(fn))
    }
    
    ...
    handle(testRouter, "/", create).Methods("POST")
    handle(testRouter, "/{id:[0-9]+}/", getByID).Methods("GET")
    

    Use a type switch in the helper function to handle different controller types:

    func handle(r *mux.Router, p string, h interface{}) *mux.Route {
        switch h := h.(type) {
        case func(http.ResponseWriter, *http.Request) error:
            return r.Handle(p, rootHandler(h))
        case func(http.ResponseWriter, *http.Request):
            return r.HandleFunc(p, h)
        case http.Handler:
            return r.Handle(p, h)
        default:
            panic(fmt.Sprintf("handler type %T not supported", h))
        }
    }