Golang HTTP自定义错误处理响应

huangapple go评论73阅读模式
英文:

Golang HTTP custom error handling response

问题

我正在阅读https://blog.golang.org/error-handling-and-go,最后给出了一个处理返回错误的更简洁方式的好例子,并且只是做了一些简单的事情:

// 处理函数的包装器。
type rootHandler func(http.ResponseWriter, *http.Request) error

// 实现http.Handler接口。
func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    err := fn(w, r) // 调用处理函数。
    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 实现了 error 接口
    return &CustomError{
        Kind:       EPARSE,
        Status:     http.StatusBadRequest,
        Message:    "some message",
        Op:         "create",
        Err:        err,
    }
}

这个方法非常有效,但我希望不要在每个控制器方法(在这种情况下是create)中包装rootHandler,并且想出了最好的方法是找出某种后置中间件。我尝试创建一个后置中间件,让路由器使用它而不是每个控制器方法,但是失败了,并想知道您如何实现这一点。我在SO上找到的最接近的答案是emmbee在https://stackoverflow.com/questions/42871194/how-can-i-combine-go-middleware-pattern-with-error-returning-request-handlers上的答案,除了AuthMiddleware中的fn将是一个控制器方法。

所以理想情况下,我希望有下面的处理程序,如果存在CustomError,则处理它:

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

为了背景,我正在使用gorilla mux和negroni。

非常感谢您的任何想法!非常感谢。

英文:

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 https://stackoverflow.com/questions/42871194/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.

答案1

得分: 4

你的理想解决方案行不通。mux API支持http.Handlerfunc(http.ResponseWriter, *http.Request)作为参数,而你有一个func(http.ResponseWriter, *http.Request) errorfunc(http.ResponseWriter, *http.Request) error不能作为mux API支持的参数类型之一。

唯一的解决方案是使用rootHandler包装器将每个控制器函数适应为mux API支持的类型。与标准的http.HandlerFunc包装器相比,这个包装器不会增加额外的开销。

使用一个辅助函数来减少包装器所需的代码量:

func handle(r *mux.Router, p string, fn func(http.ResponseWriter, *http.Request) error) *mux.Route {
    return r.Handle(p, rootHandler(fn))
}

...
handle(testRouter, "/", create).Methods("POST")
handle(testRouter, "/{id:[0-9]+}/", getByID).Methods("GET")

在辅助函数中使用类型切换来处理不同的控制器类型:

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))
    }
}
英文:

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

huangapple
  • 本文由 发表于 2021年6月24日 09:02:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/68108449.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定