1. 程式人生 > >Go Web:自帶的ServeMux multiplexer

Go Web:自帶的ServeMux multiplexer

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。