如何将Go中间件模式与错误返回请求处理程序结合使用?
我熟悉这样的围棋中间件模式:如何将Go中间件模式与错误返回请求处理程序结合使用?
// Pattern for writing HTTP middleware.
func middlewareHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Our middleware logic goes here before executing application handler.
next.ServeHTTP(w, r)
// Our middleware logic goes here after executing application handler.
})
}
因此,举例来说,如果我有一个loggingHandler:
func loggingHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Before executing the handler.
start := time.Now()
log.Printf("Strated %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
// After executing the handler.
log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
})
}
和一个简单的handleFunc:
func handleFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`Hello World!`))
}
我可以将它们组合如下:
http.Handle("/", loggingHandler(http.HandlerFunc(handleFunc)))
log.Fatal(http.ListenAndServe(":8080", nil))
没关系。
但我喜欢Handlers能像普通函数那样返回错误的想法。这使得错误处理更容易,因为如果出现错误,我只能返回错误,或者只是在函数结束时返回nil。
我已经做了这样的:
type errorHandler func(http.ResponseWriter, *http.Request) error
func (f errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := f(w, r)
if err != nil {
// log.Println(err)
fmt.Println(err)
os.Exit(1)
}
}
func errorHandle(w http.ResponseWriter, r *http.Request) error {
w.Write([]byte(`Hello World from errorHandle!`))
return nil
}
,然后用它通过包装它像这样:
http.Handle("/", errorHandler(errorHandle))
我可以让这两种模式独立工作,但我不知道我怎么能把它们结合起来。我喜欢我能够将中间件与像爱丽丝这样的库链接起来。但如果他们也可以返回错误,那将会很好。我有办法实现这一目标吗?
我喜欢这个HandlerFuncs的模式也返回错误,它更加整洁,你只需写一次你的错误处理程序。只要将您的中间件与它包含的处理程序分开来考虑,就不需要中间件来传递错误。中间件就像一个依次执行每个中间件的链,然后最后一个中间件就是一个知道你的处理器签名的中间件,并且适当地处理错误。
所以在它最简单的形式,让你有完全一样的中间件,但在末尾插入其中一个是这样的形式(与不执行其它的中间件而是一种特殊HandlerFunc):
// Use this special type for your handler funcs
type MyHandlerFunc func(w http.ResponseWriter, r *http.Request) error
// Pattern for endpoint on middleware chain, not takes a diff signature.
func errorHandler(h MyHandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Execute the final handler, and deal with errors
err := h(w, r)
if err != nil {
// Deal with error here, show user error template, log etc
}
})
}
...
然后换你的功能是这样的:
moreMiddleware(myMiddleWare(errorHandler(myhandleFuncReturningError)))
这意味着这个特殊的错误中间件永远只能换你的特殊功能签名,来到链条的尽头,但没关系。此外,我会考虑在自己的多路复用器中包装这种行为,使其更简单一些,并避免传递错误处理程序,并让您更轻松地构建中间件链,而不会在您的路由设置中包装丑陋。
我想如果你使用的是路由器库,它可能需要明确支持这种模式。您可以在此路由器,它采用正是你后签名修改的形式看到这样的例子在行动,但处理构建中间件链,无需人工包装执行它:
感谢您的帮助! – Alex
根据定义,中间件的输出是HTTP响应。如果发生错误,要么阻止请求被执行,在这种情况下,中间件应该返回一个HTTP错误(如果服务器上出现意外错误,则返回500),或者它不会,在这种情况下,发生的任何事情都应该被记录下来它可以由系统管理员修复,并且执行应该继续。
如果你想使你的函数恐慌(虽然我不建议有意这样做),捕捉这种情况,后来处理它没有崩溃的服务器来做到这一点,是在this blog post在部分恐慌恢复的例子(它甚至使用爱丽丝)。
根据我的理解,您想链接您的errorHandler
功能并将它们合并到您的loggingHandler
中。要做到这一点
的一种方法是使用一个struct
它传递给你的loggingHandler
的参数是这样的:
func loggingHandler(errorHandler ErrorHandler, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Call your error handler to do thing
err := errorHandler.ServeHTTP()
if err != nil {
log.Panic(err)
}
// next you can do what you want if error is nil.
log.Printf("Strated %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
// After executing the handler.
log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
})
}
// create the struct that has error handler
type ErrorHandler struct {
}
// I return nil for the sake of example.
func (e ErrorHandler) ServeHTTP() error {
return nil
}
,并在main
你这样称呼它:
func main() {
port := "8080"
// you can pass any field to the struct. right now it is empty.
errorHandler := ErrorHandler{}
// and pass the struct to your loggingHandler.
http.Handle("/", loggingHandler(errorHandler, http.HandlerFunc(index)))
log.Println("App started on port = ", port)
err := http.ListenAndServe(":"+port, nil)
if err != nil {
log.Panic("App Failed to start on = ", port, " Error : ", err.Error())
}
}
所以你想要返回错误...在哪里?谁将是检查返回的错误的调用者? – zerkms