英文:
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.Handler和func(http.ResponseWriter, *http.Request)作为参数,而你有一个func(http.ResponseWriter, *http.Request) error。func(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))
	}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论