Обработчик паники Go Gorilla для ответа с пользовательским статусом

В Gorilla с помощью RecoveryHandler мы могли подавить панику. Однако существует ли обработчик или библиотечный метод для ответа с определенным кодом состояния Http и сообщением для данного типа ошибки.
Например, в случае паники для Mandatory field missing error можно было бы ответить Http 400 и осмысленным сообщением что именно не так с полезной нагрузкой.

Каков рекомендуемый подход для этого?
ОБНОВЛЕНИЕ В коде: перечислены 2 подхода

  1. Обрабатывайте ошибки, возвращаемые при каждом вызове метода, и формируйте ответ.
  2. Вместо того, чтобы возвращать ошибки, паникуйте с пользовательскими типами ошибок и отложите устранение ошибок до функции, чтобы построить ответ. Это делает код легко читаемым и менее повторяющимся.
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
}

person suman j    schedule 24.11.2015    source источник
comment
Можете ли вы показать свой код? Вы не должны паниковать из-за ошибок, которые вы хотите передать пользователю: верните ошибку, проверьте ее и верните соответствующий код состояния.   -  person elithrar    schedule 25.11.2015
comment
@elithrar Это больше похоже на общий подход. Допустим, у меня есть 10 маршрутов в моем rest API, и обработка этого в каждом маршруте — это повторение. Я хочу определить обработчик и обернуть его так же, как это делает обработчик восстановления.   -  person suman j    schedule 25.11.2015
comment
Не паникуйте. Создайте пользовательский тип, который реализует http.Handler, но возвращает ошибку, чтобы вы могли возвращать коды состояния по мере необходимости и обрабатывать их в методе ServeHTTP для вашего типа. Смотрите мой пост здесь elithrar.github.io/article/ и предыдущий ответ о типах пользовательских обработчиков здесь: stackoverflow.com/a/33647455/556573 - если вы все еще запутался, я напишу минимальный пример, когда выйду из автобуса!   -  person elithrar    schedule 25.11.2015
comment
@elithrar Я попробую. Спасибо   -  person suman j    schedule 25.11.2015
comment
@elithrar смотрите мои обновления вопроса. Хотите знать, какой предпочтительный подход между двумя упомянутыми мной (или любым другим лучшим вариантом)   -  person suman j    schedule 25.11.2015
comment
Опять же, причин для паники нет. Паника должна представлять собой неисправимую ошибку или (в некоторых случаях) способ раскрутки стека из глубоко вложенной функции (которую вы должны восстановить на верхнем уровне). Ничто из этого не относится к случаю «отсутствующего поля». Смотрите мой ответ через несколько минут.   -  person elithrar    schedule 26.11.2015


Ответы (1)


Идиоматично максимально опираться на интерфейсы, предоставляемые стандартной библиотекой. В данном случае интерфейс http.Handler из пакета https://golang.org/pkg/net/http/.

В вашем случае вы можете создать новый тип, который позволит вашим обработчикам возвращать тип ошибки и централизованно обрабатывать все эти случаи ошибок.

// 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
}

Преимущества, которые вы получаете здесь, включают в себя:

  • Нет паники из-за ошибок, которые можно обработать
  • Вы можете централизовать обработку ошибок в своем методе ServeHTTP, т.е. для 400 ошибок вы можете указать причину ошибки в ответе. Для ошибок 500 вы можете вернуть общее сообщение, поскольку HTTP 500 — это не то, что может решить пользователь.
  • Ваши функции-обработчики явно возвращают ошибки, и вам больше не нужно помнить об использовании голых операторов return, чтобы избежать продолжения выполнения.
  • Ваш тип StatusError оборачивает ошибку кодом состояния, но по-прежнему позволяет вам легко проверять/регистрировать/записывать обернутую ошибку.

Дальнейшее чтение:

person elithrar    schedule 26.11.2015
comment
Это похоже на первый подход, который я упомянул в своем вопросе. sendErrorResponse занимается централизованной обработкой ошибок. Однако ожидается, что на каждом этапе разработчик будет проверять наличие ошибок, вызывать sendErrorResponse и возвращать значение. Это боль. В таких языках, как Java/Python, идиоматично генерировать/создавать исключения и перехватывать их. Этот рейз/бросок и ловля — это то, о чем я упоминал во втором подходе. Я понимаю вашу точку зрения, но я все еще чувствую, что внутри обработчика много ненужного кода, который загрязняет основную логику. if foo,err = func3(); err != nil { //centralized handling of error ; return } повторяется. - person suman j; 26.11.2015
comment
Идиома if err != nil является частью дизайна Go. Ваш подход также имеет свою стоимость (отсрочка не бесплатна). Локальная обработка ошибок распространена в программах Go. Go — это не Java, и попытка добиться этого потребует от вас дополнительных усилий, чтобы обойти его (как вы видите!). - person elithrar; 26.11.2015
comment
Возможно, на создателей Go больше повлиял язык C. Только что написал свой первый API отдыха на Go. Код определенно уродлив из-за многословия в обработке ошибок. Я изменил его, используя панику/восстановление, и теперь код стал намного чище и легко читается. - person suman j; 29.11.2015