- 原文地址:www.alexedwards.net/blog/a-reca…
- 原文做者:Alex Edwards
- 譯文地址:github.com/watermelo/d…
- 譯者:咔嘰咔嘰
- 譯者水平有限,若有翻譯或理解謬誤,煩請幫忙指出
使用 Go 處理 HTTP 請求主要涉及兩件事:ServeMuxes 和 Handlers。git
ServeMux本質上是一個 HTTP 請求路由器(或多路複用器)。它將傳入的請求與預約義的 URL 路徑列表進行比較,並在找到匹配時調用路徑的關聯 handler。github
handler 負責寫入響應頭和響應體。幾乎任何對象均可以是 handler,只要它知足http.Handler接口便可。在非專業術語中,這僅僅意味着它必須是一個擁有如下簽名的ServeHTTP
方法:golang
ServeHTTP(http.ResponseWriter, *http.Request)
數據庫
Go 的 HTTP 包附帶了一些函數來生成經常使用的 handler,例如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。/foo
的全部傳入請求的handler。繼續運行應用程序:函數
$ go run main.go
Listening...
複製代碼
並在瀏覽器中訪問http://localhost:3000/foo。你會發現請求已經被成功重定向。測試
你可能已經注意到了一些有趣的東西:ListenAndServe 函數的簽名是ListenAndServe(addr string, handler Handler)
,但咱們傳遞了一個 ServeMux 做爲第二個參數。ui
能這麼作是由於 ServeMux 類型也有一個 ServeHTTP 方法,這意味着它也知足 Handler 接口。
對我而言,它只是將 ServeMux 視爲一種特殊的 handler,而不是把響應自己經過第二個 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))
}
複製代碼
這裏確切的代碼並不過重要。
真正重要的是咱們有一個對象(在該示例中它是一個timeHandler
結構,它一樣能夠是一個字符串或函數或其餘任何東西),而且咱們已經實現了一個帶有簽名ServeHTTP(http.ResponseWriter, *http.Request)
的方法。這就是咱們實現一個 handler 所需的所有內容。
讓咱們將其嵌入一個具體的例子中:
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
函數將其註冊到咱們的ServeMux。
如今,當咱們運行應用程序時,ServeMux會將任何經過/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)
}
複製代碼
對於簡單的狀況(如上例),定義新的自定義類型和 ServeHTTP 方法感受有點囉嗦。讓咱們看看另外一個方法,咱們利用 Go 的http.HandlerFunc類型來使正常的函數知足 Handler 接口。
任何具備簽名func(http.ResponseWriter, *http.Request)
的函數均可以轉換爲 HandlerFunc 類型。這頗有用,由於 HandleFunc 對象帶有一個內置的ServeHTTP
方法 - 這很是巧妙且方便 - 執行原始函數的內容。
若是這聽起來使人費解,請嘗試查看相關的源代碼。你將看到它是一種讓函數知足 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提供了一個快捷的轉換方法:mux.HandleFunc方法。
若是咱們使用這個轉換方法,main()
函數將是這個樣子:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/time", timeHandler)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}
複製代碼
大多數時候使用這樣的 handler 頗有效。可是當事情變得愈來愈複雜時,將會受限。
你可能已經注意到,與以前的方法不一樣,咱們必須在timeHandler
函數中對時間格式進行硬編碼。當咱們想要將信息或變量從main()
傳遞給 handler 時會發生什麼?
一個簡潔的方法是將咱們的 handler 邏輯放入一個閉包中,把咱們想用的變量包起來:
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,而不是將函數強制轉換爲handler(就像咱們以前所作的那樣)。能這麼作有兩個關鍵點。
首先它建立了一個匿名函數fn
,它訪問造成閉包的format
變量。不管咱們如何處理閉包,它老是可以訪問它做用域下所建立的局部變量 - 在這種狀況下意味着它老是能夠訪問format
變量。
其次咱們的閉包有簽名爲func(http.ResponseWriter, *http.Request)
的函數。你可能還記得,這意味着咱們能夠將其轉換爲HandlerFunc類型(以便它知足Handler接口)。而後咱們的timeHandler
函數返回這個轉換後的閉包。
在這個例子中,咱們僅僅將一個簡單的字符串傳遞給handler。但在實際應用程序中,您可使用此方法傳遞數據庫鏈接,模板映射或任何其餘應用程序級的上下文。它是全局變量的一個很好的替代方案,而且可使測試的自包含handler變得更整潔。
你可能還會看到相同的模式,以下所示:
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 源代碼。
我花了很長時間才意識到它並不特別。 DefaultServeMux 只是一個普通的 ServeMux,就像咱們已經使用的那樣,默認狀況下在使用 HTTP 包時會實例化。如下是 Go 源代碼中的相關行:
var DefaultServeMux = NewServeMux()
複製代碼
一般,你不該使用 DefaultServeMux,由於它會帶來安全風險。
因爲 DefaultServeMux 存儲在全局變量中,所以任何程序包均可以訪問它並註冊路由 - 包括應用程序導入的任何第三方程序包。若是其中一個第三方軟件包遭到破壞,他們可使用 DefaultServeMux 向 Web 公開惡意 handler。
所以,根據經驗,避免使用 DefaultServeMux 是一個好主意,取而代之使用你本身的本地範圍的 ServeMux,就像咱們到目前爲止同樣。但若是你決定使用它……
HTTP 包提供了一些使用 DefaultServeMux 的便捷方式:http.Handle和http.HandleFunc。這些與咱們已經看過的同名函數徹底相同,不一樣之處在於它們將 handler 添加到 DefaultServeMux 而不是你本身建立的 handler。
此外,若是沒有提供其餘 handler(即第二個參數設置爲nil
),ListenAndServe 將退回到使用 DefaultServeMux。
所以,做爲最後一步,讓咱們更新咱們的 timeHandler 應用程序以使用 DefaultServeMux:
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)
}
複製代碼
若是你喜歡這篇博文,請不要忘記查看個人新書《用 Go 構建專業的 Web 應用程序》!
在推特上關注我 @ajmedwards。
此文章中的全部代碼均可以在MIT Licence許可下無償使用。