Golang標準庫http包提供了基礎的http服務落剪,這個服務又基于Handler接口和ServeMux結(jié)構(gòu)的做Mutilpexer。實際上场靴,go的作者設計Handler這樣的接口啡莉,不僅提供了默認的ServeMux對象,開發(fā)者也可以自定義ServeMux對象旨剥。
本質(zhì)上ServeMux只是一個路由管理器咧欣,而它本身也實現(xiàn)了Handler接口的ServeHTTP方法。因此圍繞Handler接口的方法ServeHTTP轨帜,可以輕松的寫出go中的中間件魄咕。
在go的http路由原理討論中,追本溯源還是討論Handler接口和ServeMux結(jié)構(gòu)蚌父。下面就基于這兩個對象開始更多關于go中http的故事吧哮兰。
介紹http庫源碼的時候,創(chuàng)建http服務的代碼很簡單苟弛,實際上代碼隱藏了很多細節(jié)喝滞,才有了后來的流程介紹。本文的目的主要是把這些細節(jié)暴露膏秫,從更底層的方式開始右遭,一步步隱藏細節(jié),完成樣例代碼的一樣的邏輯缤削。了解更多http包的原理之后窘哈,才能基于此構(gòu)建中間件。
自定義的Handler
標準庫http提供了Handler接口亭敢,用于開發(fā)者實現(xiàn)自己的handler宵距。只要實現(xiàn)接口的ServeHTTP方法即可。
關于約定名詞 handler函數(shù)吨拗,handler處理器满哪,handler,請參考http原理與源碼筆記中的定義劝篷。不然對下文的描述將會很困惑哨鸭。
type textHandler struct {
responseText string
}
func (th *textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, th.responseText)
}
type indexHandler struct {}
func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
html := `<doctype html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>
<a href="/welcome">Welcome</a> | <a href="/message">Message</a>
</p>
</body>
</html>`
fmt.Fprintln(w, html)
}
func main() {
mux := http.NewServeMux()
mux.Handle("/", &indexHandler{})
thWelcome := &textHandler{"TextHandler !"}
mux.Handle("/text",thWelcome)
http.ListenAndServe(":8000", mux)
}
上面自定義了兩個handler結(jié)構(gòu),都實現(xiàn)了ServeHTTP方法娇妓。我們知道像鸡,NewServeMux可以創(chuàng)建一個ServeMux實例,ServeMux同時也實現(xiàn)了ServeHTTP方法,因此代碼中的mux也是一種handler只估。把它當成參數(shù)傳給http.ListenAndServe方法志群,后者會把mux傳給Server實例。因為指定了handler蛔钙,因此整個http服務就不再是DefaultServeMux锌云,而是mux,無論是在注冊路由還是提供請求服務的時候吁脱。
有一點值得注意桑涎,這里并沒有使用HandleFunc注冊路由,而是直接使用了mux注冊路由兼贡。當沒有指定mux的時候攻冷,系統(tǒng)需要創(chuàng)建一個默認的defaultServeMux,此時我們已經(jīng)有了mux遍希,因此不再需要http.HandleFucn方法了等曼,直接使用mux的Handle方法注冊即可。
此外凿蒜,Handle第二個參數(shù)是一個handler(處理器)禁谦,并不是HandleFunc的一個handler函數(shù),其原因也是因為mux.Handle本質(zhì)上就需要綁定url的pattern模式和handler(處理器)即可篙程。既然indexHandler是handle(處理器)枷畏,當然就能作為參數(shù),一切請求的處理過程虱饿,都交給器實現(xiàn)的接口方法ServeHTTP就行了拥诡。這個過程有點饒,如果不甚了解氮发,建議先閱讀http原理與源碼筆記了解注冊路由的本質(zhì)渴肉。下圖
左邊的12兩步只是為了創(chuàng)建一個ServeMux實例,然后調(diào)用實例的Handle方法爽冕,右邊的直接就調(diào)用了mux實例的Handle方法仇祭。
創(chuàng)建handler處理器
上面費勁口舌羅嗦,不就是1颈畸,2乌奇,3與3的差別么,并且1眯娱,2的兩步操作礁苗,封裝程度更高,開發(fā)者只需要寫函數(shù)即可徙缴,不用再定義結(jié)構(gòu)试伙。代碼更簡潔,因此,下面將直接創(chuàng)建handler函數(shù)疏叨,調(diào)用go的方法將函數(shù)轉(zhuǎn)變成handler(處理器)潘靖。
func text(w http.ResponseWriter, r *http.Request){
fmt.Fprintln(w, "hello world")
}
func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
html := `<doctype html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>
<a href="/welcome">Welcome</a> | <a href="/message">Message</a>
</p>
</body>
</html>`
fmt.Fprintln(w, html)
}
func main() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(index))
mux.HandleFunc("/text", text)
http.ListenAndServe(":8000", mux)
}
代碼中使用了http.HandlerFunc
方法直接將一個handler函數(shù)轉(zhuǎn)變成實現(xiàn)了handler(處理器)。等價與圖中的3的步驟蚤蔓。
而mux.HandleFunc("/text", text)
就更進一步卦溢,與圖中的2步驟一致,與defaultServemux.HandleFunc(pattern, function)
的用法一樣昌粤。
使用默認的DefaultServeMux
經(jīng)過了上面兩個過程的轉(zhuǎn)化既绕,隱藏了更多的細節(jié)啄刹,代碼與defaultServeMux的方式越來越像涮坐。下面再去掉自定義的ServeMux,只需要修改main函數(shù)的邏輯如下:
func main() {
http.Handle("/", http.HandlerFunc(index))
http.HandleFunc("/text", text)
http.ListenAndServe(":8000", nil)
}
上述的代碼就和前文的例子一樣誓军,當代碼中不顯示的創(chuàng)建serveMux對象袱讹,http包就默認創(chuàng)建一個DefaultServeMux對象用來做路由管理器mutilplexer。
自定義Server
默認的DefaultServeMux創(chuàng)建的判斷來自server對象昵时,如果server對象不提供handler捷雕,才會使用默認的serveMux對象。既然ServeMux可以自定義壹甥,那么Server對象一樣可以救巷。
使用http.Server 即可創(chuàng)建自定義的server對象:
func main(){
http.HandleFunc("/", index)
server := &http.Server{
Addr: ":8000",
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
}
server.ListenAndServe()
}
自定義的serverMux對象也可以傳到server對象中。
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", index)
server := &http.Server{
Addr: ":8000",
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
Handler: mux,
}
server.ListenAndServe()
}
可見go中的路由和處理函數(shù)之間關系非常密切句柠,同時又很靈活浦译。通過巧妙的使用Handler接口,可以設計出優(yōu)雅的中間件程序溯职。
中間件Middleware
所謂中間件精盅,就是連接上下級不同功能的函數(shù)或者軟件,通常進行一些包裹函數(shù)的行為谜酒,為被包裹函數(shù)提供添加一些功能或行為叹俏。前文的HandleFunc就能把簽名為 func(w http.ResponseWriter, r *http.Reqeust)
的函數(shù)包裹成handler。這個函數(shù)也算是中間件僻族。
這里我們以HTTP請求的中間件為例子粘驰,提供一個log中間件,能夠打印出每一個請求的log述么。
go的http中間件很簡單蝌数,只要實現(xiàn)一個函數(shù)簽名為func(http.Handler) http.Handler
的函數(shù)即可。http.Handler是一個接口碉输,接口方法我們熟悉的為serveHTTP籽前。返回也是一個handler。因為go中的函數(shù)也可以當成變量傳遞或者或者返回,因此也可以在中間件函數(shù)中傳遞定義好的函數(shù)枝哄,只要這個函數(shù)是一個handler即可肄梨,即實現(xiàn)或者被handlerFunc包裹成為handler處理器。
func middlewareHandler(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
// 執(zhí)行handler之前的邏輯
next.ServeHTTP(w, r)
// 執(zhí)行完畢handler后的邏輯
})
}
這種方式在Elixir的Plug框架中很流行挠锥,思想偏向于函數(shù)式范式众羡。熟悉python的朋友一定也想到了裝飾器。閑話少說蓖租,來看看go是如何實現(xiàn)的吧:
func loggingHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
})
}
func main() {
http.Handle("/", loggingHandler(http.HandlerFunc(index)))
http.ListenAndServe(":8000", nil)
}
loggingHandler即是一個中間件函數(shù)粱侣,將請求的和完成的時間處理”突拢可以看見請求或go的輸出:
2016/12/04 21:18:13 Started GET /
2016/12/04 21:18:13 Comleted / in 13.365μs
2016/12/04 21:18:20 Started GET /
2016/12/04 21:18:20 Comleted / in 17.541μs
既然中間件是一種函數(shù)齐婴,并且簽名都是一樣,那么很容易就聯(lián)想到函數(shù)一層包一層的中間件稠茂。再添加一個函數(shù)柠偶,然后修改main函數(shù):
func hook(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("before hook")
next.ServeHTTP(w, r)
log.Println("after hook")
})
}
func main() {
http.Handle("/", hook(loggingHandler(http.HandlerFunc(index))))
http.ListenAndServe(":8000", nil)
}
在loggingHandler再包了一層hook,可以看到輸出為:
2016/12/04 21:26:30 before hook
2016/12/04 21:26:30 Started GET /
2016/12/04 21:26:30 Comleted / in 14.016μs
2016/12/04 21:26:30 after hook
函數(shù)調(diào)用形成了一條鏈睬关,可以是在這條鏈上做很多事情诱担。當然go的寫法上,比起elixir的|>
的符號电爹,優(yōu)雅性略差蔫仙。
總結(jié)
通過對http包的源碼學習,我們了解了Handler接口和ServeMux結(jié)構(gòu)丐箩。并且知道如何配合他們實現(xiàn)go的中間件函數(shù)摇邦。當然,對于幾個約定名詞雏蛮,handler函數(shù)涎嚼,handler處理器和handler對象的理解,是掌握它們關系的關鍵因素挑秉,而handler處理器和handler對象的關系法梯,恰恰又是go接口使用的經(jīng)典例子,讓go具有一些動態(tài)類型的特性犀概。
了解了http服務如何構(gòu)建之后立哑,處理請求和返回響應就是下一個故事。而實現(xiàn)處理邏輯恰恰在我們一直在強調(diào)的ServeHTTP接口方法中姻灶。
接下來將會更詳細的討論請求和響應相關的函數(shù)對象铛绰。