// middleware/hello.go package main func hello(wr http.ResponseWriter, r *http.Request) { wr.Write([]byte("hello")) } func main() { http.HandleFunc("/", hello) err := http.ListenAndServe(":8080", nil) ... }這是一個典型的 Web 服務,掛載了一個簡單的路由。我們的線上服務一般也是從這樣簡單的服務開始逐漸拓展開去的。
// middleware/hello_with_time_elapse.go var logger = log.New(os.Stdout, "", 0) func hello(wr http.ResponseWriter, r *http.Request) { timeStart := time.Now() wr.Write([]byte("hello")) timeElapsed := time.Since(timeStart) logger.Println(timeElapsed) }這樣便可以在每次接收到 http 請求時,列印出當前請求所消耗的時間。
// middleware/hello_with_more_routes.go // 省略了一些相同的程式碼 package main func helloHandler(wr http.ResponseWriter, r *http.Request) { // ... } func showInfoHandler(wr http.ResponseWriter, r *http.Request) { // ... } func showEmailHandler(wr http.ResponseWriter, r *http.Request) { // ... } func showFriendsHandler(wr http.ResponseWriter, r *http.Request) { timeStart := time.Now() wr.Write([]byte("your friends is tom and alex")) timeElapsed := time.Since(timeStart) logger.Println(timeElapsed) } func main() { http.HandleFunc("/", helloHandler) http.HandleFunc("/info/show", showInfoHandler) http.HandleFunc("/email/show", showEmailHandler) http.HandleFunc("/friends/show", showFriendsHandler) // ... }每一個 handler 裡都有之前提到的記錄執行時間的程式碼,每次增加新的路由我們也同樣需要把這些看起來長得差不多的程式碼拷貝到我們需要的地方去。因為程式碼不太多,所以實施起來也沒有遇到什麼大問題。
func helloHandler(wr http.ResponseWriter, r *http.Request) { timeStart := time.Now() wr.Write([]byte("hello")) timeElapsed := time.Since(timeStart) logger.Println(timeElapsed) // 新增耗時上報 metrics.Upload("timeHandler", timeElapsed) }修改到這裡,本能地發現我們的開發工作開始陷入了泥潭。無論未來對我們的這個 Web 系統有任何其它的非功能或統計需求,我們的修改必然牽一髮而動全身。只要增加一個非常簡單的非業務統計,我們就需要去幾十個 handler 裡增加這些業務無關的程式碼。雖然一開始我們似乎並沒有做錯,但是顯然隨著業務的發展,我們的行事方式讓我們陷入了程式碼的泥潭。
func hello(wr http.ResponseWriter, r *http.Request) { wr.Write([]byte("hello")) } func timeMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) { timeStart := time.Now() // next handler next.ServeHTTP(wr, r) timeElapsed := time.Since(timeStart) logger.Println(timeElapsed) }) } func main() { http.Handle("/", timeMiddleware(http.HandlerFunc(hello))) err := http.ListenAndServe(":8080", nil) ... }這樣就非常輕鬆地實現了業務與非業務之間的剝離,魔法就在於這個 timeMiddleware 。可以從程式碼中看到,我們的 timeMiddleware() 也是一個函數,其引數為 http.Handler,http.Handler 的定義在 net/http 包中:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type Handler interface { ServeHTTP(ResponseWriter, *Request) } type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }實際上只要你的 handler 函數簽名是:
func (ResponseWriter, *Request)
那麼這個 handler 和 http.HandlerFunc() 就有了一致的函數簽名,可以將該 handler() 函數進行型別轉換,轉為 http.HandlerFunc。h = getHandler() => h.ServeHTTP(w, r) => h(w, r)
上面提到的把自定義 handler 轉換為 http.HandlerFunc() 這個過程是必須的,因為我們的 handler 沒有直接實現 ServeHTTP 這個介面。上面的程式碼中我們看到的 HandleFunc( 注意 HandlerFunc 和 HandleFunc 的區別 ) 裡也可以看到這個強制轉換過程:func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } // 呼叫 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) }知道 handler 是怎麼一回事,我們的中介軟體通過包裝 handler,再返回一個新的 handler 就好理解了。
customizedHandler = logger(timeout(ratelimit(helloHandler)))
這個函數鏈在執行過程中的上下文可以用下圖來表示。