ServeMux簡介
ServeMux扮演的角色是Multiplexer,它用來將將請求根據url路由給已注冊的handler。如下圖:
上圖中為3個路徑注冊了handler,一個是"/",另外兩個是"/hello"和"/world"。這表示訪問http://hostname/hello
時,multiplexer會調用上圖中對應的第二個handler,當訪問http://hostname/world
時,multiplexer會調用上圖中對應的第三個handler,當不是這兩個路徑時,將調用第一個綁定在"/"上的handler。
注意,go的mux路由請求時,handler綁定的路徑是否帶尾隨"/"是不一樣的。帶上尾隨"/",表示此路徑以及此路徑下的子路徑,都會調用注冊在此路徑上的handler。
例如,當請求uri為"/hello/abc"的時候,不會調用"/hello"對應的handler,而是調用"/"對應的handler。只有注冊handler的路徑為"/hello/"時,uri為"/hello/abc"才會調用此handler。
實際上,當注冊handler的路徑帶上尾隨斜"/"時,在發起此路徑的請求時,會通過301重定向的方式自動補齊這個尾隨斜線,讓瀏覽器發起第二次請求。例如,下面是注冊handler的路徑:
http.Handle("/hello/", &myHandler)
發起http://hostname/hello
的請求時,會自動補齊為http://hostname/hello/
,然后瀏覽器自動發起第二次請求。
ServeMux的匹配規則
ServeMux對每次流入的http請求的URL進行模式(pattern)匹配,然后調用注冊在此pattern上的handler來處理這個請求。
Pattern部分可以定義為匹配host的模式。如果pattern以"/"開頭,表示匹配URL的路徑部分,如果不以"/"開頭,表示從host開始匹配。
匹配時選擇匹配匹配度最高(長匹配優先於短匹配)。例如為"/images/"注冊了handler1,"/images/thumbnails/"注冊了handler2,如果請求的URL路徑部分為"/images/thumbnails/",將會調用handler2處理這個請求,如果請求的URL路徑部分為"/images/foo/",將調用handler1處理。
注意,注冊在"/"上的pattern會在其它模式匹配不上時被選中,因為所有請求都可以匹配這個pattern,只不過能匹配到的長度最短。
如果pattern帶上了尾隨斜線"/",ServeMux將會對請求不帶尾隨斜線的URL進行301重定向。例如,在"/images/"模式上注冊了一個handler,當請求的URL路徑為"/images"時,將自動重定向為"/images/"。除非再單獨為"/images"模式注冊一個handler。
如果為"/images"注冊了handler,當請求URL路徑為"/images/"時,將無法匹配該模式。
ServeMux詳細解釋
看看net/http/server.go文件中ServeMux的結構:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
結構看上去很簡單。一個字段mu是RWMutex,m是注冊handler和pattern的,hosts用於判斷pattern是否包含了host的匹配。看看Handle()函數的定義會更清晰:
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
pattern為空或者handler為空時,都會panic。此外,想要定義重復的pattern,也會panic。如果pattern的第一個字符不是"/",則表示這個pattern是從主機名開始匹配的。
唯一需要注意的是,每個Handle()都會對ServeMux實例加上寫鎖。
以常用的DefaultServeMux為例,它是ServeMux的一個實例。當使用DefualtServeMux時,每調用一次Handle()或HandleFunc(),都意味着向這個DefaultServeMux的結構中的m字段添加pattern和對應的handler。由於加了寫鎖,如果使用多個goroutine同時啟動多個web服務,在同一時刻將只能有一個goroutine啟動的web服務能設置DefaultServeMux。當然,一般情況下不會使用goroutine的方式同時啟動多個web服務。
第三方ServeMux
自帶的默認的DefaultServeMux其實功能限制很大。比如請求的URL路徑為"/images/123.png",想要匹配這個確實容易,但是想要取出其中的"123.png"字符串,DefaultServeMux就沒法實現。
有一個非常強大的Gorilla工具包(www.gorillatoolkit.org),它有好幾個功能,其中一個功能是提供multiplexer。