如何将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)) 

我可以让这两种模式独立工作,但我不知道我怎么能把它们结合起来。我喜欢我能够将中间件与像爱丽丝这样的库链接起来。但如果他们也可以返回错误,那将会很好。我有办法实现这一目标吗?

+0

所以你想要返回错误...在哪里?谁将是检查返回的错误的调用者? – zerkms

我喜欢这个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))) 

这意味着这个特殊的错误中间件永远只能换你的特殊功能签名,来到链条的尽头,但没关系。此外,我会考虑在自己的多路复用器中包装这种行为,使其更简单一些,并避免传递错误处理程序,并让您更轻松地构建中间件链,而不会在您的路由设置中包装丑陋。

我想如果你使用的是路由器库,它可能需要明确支持这种模式。您可以在此路由器,它采用正是你后签名修改的形式看到这样的例子在行动,但处理构建中间件链,无需人工包装执行它:

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

+0

感谢您的帮助! – 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()) 
    } 

}