golang http 路由

來看看 http 自帶的路由功能摆尝。

閱讀前留幾個問題

  • 如果要自己實現(xiàn)路由,該怎么做逻恐?
  • 默認(rèn)路由的結(jié)構(gòu)像吻,搜索方式是什么?

例子

我們經(jīng)常在示例中看到如下兩種寫法复隆,示例來自于 stackoverflow

// 使用 DefaultServeMux
func main() {
  http.HandleFunc("/page2", Page2)
  http.HandleFunc("/", Index)
  http.ListenAndServe(":3000", nil)
}

// 使用 ServeMux 路由
func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/page2", Page2)
  mux.HandleFunc("/", Index)
  http.ListenAndServe(":3000", mux)
}

其實通過代碼就可以發(fā)現(xiàn)拨匆,http 包直接調(diào)用 HandleFunc 使用的是DefaultServeMux,我們可以看到最后調(diào)用 ListenAndServe挽拂,后面一個參數(shù)傳遞的是 nil惭每,會讓 server 使用 DefaultServeMux,通過分析代碼可以知道亏栈,DefaultServeMux 和 ServeMux的結(jié)構(gòu)台腥,path解析都是一樣的。唯一的區(qū)別在于下面绒北,

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

可以看到這個 DefaultServeMux 是一個全局變量黎侈,那么意味這有安全風(fēng)險,任何其他的第三方包都可以添加路由闷游。這個存在的意義據(jù)介紹是為了一些自定義功能峻汉,我們大部分場景都可以不使用這個方法贴汪。那么從這里可以看出,如果我們要實現(xiàn)一個路由休吠,得滿足下面的接口扳埂。然后傳遞到 ListenAndServe 函數(shù)的第二個參數(shù)就行了。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

結(jié)構(gòu)介紹

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool // hostname 路由
}

type muxEntry struct {
    explicit bool  // 是否注冊過
    h        Handler // 處理函數(shù)
    pattern  string // 路由 path 和 key 一致
}

看到上面的結(jié)構(gòu)蛛碌,可以知道路由的節(jié)點是使用 hash 存儲的聂喇,key 為路由 path,value 為節(jié)點蔚携,其結(jié)構(gòu)為 muxEntry希太。

路由注冊

我們直接看路由注冊部分的代碼。

// Handle 為給定路由 path 注冊 handler酝蜒。
// 如果該路由 path 的 handler 已存在會在編譯時 panic誊辉。即 mux.m[pattern].explicit
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)
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }

    // 如果 pattern 是 /tree/,則為 /tree 插入隱式永久重定向亡脑。
    // 這個規(guī)則可以被覆蓋堕澄。
    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, "/"):]
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
    }
}

從代碼可以了解到,其實就是把路由信息放在了 hash 結(jié)構(gòu)里霉咨,最后對 /tree 這種方式做了一些小優(yōu)化蛙紫,使它重定向到 /tree/。

路由查找

從請求分析路徑可以知到路由的查找源頭途戒,如下

// ServeHTTP 調(diào)度請求給已注冊模式最匹配 URL 的 handler
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

再往下找坑傅,我們可以知道最終的匹配規(guī)則,

// 通過給定的 path 從 handler hash 結(jié)構(gòu)中查找 handler
// 最長模式優(yōu)先
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    // 遍歷
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        // len(k) > n 即還能找到更長的 pattern喷斋,滿足最長模式優(yōu)先
        if h == nil || len(k) > n {
            n = len(k)
            // 返回 handler
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

// 匹配
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
    }
    // 是否匹配唁毒,mux 的 key(pattern)和給定的 path比較,按照最長原則
    return len(path) >= n && path[0:n] == pattern
}

回顧問題

  • 如果要自己實現(xiàn)路由星爪,該怎么做浆西?
    我們得實現(xiàn) Handler 接口,即實現(xiàn)簽名為ServeHTTP(ResponseWriter, *Request)的方法顽腾。

  • 默認(rèn)路由的結(jié)構(gòu)近零,搜索方式是什么?
    默認(rèn)路由用的 ServeMux 結(jié)構(gòu)崔泵,用 hash 存儲了 path 和 handler秒赤。查找的時候會遍歷這個 hash 結(jié)構(gòu),采用最長匹配的原則去查找 handler憎瘸,當(dāng)出現(xiàn) /tree/ 和 /tree/sub/ 同時注冊時,/tree/sub/ 會用 /tree/sub/ 注冊的 handler 而不是 /tree/ 注冊的 handler陈瘦。同時幌甘,當(dāng) /tree/ 沒有子路由注冊時, /tree/ 的 handler 會匹配所有 /tree/sub/ 等這種子路由,一定程度滿足了 * 這樣的匹配模式锅风。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酥诽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子皱埠,更是在濱河造成了極大的恐慌肮帐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件边器,死亡現(xiàn)場離奇詭異训枢,居然都是意外死亡,警方通過查閱死者的電腦和手機忘巧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門恒界,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砚嘴,你說我怎么就攤上這事十酣。” “怎么了际长?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵耸采,是天一觀的道長。 經(jīng)常有香客問我工育,道長虾宇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任翅娶,我火速辦了婚禮文留,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竭沫。我一直安慰自己燥翅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布蜕提。 她就那樣靜靜地躺著森书,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谎势。 梳的紋絲不亂的頭發(fā)上凛膏,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音脏榆,去河邊找鬼猖毫。 笑死,一個胖子當(dāng)著我的面吹牛须喂,可吹牛的內(nèi)容都是我干的吁断。 我是一名探鬼主播趁蕊,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仔役!你這毒婦竟也來了掷伙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤又兵,失蹤者是張志新(化名)和其女友劉穎任柜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沛厨,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡宙地,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俄烁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绸栅。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖页屠,靈堂內(nèi)的尸體忽然破棺而出粹胯,到底是詐尸還是另有隱情,我是刑警寧澤辰企,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布风纠,位于F島的核電站,受9級特大地震影響牢贸,放射性物質(zhì)發(fā)生泄漏竹观。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一潜索、第九天 我趴在偏房一處隱蔽的房頂上張望臭增。 院中可真熱鬧,春花似錦竹习、人聲如沸誊抛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拗窃。三九已至,卻和暖如春泌辫,著一層夾襖步出監(jiān)牢的瞬間随夸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工震放, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宾毒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓殿遂,卻偏偏與公主長得像伍俘,于是被迫代替她去往敵國和親邪锌。 傳聞我的和親對象是個殘疾皇子勉躺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容