這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
ServeMux解析
- 總覽
- ServeMux結構體
- NewServeMux
- pathMatch
- * ServeMux.Handler
- * ServeMux.handler
- * ServeMux.Handle
- * ServeMux.ServeHTTP
總覽
本來是想做一個UML出來讓這篇解析更清晰一點的,但是markdown的UML文法我一直搗鼓不出來。試了幾個軟體感覺也沒有
想象中的好用和方便,看來是時候自己開發一個了(笑).
口述一個流程,具體的函數大家可以頁內跳轉去看.
首先我們是通過ListenAndServe來監聽本地連接埠的,之後ListenAndServe將收到的建立一個Response連同收到的Request
作為參數調用ServeMux結構體的ServeHTTP(省略了中間過程).ServeHTTP將Request作為參數調用
Handler函數,Handler的傳回值為一個Handler類型的介面,ServeHTTP會調用介面實現的ServeHTTP處理Response.
如果Request.URL.Path中有不合法的內容,則調用cleanPath清理,隨後將Request.Host以及清理後的
內容傳入handler函數,隨後返回一個RedirectHandler以及handler所返回的路徑。如果Request.URL.Path合法,那麼
直接調用handler,傳回值與handler傳回值相同。
handler中通過判斷ServeMux.hosts來決定是否實現pattern = r.Host + r.URL.Path.之後將pattern作為參數調用match,並將
match的傳回值返回.
match的判別方式比較"有趣",它雖然沒實現為樹形結構(只是用了映射),但是搜尋的方法就是樹形,因為URL路徑就是個樹形.它按照樹的根節點
與子節點的關係進行判斷,譬如路徑"/home/select/usercourse",match在匹配的時候會首先匹配到"/"(假如我們註冊了),其次是"/home",
之後逐層匹配下來,假如我們沒註冊過"/home/select/usercourse",但是註冊了"/home/select/",那麼match就會匹配到這一層.然後返回
"/home/select/"的Handler以及url(pattern).match函數的匹配規則實現在pathMatch
ServeMux結構體
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames }type muxEntry struct { explicit bool h Handler pattern string }
這樣看起來還蠻直觀的,mu是一個互斥鎖,m則是我們所要用的路由(router),其中的鍵(key)則是我們掛載的路徑,
對應的值則是響應的muxEntry,hosts指明了我們是否在每個路徑中都聲明了hostnames.比如我們平常用'/'來表示
掛載在網域名稱根目錄下的HandlerFunc,但是如果hosts=true,那麼我們就需要'127.0.0.1/'來做同樣的事情了.
下面我們來看muxEntry,h比較明確,就是我們註冊的處理過程.pattern與ServeMux中m的key相同,也就是說是我們
註冊的路徑,而explicit的用途其實是比較隱晦的。程式會根據explicit判別這個路徑的Handler是否是使用者註冊的。
如果是程式為了Redirect(詳細點擊這裡)而設定的,那麼在它是可以被覆蓋的,否則就是不可以被覆蓋的.
NewServeMux()
DefaultServeMux變數就是直接調用的這個函數。那麼這個函數只make了一個變數m,也就是為map分配了空間。
在golang的文法中,結構體裡未聲明的變數也是存在的(即分配了記憶體),只不過是該類型的預設值,比如hosts就
被設定成了false.這個問題不過多解釋,有興趣的話可以看一下golang中的相關內容
pathMatch()
這個函數是ServeMux用來匹配路徑的主要函數,所以看一下策略還是很重要的.
函數中的參數pattern是我們註冊的路徑,path是使用者請求的路徑
func pathMatch(pattern, path string) bool { if len(pattern) == 0 { // should not happen return false } n := len(pattern) if pattern[n-1] != '/' { return pattern == path } return len(path) >= n && path[0:n] == pattern}
如果我們掛載的路徑不是以'/'結尾的,那麼就直接判斷兩個參數是否相同。如果是以'/'結尾的,只要path的路徑包含
pattern那麼就被判定是匹配。也就是說,如果我註冊了/home/select/,那麼/home/select/hello也會被定位到/home/select/
掛載的HandlerFunc上.這樣做相當於為路徑設定了一個index,不符合規則的URL都會被Redirect到這個index上
* ServeMux.Handler()
注釋寫的很清楚,這個函數就是處理URL,然後調用*ServeMux.handler().首先調用cleanPath清理請求URL中的不合法內容。如果存在不合法內容,
則將清理過的URL交由*ServeMux.handler()處理並獲得匹配到的pattern,然後修改url.Path的內容並調用RedirectHandler.
如果內容合法,則直接調用*ServeMux.handler()並返回結果
* ServeMux.handler()
調用ServeMux.match()(封裝了pathMatch函數)來獲得匹配到的Handler以及對應pattern,如果ServeMux.hosts==true,那麼
傳入的參數為host + path,如果找不到的話,調用NotFoundHandler函數,並將其結果返回.
* ServeMux.Handle()
Handle函數是用來註冊路徑與處理過程的.如果該路徑已經存在了一個使用者註冊的Handler則會panic(意思就是說不支援覆蓋).判別了合法參數以後就將
pattern作為key,建立一個muxEntry類型變數作為value加入到map中。
if pattern[0] != '/' { mux.hosts = true}
這是這個函數中比較有意思的一個部分,通過這裡我們可以看到如果註冊路徑的時候並不是以'/'開頭的,那麼ServeMux就會開啟hosts,然後會在
請求到達的時候將URL.Host和URL.Path串連在一起放入match中尋找,具體資訊請看這裡
接下來是關於路徑的處理,也就是關於"/home"與"/home/"的區別.我們先來看看作者怎麼說
// Helpful behavior: // If pattern is /tree/, insert an implicit permanent redirect for /tree. // It can be overridden by an explicit registration.
如果路徑的末尾是以'/'結尾並且該路徑去掉末尾的'/'以後並沒有被註冊.那麼將會去掉'/'並且為其綁定一個Redirect到現在的路徑.
我自己寫起來都覺得繞,舉個例子就清楚了.
我註冊了一個路徑"/home/",但是沒有註冊"/home",那麼如果使用者訪問了"/home"會發生什麼呢?是的,會被Redirect到"/home/".
需要注意的是,這裡的muxEntry中的explicit沒有填,也就是說是false,那麼即是可以覆蓋的.
* ServeMux.ServeHTTP()
ServeHTTP會檢測非法的URI(* )
如果通過檢測就會調用自身的Handler()來返回註冊的Handler,隨後調用Handler的ServeHTTP方法