我熟悉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))
我可以将这两种模式分开工作,但我不知道如何将它们组合起来。我喜欢我能够用Alice这样的库链接中间件。但如果他们也可以返回错误那就太好了。我有办法实现这个目标吗?
答案 0 :(得分:1)
我喜欢这种HandlerFuncs模式也返回错误,它更整洁,你只需编写一次错误处理程序。只需将中间件与其包含的处理程序分开考虑,您就不需要中间件来传递错误。中间件就像一个链依次执行每个中间件,然后最后一个中间件是一个知道你的处理程序签名的中间件,并适当地处理错误。
因此,在它最简单的形式中,保持中间件完全相同,但最后插入一个这种形式(并且不会执行另一个中间件,但是一个特殊的HandlerFunc): / p>
// 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)))
这意味着这个特殊的错误中间件只能包装你的特殊功能签名,并且链接的末尾,但这很好。此外,我还考虑将此行为包装在您自己的多路复用器中,以使其更简单并避免传递错误处理程序,并让您更轻松地构建一系列中间件,而不会在路由设置中进行难看的包装。
我认为如果您正在使用路由器库,则需要明确支持此模式才能正常工作。您可以在此路由器中以修改后的形式查看此操作示例,该路由器完全使用您之后的签名,但处理构建中间件链并执行它而无需手动换行:
答案 1 :(得分:0)
根据定义,中间件的输出是HTTP响应。如果发生错误,它会阻止请求被执行,在这种情况下,中间件应该返回HTTP错误(如果服务器上出现意外错误,则返回500),或者它不会,在这种情况下,应该记录所发生的任何事情。它可以由系统管理员修复,执行应该继续。
如果你想通过允许你的功能恐慌来实现这一点(尽管我不建议这样做),抓住这种情况并在以后处理它而不会崩溃服务器,this blog post部分有一个例子恐慌恢复(甚至使用Alice)。
答案 2 :(得分:0)
根据我的理解,您希望将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())
}
}
中将其称为:
{{1}}
答案 3 :(得分:0)
最灵活的解决方案是这样的:
首先定义一个与您的处理程序签名匹配的类型并实现 ServeHTTP
以满足 http.Handler 接口。通过这样做,ServeHTTP
将能够调用处理程序函数并在失败时处理错误。类似的东西:
type httpHandlerWithError func(http.ResponseWriter, *http.Request) error
func (fn httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Message, err.StatusCode)
}
}
现在像往常一样创建中间件。中间件应该创建一个函数,如果失败或调用链中的下一个成功则返回错误。然后将函数转换为定义的类型,例如:
func AuthMiddleware(next http.Handler) http.Handler {
// create handler which returns error
fn := func(w http.ResponseWriter, r *http.Request) error {
//a custom error value
unauthorizedError := &httpError{Code: http.StatusUnauthorized, Message: http.StatusText(http.StatusUnauthorized)}
auth := r.Header.Get("authorization")
creds := credentialsFromHeader(auth)
if creds != nil {
return unauthorizedError
}
user, err := db.ReadUser(creds.username)
if err != nil {
return &httpError{Code: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)}
}
err = checkPassword(creds.password+user.Salt, user.Hash)
if err != nil {
return unauthorizedError
}
ctx := r.Context()
userCtx := UserToCtx(ctx, user)
// we got here so there was no error
next.ServeHTTP(w, r.WithContext(userCtx))
return nil
}
// convert function
return httpHandlerWithError(fn)
}
现在您可以像使用任何常规中间件一样使用中间件。