2017-03-18 39 views
3

我熟悉這樣的圍棋中間件模式:如何將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

回答

0

我喜歡這個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

0

根據定義,中間件的輸出是HTTP響應。如果發生錯誤,要麼阻止請求被執行,在這種情況下,中間件應該返回一個HTTP錯誤(如果服務器上出現意外錯誤,則返回500),或者它不會,在這種情況下,發生的任何事情都應該被記錄下來它可以由系統管理員修復,並且執行應該繼續。

如果你想使你的函數恐慌(雖然我不建議有意這樣做),捕捉這種情況,後來處理它沒有崩潰的服務器來做到這一點,是在this blog post在部分恐慌恢復的例子(它甚至使用愛麗絲)。

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()) 
    } 

} 
相關問題