mux源碼解讀

mux簡介

go語言中一個強大的web路由,在這里我準(zhǔn)備理解其原理后實現(xiàn)一個python版的mux,同時也為了進(jìn)一步學(xué)習(xí)go。

0x01 Router淺析

首先我們從一個簡單的demo入手,來追蹤mux的內(nèi)部實現(xiàn)原理。新建一個main.go到go的工作目錄下。

package main

import (
"github.com/gorilla/mux"
"log"
"net/http"
  )

func YourHandler(w http.ResponseWriter, r *http.Request) {
      w.Write([]byte("Gorilla!\n"))
}

func HomeHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello Golang!\n"))
}

func main() {
    r := mux.NewRouter()
// Routes consist of a path and a handler function.
    r.Path("/").HandlerFunc(HomeHandler)
    r.Path("/articles/{category}/{id:[0-9]+}").
    HandlerFunc(YourHandler).
    Name("article")

// Bind to a port and pass our router in
    log.Fatal(http.ListenAndServe(":8000", r))
}

從demo中可以看出,先簡單的調(diào)用NewRouter方法實例化Router,默認(rèn)KeepContextFalse,暫時猜測KeepContext應(yīng)該跟keepalive選項有關(guān)。
接下來調(diào)用Path方法,為Router簡單的新建一個Route。函數(shù)實現(xiàn)是,NewRoute方法返回的是一個Route對象,然后再調(diào)用Route對象的Path方法,

func (r *Router) Path(tpl string) *Route {
    return r.NewRoute().Path(tpl)
}

0x02 Route的實現(xiàn)

NewRoute的函數(shù)實現(xiàn)如下,在Router中新建的每一條Route都是從Router中繼承一些選項的,如strctSlash,這樣比較方便支持多Router

func (r *Router) NewRoute() *Route {
    route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
    r.routes = append(r.routes, route)
    return route
}

RoutePath方法只是簡單的調(diào)用addRegexpMatcher方法,給Route增加一個Match,實現(xiàn)如下:

func (r *Route) Path(tpl string) *Route {
    r.err = r.addRegexpMatcher(tpl, false, false, false)
    return r
}

0x03 routeRegexp原理

addRegexpMatcher方法是Route對象的比較核心的部分,r.getRegexpGroup()方法和繼承父路由中的routeRegexpGroup或者新建一個空的routeRegexpGroup。接下來是調(diào)用newRouteRegexp方法,根據(jù)request生成一個routeRegexp,最后再把生成的routeRegexp追加到Routematchers中,所以我們現(xiàn)在可以知道Route中的matchers對應(yīng)的是一個routeRegexp。addRegexpMatcher函數(shù)實現(xiàn)如下:

func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
    if r.err != nil {
        return r.err
    }
    r.regexp = r.getRegexpGroup()
    if !matchHost && !matchQuery {
        if len(tpl) == 0 || tpl[0] != '/' {
            return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
        }
        if r.regexp.path != nil {
            tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
        }
    }
    rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
    if err != nil {
        return err
    }
    for _, q := range r.regexp.queries {
        if err = uniqueVars(rr.varsN, q.varsN); err != nil {
            return err
        }
    }
    if matchHost {
        if r.regexp.path != nil {
            if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
                return err
            }
        }
        r.regexp.host = rr
    } else {
        if r.regexp.host != nil {
            if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
                return err
            }
        }
        if matchQuery {
            r.regexp.queries = append(r.regexp.queries, rr)
        } else {
            r.regexp.path = rr
        }
    }
    r.addMatcher(rr)
    return nil
}

newRouteRegexp對象是Route對象的重中之重。函數(shù)實現(xiàn)和注釋如下:

func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
    // Check if it is well-formed.
    idxs, errBraces := braceIndices(tpl)   //獲取大括號在tpl的下標(biāo)
    if errBraces != nil {
        return nil, errBraces
    }
    // Backup the original.
    template := tpl
    // Now let's parse it.
    defaultPattern := "[^/]+"
    if matchQuery {
        defaultPattern = "[^?&]*"
    } else if matchHost {
        defaultPattern = "[^.]+"
        matchPrefix = false
    }
    // Only match strict slash if not matching
    if matchPrefix || matchHost || matchQuery {
        strictSlash = false
    }
    // Set a flag for strictSlash.
    endSlash := false
    if strictSlash && strings.HasSuffix(tpl, "/") {
        tpl = tpl[:len(tpl)-1]
        endSlash = true
    }
    varsN := make([]string, len(idxs)/2)
    varsR := make([]*regexp.Regexp, len(idxs)/2)
    pattern := bytes.NewBufferString("")
    pattern.WriteByte('^')
    reverse := bytes.NewBufferString("")
    var end int
    var err error
    for i := 0; i < len(idxs); i += 2 {
        // Set all values we are interested in.
        raw := tpl[end:idxs[i]]     // 沒有匹配到變量的字符串,如demo中的 articles
        end = idxs[i+1]
        parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)    //把如 id:[0-9]+ 這樣的字符串根據(jù):切分,id賦給name, [0-9]+賦給patt
        name := parts[0]
        patt := defaultPattern
        if len(parts) == 2 {
            patt = parts[1]
        }
        // Name or pattern can't be empty.
        if name == "" || patt == "" {
            return nil, fmt.Errorf("mux: missing name or pattern in %q",
                tpl[idxs[i]:end])
        }
        // Build the regexp pattern.
                    //格式化成如下形式 articles/(?P<v0>[^/]+)/(?P<v0>[0-9]+)
        fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)

        // Build the reverse template.
        fmt.Fprintf(reverse, "%s%%s", raw)

        // Append variable name and compiled pattern.
        varsN[i/2] = name
        varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
        if err != nil {
            return nil, err
        }
    }
    // Add the remaining.
    raw := tpl[end:]
    pattern.WriteString(regexp.QuoteMeta(raw))
    if strictSlash {
        pattern.WriteString("[/]?")
    }
    if matchQuery {
        // Add the default pattern if the query value is empty
        if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
            pattern.WriteString(defaultPattern)
        }
    }
    if !matchPrefix {
        pattern.WriteByte('$')
    }
    reverse.WriteString(raw)
    if endSlash {
        reverse.WriteByte('/')
    }
    // Compile full regexp.
    reg, errCompile := regexp.Compile(pattern.String())
    if errCompile != nil {
        return nil, errCompile
    }
    // Done!
    return &routeRegexp{
        template:       template,
        matchHost:      matchHost,
        matchQuery:     matchQuery,
        strictSlash:    strictSlash,
        useEncodedPath: useEncodedPath,
        regexp:         reg,
        reverse:        reverse.String(),
        varsN:          varsN,
        varsR:          varsR,
    }, nil
}

Route中HandlerFunc的作用

HandlerFuncRoute對象的方法,可以給一條Route注冊一個回調(diào)函數(shù)。HandlerFunc函數(shù)實現(xiàn)是

func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
    return r.Handler(http.HandlerFunc(f))
}

r.Handler(http.HandlerFunc(f))中, 再次調(diào)用了 RouteHandler函數(shù), 其中http.HandlerFunc是一個類型。官網(wǎng)文檔是這樣寫的,可以看出,對自定義的回調(diào)函數(shù)是需要進(jìn)行這種轉(zhuǎn)換的。

The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a >function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.

ListenAndServe啟動服務(wù)

注冊完單條路由后,就開始處理http請求啦。

http.ListenAndServe(":8000", r)

其函數(shù)簽名是,

func ListenAndServe(addr string, handler Handler) error

而Handler是一個接口,它定義了ServeHTTP方法,而我們在ListenAndServe中把Router的一個實例傳進(jìn)去了。所以在這里會調(diào)用Router的ServeHTTP方法

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

ServeHTTP實現(xiàn)如下

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {

if !r.skipClean {
    path := req.URL.Path
    if r.useEncodedPath {
        path = getPath(req)
    }
    // Clean path to canonical form and redirect.
    if p := cleanPath(path); p != path {

        // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
        // This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
        // http://code.google.com/p/go/issues/detail?id=5252
        url := *req.URL
        url.Path = p
        p = url.String()

        w.Header().Set("Location", p)
        w.WriteHeader(http.StatusMovedPermanently)
        return
    }
}
var match RouteMatch
var handler http.Handler
if r.Match(req, &match) {
    handler = match.Handler
    req = setVars(req, match.Vars)
    req = setCurrentRoute(req, match.Route)
}
if handler == nil {
    handler = http.NotFoundHandler()
}
if !r.KeepContext {
    defer contextClear(req)
}
handler.ServeHTTP(w, req)
}

其中skipCleanuseEncodedPath默認(rèn)為false。核心部分是從 r.Match(req, &match)開始的,Match方法定義如下,首先會遍歷Router中的所有路由route的Match方法,如有匹配到,則直接返回,否則返回NotFoundHandler。

func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
for _, route := range r.routes {
    if route.Match(req, match) {
        return true
    }
}

// Closest match for a router (includes sub-routers)
if r.NotFoundHandler != nil {
    match.Handler = r.NotFoundHandler
    return true
}
return false
}

注意到Match的函數(shù)簽名中的req和match都是指針,而這個match是從ServeHTTP方法傳過來的。

Match(req *http.Request, match *RouteMatch) bool

Route的Match方法定義如下

func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
    if r.buildOnly || r.err != nil {
        return false
    }
    // Match everything.
    for _, m := range r.matchers {

        if matched := m.Match(req, match); !matched {
            return false
        }
    }
    // Yay, we have a match. Let's collect some info about it.
    if match.Route == nil {
        match.Route = r
    }
    if match.Handler == nil {
        match.Handler = r.handler
    }
    if match.Vars == nil {
        match.Vars = make(map[string]string)
    }
    // Set variables.
    if r.regexp != nil {
        r.regexp.setMatch(req, match, r)
    }
    return true
}

核心部分是再次遍歷Routematchers,而matchers從哪里來呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,621評論 2 380

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