譯註: 這篇文章的內容很是基礎,也很是容易理解。原文地址,感受是最能清晰的講述了net/http
包的用法的一篇,故翻譯一下共享之。golang
Go 語言中處理 HTTP 請求主要跟兩個東西相關:ServeMux
和 Handler
。數據庫
ServrMux
本質上是一個 HTTP 請求路由器(或者叫多路複用器,Multiplexor)。它把收到的請求與一組預先定義的 URL 路徑列表作對比,而後在匹配到路徑的時候調用關聯的處理器(Handler)。瀏覽器
處理器(Handler)負責輸出HTTP響應的頭和正文。任何知足了http.Handler
接口的對象均可做爲一個處理器。通俗的說,對象只要有個以下簽名的ServeHTTP
方法便可:bash
ServeHTTP(http.ResponseWriter, *http.Request)
Go 語言的 HTTP 包自帶了幾個函數用做經常使用處理器,好比FileServer
,NotFoundHandler
和 RedirectHandler
。咱們從一個簡單具體的例子開始:服務器
$ mkdir handler-example $ cd handler-example $ touch main.go
//File: main.go package main import ( "log" "net/http" ) func main() { mux := http.NewServeMux() rh := http.RedirectHandler("http://example.org", 307) mux.Handle("/foo", rh) log.Println("Listening...") http.ListenAndServe(":3000", mux) }
快速地過一下代碼:閉包
在 main
函數中咱們只用了 http.NewServeMux
函數來建立一個空的 ServeMux
。函數
而後咱們使用 http.RedirectHandler
函數建立了一個新的處理器,這個處理器會對收到的全部請求,都執行307重定向操做到 http://example.org
。測試
接下來咱們使用 ServeMux.Handle
函數將處理器註冊到新建立的 ServeMux
,因此它在 URL 路徑/foo
上收到全部的請求都交給這個處理器。編碼
最後咱們建立了一個新的服務器,並經過 http.ListenAndServe
函數監聽全部進入的請求,經過傳遞剛纔建立的 ServeMux
來爲請求去匹配對應處理器。.net
繼續,運行一下這個程序:
$ go run main.go Listening...
而後在瀏覽器中訪問 http://localhost:3000/foo
,你應該能發現請求已經成功的重定向了。
明察秋毫的你應該能注意到一些有意思的事情:ListenAndServer
的函數簽名是 ListenAndServe(addr string, handler Handler)
,可是第二個參數咱們傳遞的是個 ServeMux
。
咱們之因此能這麼作,是由於 ServeMux
也有 ServeHTTP
方法,所以它也是個合法的 Handler
。
對我來講,將 ServerMux
用做一個特殊的Handler是一種簡化。它不是本身輸出響應而是將請求傳遞給註冊到它的其餘 Handler。這乍一聽起來不是什麼明顯的飛躍 - 但在 Go 中將 Handler
鏈在一塊兒是很是廣泛的用法。
讓咱們建立一個自定義的處理器,功能是將以特定格式輸出當前的本地時間:
type timeHandler struct { format string } func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write([]byte("The time is: " + tm)) }
這個例子裏代碼自己並非重點。
真正的重點是咱們有一個對象(本例中就是個timerHandler
結構體,可是也能夠是一個字符串、一個函數或者任意的東西),咱們在這個對象上實現了一個 ServeHTTP(http.ResponseWriter, *http.Request)
簽名的方法,這就是咱們建立一個處理器所需的所有東西。
咱們把這個集成到具體的示例裏:
//File: main.go package main import ( "log" "net/http" "time" ) type timeHandler struct { format string } func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(th.format) w.Write([]byte("The time is: " + tm)) } func main() { mux := http.NewServeMux() th := &timeHandler{format: time.RFC1123} mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux) }
main
函數中,咱們像初始化一個常規的結構體同樣,初始化了timeHandler
,用 &
符號得到了其地址。隨後,像以前的例子同樣,咱們使用 mux.Handle
函數來將其註冊到 ServerMux
。
如今當咱們運行這個應用,ServerMux
將會將任何對 /time
的請求直接交給 timeHandler.ServeHTTP
方法處理。
訪問一下這個地址看一下效果:http://localhost:3000/time 。
注意咱們能夠在多個路由中輕鬆的複用 timeHandler
:
func main() { mux := http.NewServeMux() th1123 := &timeHandler{format: time.RFC1123} mux.Handle("/time/rfc1123", th1123) th3339 := &timeHandler{format: time.RFC3339} mux.Handle("/time/rfc3339", th3339) log.Println("Listening...") http.ListenAndServe(":3000", mux) }
對於簡單的狀況(好比上面的例子),定義個新的有 ServerHTTP
方法的自定義類型有些累贅。咱們看一下另一種方式,咱們藉助 http.HandlerFunc
類型來讓一個常規函數知足做爲一個 Handler
接口的條件。
任何有 func(http.ResponseWriter, *http.Request)
簽名的函數都能轉化爲一個 HandlerFunc
類型。這頗有用,由於 HandlerFunc
對象內置了 ServeHTTP
方法,後者能夠聰明又方便的調用咱們最初提供的函數內容。
若是你聽起來還有些困惑,能夠嘗試看一下[相關的源代碼]http://golang.org/src/pkg/net...。你將會看到讓一個函數對象知足 Handler
接口是很是簡潔優雅的。
讓咱們使用這個技術從新實現一遍timeHandler
應用:
//File: main.go package main import ( "log" "net/http" "time" ) func timeHandler(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(time.RFC1123) w.Write([]byte("The time is: " + tm)) } func main() { mux := http.NewServeMux() // Convert the timeHandler function to a HandlerFunc type th := http.HandlerFunc(timeHandler) // And add it to the ServeMux mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux) }
實際上,將一個函數轉換成 HandlerFunc
後註冊到 ServeMux
是很廣泛的用法,因此 Go 語言爲此提供了個便捷方式:ServerMux.HandlerFunc
方法。
咱們使用便捷方式重寫 main()
函數看起來是這樣的:
func main() { mux := http.NewServeMux() mux.HandleFunc("/time", timeHandler) log.Println("Listening...") http.ListenAndServe(":3000", mux) }
絕大多數狀況下這種用函數當處理器的方式工做的很好。可是當事情開始變得更復雜的時候,就會有些產生一些限制了。
你可能已經注意到了,跟以前的方式不一樣,咱們不得不將時間格式硬編碼到 timeHandler
的方法中。若是咱們想從 main()
函數中傳遞一些信息或者變量給處理器該怎麼辦?
一個優雅的方式是將咱們處理器放到一個閉包中,將咱們要使用的變量帶進去:
//File: main.go package main import ( "log" "net/http" "time" ) func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) } return http.HandlerFunc(fn) } func main() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux) }
timeHandler
函數如今有了個更巧妙的身份。除了把一個函數封裝成 Handler(像咱們以前作到那樣),咱們如今使用它來返回一個處理器。這種機制有兩個關鍵點:
首先是建立了一個fn
,這是個匿名函數,將 format
變量封裝到一個閉包裏。閉包的本質讓處理器在任何狀況下,均可以在本地範圍內訪問到 format
變量。
其次咱們的閉包函數知足 func(http.ResponseWriter, *http.Request)
簽名。若是你記得以前咱們說的,這意味咱們能夠將它轉換成一個HandlerFunc
類型(知足了http.Handler
接口)。咱們的timeHandler
函數隨後轉換後的 HandlerFunc
返回。
在上面的例子中咱們已經能夠傳遞一個簡單的字符串給處理器。可是在實際的應用中可使用這種方法傳遞數據庫鏈接、模板組,或者其餘應用級的上下文。使用全局變量也是個不錯的選擇,還能獲得額外的好處就是編寫更優雅的自包含的處理器以便測試。
你也可能見過相同的寫法,像這樣:
func timeHandler(format string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) }) }
或者在返回時,使用一個到 HandlerFunc
類型的隱式轉換:
func timeHandler(format string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) } }
你可能已經在不少地方看到過 DefaultServeMux
, 從最簡單的 Hello World
例子,到 go 語言的源代碼中。
我花了很長時間才意識到 DefaultServerMux
並無什麼的特殊的地方。DefaultServerMux
就是咱們以前用到的 ServerMux
,只是它隨着 net/httpp
包初始化的時候被自動初始化了而已。Go 源代碼中的相關行以下:
var DefaultServeMux = NewServeMux()
net/http
包提供了一組快捷方式來配合 DefaultServeMux
:http.Handle
和 http.HandleFunc
。這些函數與咱們以前看過的相似的名稱的函數功能同樣,惟一的不一樣是他們將處理器註冊到 DefaultServerMux
,而以前咱們是註冊到本身建立的 ServeMux
。
此外,ListenAndServe
在沒有提供其餘的處理器的狀況下(也就是第二個參數設成了 nil
),內部會使用 DefaultServeMux
。
所以,做爲最後一個步驟,咱們使用 DefaultServeMux
來改寫咱們的 timeHandler
應用:
//File: main.go package main import ( "log" "net/http" "time" ) func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(format) w.Write([]byte("The time is: " + tm)) } return http.HandlerFunc(fn) } func main() { // Note that we skip creating the ServeMux... var format string = time.RFC1123 th := timeHandler(format) // We use http.Handle instead of mux.Handle... http.Handle("/time", th) log.Println("Listening...") // And pass nil as the handler to ListenAndServe. http.ListenAndServe(":3000", nil) }