1

In Gorilla, using RecoveryHandler we could suppress the panics. However is there a handler or a library method to respond with a specific Http status code and message for given error type.
For example, in case of a Panic for Mandatory field missing error, one would want to respond with Http 400 and a meaningful message of what exactly is wrong with the payload.

What is the recommended approach to do this?
UPDATE In code: 2 approaches are listed

  1. Handle errors returned at each method call and build the response.
  2. Instead of returning errors, panic with custom error types and defer the error recovery to a func to build the response. This makes the code easy to read and less repetitive.
func fooHandler(w http.ResponseWriter, r *http.Request) {
    //decode the request body into a struct instance
    if err := decode(r, myInstance); err != nil {
        sendErrorResponse(w,err,http.StatusBadRequest)
        return
    }
    //validate the struct instance for all mandatory keys presence
    if err := file.validate(); err != nil {
        sendErrorResponse(w,err,http.StatusBadRequest)
        return
    }
    //call DB and validate the response and handle the error

    //do some computation and again handle error.

    //finally construct response 
}

func barHandler(w http.ResponseWriter, r *http.Request) {
    //similar to above handler many funcs are called before the response is contruscted
}

func tomHandler(w http.ResponseWriter, r *http.Request) {
    //similar to above handler many funcs are called before the response is contruscted
}

func differentHandler(w http.ResponseWriter, r *http.Request) {
    defer recoverForErrors(w,r)
    // call as many funcs as you need.
    // validation, decoding etc will panic instead of returning errors.
    // This will avoid the repetitive boiler plate code of handling error and converting to meaningful error response
    // instead all this logic is pushed to recoverForErrors func. Which retrieves the error from panic and checks for 
    // specific error type to construct the error http response
}
Mr Lister
  • 45,515
  • 15
  • 108
  • 150
suman j
  • 6,710
  • 11
  • 58
  • 109
  • 1
    Can you show your code? You shouldn't be panicking on errors that you want to propagate to the user: return the error, inspect it, and return the appropriate status code. – elithrar Nov 24 '15 at 21:58
  • @elithrar This is more like a generic approach. Let's say I have 10 routes on my rest API and handling this in each route is repetition . I want to define a handler and wrap with it much like recovery handler does. – suman j Nov 24 '15 at 22:43
  • 1
    Don't panic. Create a custom type that implements http.Handler but returns an error, so you can return status codes as needed and process then in the ServeHTTP method for your type. See my post here http://elithrar.github.io/article/http-handler-error-handling-revisited/ and a previous answer about custom handler types here: http://stackoverflow.com/a/33647455/556573 - if you're still confused, I'll write a minimal example when I get off the bus! – elithrar Nov 24 '15 at 22:47
  • @elithrar I will give it a try. Thanks – suman j Nov 24 '15 at 23:46
  • @elithrar see my updates to the question. Want to know whats the preferred approach between the 2 I mentioned (or any other better option) – suman j Nov 25 '15 at 17:57
  • Again, there is honestly no reason to panic. A panic should represent an irrecoverable error, or (in certain cases), a way to unwind the stack from a deeply-nested function (which you should recover at the top level). None of this applies to a 'field missing' case. See my answer in a few moments. – elithrar Nov 26 '15 at 00:32

1 Answers1

4

It is idiomatic to lean on the interfaces provided by the standard library as much as possible. In this case, the http.Handler interface from the net/http package.

In your case, you can create a new type that allows your handlers to return an error type, and handle all of those error cases centrally.

// StatusError wraps an existing error with a HTTP status code.
type StatusError struct {
    Status int
    // Allows you to wrap another error
    Err error
}

func (e *StatusError) Error() string {
    return e.Error()
}

type AppHandler func(w http.ResponseWriter, r *http.Request) error

// Satisfies the http.Handler interface
func (ah AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Centralises your error handling
    err := ah(w, r)
    if err != nil {
        switch e := a.(type) {
        case *StatusError:
            switch e.Status {
            case 400:
                http.Error(w, e.Err.Error(), 400)
                return
            case 404:
                http.NotFound(w, r)
                return
            default:
                http.Error(w, http.StatusText(500), 500)
                return
        }
        default:
            http.Error(w, http.StatusText(500), 500)
            return
        }
}

// Your handlers will look like this
func SomeHandler(w http.ResponseWriter, r *http.Request) error {
    err := decode(r, myInstance)
    if err != nil {
        return &StatusError{400, err}
    }

    err := file.validate()
    if err != nil {
        return &StatusError{400, err}
    }

    // Continue on...
    return nil
}

The benefits you get here include:

  • No panicking for errors that can be handled
  • You can centralise your error handling in your ServeHTTP method - i.e. for 400 errors, you might write the error reason to the response. For 500 errors, you might return a generic message since a HTTP 500 isn't something the user can be expected to solve.
  • Your handler functions return errors explicitly, and you no longer need to remember to use naked return statements to avoid continued execution.
  • Your StatusError type wraps the error with a status code, but still allows you to inspect/log/write out the wrapped error easily.

Further reading:

elithrar
  • 23,364
  • 10
  • 85
  • 104
  • This is similar to the first approach I mentioned in my question. sendErrorResponse handles the centralized error handling. However at every step developer is expected to check for error and call sendErrorResponse and return. This is a pain. In languages like Java/Python, idiomatic is to throw/raise exceptions and catch them. This raise/throw and catch is what I mentioned in 2nd approach. I see your point but I still feel its lot of unnecessary code inside handler which pollutes the main logic. `if foo,err = func3(); err != nil { //centralized handling of error ; return }` is repetitive. – suman j Nov 26 '15 at 04:54
  • 1
    The `if err != nil` idiom is part of Go's design. Your approach also has a cost (defer isn't free). Local error handling is common in Go programs. Go isn't Java and trying to force that will require you to do more work to get around it (as you are finding!). – elithrar Nov 26 '15 at 05:11
  • Perhaps go creators are more influenced by C language. Just wrote my first rest API in Go. Code is definitely ugly due to verbosity in error handling . I changed it use panics/recovery and code is much clean and easy to read now . – suman j Nov 29 '15 at 04:02