如何将Go中间件模式与错误返回请求处理程序结合起来?

Ale*_*lex 6 middleware go go-alice

我熟悉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.
    })
}
Run Code Online (Sandbox Code Playgroud)

例如,如果我有一个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))
    })
}
Run Code Online (Sandbox Code Playgroud)

还有一个简单的handleFunc:

func handleFunc(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`Hello World!`))
}
Run Code Online (Sandbox Code Playgroud)

我可以这样组合它们:

http.Handle("/", loggingHandler(http.HandlerFunc(handleFunc)))
log.Fatal(http.ListenAndServe(":8080", nil))
Run Code Online (Sandbox Code Playgroud)

这一切都很好.

但我喜欢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
}
Run Code Online (Sandbox Code Playgroud)

然后像这样包装它来使用它:

http.Handle("/", errorHandler(errorHandle))
Run Code Online (Sandbox Code Playgroud)

我可以将这两种模式分开工作,但我不知道如何将它们组合起来.我喜欢我能够用Alice这样的库链接中间件.但如果他们也可以返回错误那就太好了.我有办法实现这个目标吗?

Ken*_*ant 5

我也喜欢这种 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
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

...

然后像这样包装你的函数:

moreMiddleware(myMiddleWare(errorHandler(myhandleFuncReturningError)))
Run Code Online (Sandbox Code Playgroud)

这意味着这个特殊的错误中间件只能包装您的特殊函数签名,并位于链的末尾,但这很好。此外,我会考虑将此行为包装在您自己的多路复用器中,以使其更简单一些并避免传递错误处理程序,并让您更轻松地构建中间件链,而无需在路由设置中进行丑陋的包装。

我认为如果您使用的是路由器库,则可能需要明确支持此模式才能工作。您可以在此路由器中以修改后的形式看到此示例,它完全使用您所追求的签名,但处理构建中间件链并在没有手动包装的情况下执行它:

https://github.com/fragmenta/mux/blob/master/mux.go