從http.ListenAndServe開始看源碼(二)

handler的存儲及匹配

前言

我們在前一節的http ListenAndServe相關的源碼中,我們簡單提到了在HandleFunc及DefaultServeMux中完成pattern及handler的存儲及匹配,具體的過程我們現在詳細研究下。

一、ServerMux-結構

ServerMux是路由pattern及handler存儲的結構,請求路由的匹配也是在此中的func中完成的。

type ServeMux struct {
    mu    sync.RWMutex//讀寫鎖
    m     map[string]muxEntry//pattern及handler存儲map
    hosts bool // whether any patterns contain hostnames(是否包含hostnames)
}

type muxEntry struct {
    explicit bool    //當前pattern是否註冊
    h        Handler //我們自定義處理的handler
    pattern  string  //我們定義的pattern
}

ServeMux主要的存儲機制就是一個map[string]muxEntry,key存儲pattern,value 存儲一個包含handler及pattern等信息的muxEntry。mu作爲讀寫鎖,爲了保證map的讀寫安全。

二、HandleFunc-存儲

http.HandleFunc->DefaultServeMux.HandleFunc->mux.Handle

這幾步初步是完成了轉入DefaultServeMux處理及handler的HandleFunc轉換,然後調用mux.Handle具體處理。

mux.Handle的源碼如下:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()//寫操作時加鎖
    defer mux.mu.Unlock()//完成後解鎖

    if pattern == "" {
        panic("http: invalid pattern " + pattern)
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)//初始化map
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}//添加pattern至mux.m中,並以pattern爲key

    if pattern[0] != '/' {//如果不以'/'開頭,則帶有host
        mux.hosts = true
    }

    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    //處理'/'結尾的pattern,可以被結尾不帶'/'的同名pattern覆蓋
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
        // If pattern contains a host name, strip it and use remaining
        // path for redirect.
        path := pattern
        if pattern[0] != '/' {
            // In pattern, at least the last character is a '/', so
            // strings.Index can't be -1.
            path = pattern[strings.Index(pattern, "/"):]//path必須以'/'開頭
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}//添加不帶'/'的pattern的handler,並重定向至帶'/'結尾的pattern。如果請求時不帶'/',則會重定向至帶'/',查看請求的header可以在Referer中查看到原始的請求url
    }
}

總結:

以上就是一個簡單的map操作過程,不過添加了對pattern的處理:

1.針對pattern及handler的要求不可爲零值,且同一pattern且不可重複添加。

2.直接添加pattern及對應的handler,pattern不以’/'開頭則存在host。

3.如果pattern及’/‘結尾,且未存儲不帶’/‘的同樣pattern,則添加不帶’/‘的pattern的handler,並重定向至帶’/‘結尾的pattern。注意,如果後面還存在不帶’/'的pattern,會覆蓋此條信息。

三、ServeHTTP-匹配

serverHandler{c.server}.ServeHTTP(w, w.req)->handler.ServeHTTP(rw, req)->(mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

以上幾步主要是將server、請求r及響應w經過轉換轉入ServeMux的ServeHTTP處理(轉換的邏輯見之前的分析文章)。

ServeMux的ServeHTTP源碼如下:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {//原始請求URI爲*
        if r.ProtoAtLeast(1, 1) {//http協議版本1.1及以上時添加header參數
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)//返回400
        return
    }
    //正常請求
    h, _ := mux.Handler(r)//獲取請求對應的handler
    h.ServeHTTP(w, r)//調用handler處理
}

Handler源碼如下:

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {//請求方式非CONNECT時
        if p := cleanPath(r.URL.Path); p != r.URL.Path {//cleanPath獲取絕對路徑,如果pattern不是絕對路徑格式,則重定向絕對路徑的格式處理
            _, pattern = mux.handler(r.Host, p)
            url := *r.URL
            url.Path = p
            return RedirectHandler(url.String(), StatusMovedPermanently), pattern
        }
    }
    //絕對路徑或CONNECT請求
    return mux.handler(r.Host, r.URL.Path)
}

handler源碼如下:

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()//讀鎖
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {//存在host時,匹配路徑
        h, pattern = mux.match(host + path)
    }
    if h == nil {//未匹配,則只匹配請求絕對路徑
        h, pattern = mux.match(path)
    }
    if h == nil {//仍未匹配,則返回404
        h, pattern = NotFoundHandler(), ""
    }
    return
}

match源碼如下:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {//path是否匹配存儲map中的key
            continue
        }
        if h == nil || len(k) > n {//獲取最長的匹配的pattern及對應的handler
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

pathMatch源碼如下:

//比較請求存儲支持的pattern及請求的路徑
// Does path match pattern?
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//處理以'/'結尾的patten比較,path必須以pattern開頭,注意此處,只要求請求的path以pattern爲前綴即可,即/tree/list的請求會匹配到/tree/,所以如果要求精確匹配pattern儘量不以'/'結尾
}

總結:

以上調用過程雖然看起來長,實際上也還是map的取值過程O(∩_∩)O哈哈~。主要針對請求url的處理如下:

1.如果存在host,先匹配host+path;

2.如果不存在host或者host匹配結果爲空,則匹配path。

具體的匹配邏輯:

1.如果存儲的pattern不是以’/'結尾,則請求path必須與pattern完全一致才能匹配成功。

2.如果存儲的pattern是以’/'結尾,則請求path必須以pattern爲前綴即可,此處會存在多個匹配的情況,如:

/tree/list/及/tree/的請求都會匹配到/tree/這個pattern。

3.如果存在多個匹配的情況,則以匹配到最長的pattern(匹配度最高)爲最終匹配結果。

如:存在/tree/list/這個pattern時,請求/tree/list/最終會匹配到/tree/list/,雖然中間也會匹配到/tree/這個pattern。

四、總結

golang底層的pattern的存儲與匹配是基於簡易的map結構來完成,其實第三方的基於golang的網絡架構也是基於這一套,不一樣的是map的複雜度及pattern的自由度等地方,如果你感興趣的話,不妨去看看一些框架的源碼。

從以上的源碼分析中,我們可以瞭解到對我們代碼有益的地方,如:

1.使用HandleFunc時pattern儘量不以’/'結尾,可以調高存儲及匹配的效率,同時,可以避免誤匹配的情況。

2.map存在讀寫都有的情況,使用讀寫鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章