美文网首页go
mux源码解读

mux源码解读

作者: 爱林林爱生活 | 来源:发表于2017-01-17 16:21 被阅读66次

    mux简介

    go语言中一个强大的web路由,在这里我准备理解其原理后实现一个python版的mux,同时也为了进一步学习go。

    0x01 Router浅析

    首先我们从一个简单的demo入手,来追踪mux的内部实现原理。新建一个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中可以看出,先简单的调用NewRouter方法实例化Router,默认KeepContextFalse,暂时猜测KeepContext应该跟keepalive选项有关。
    接下来调用Path方法,为Router简单的新建一个Route。函数实现是,NewRoute方法返回的是一个Route对象,然后再调用Route对象的Path方法,

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

    0x02 Route的实现

    NewRoute的函数实现如下,在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方法只是简单的调用addRegexpMatcher方法,给Route增加一个Match,实现如下:

    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。接下来是调用newRouteRegexp方法,根据request生成一个routeRegexp,最后再把生成的routeRegexp追加到Routematchers中,所以我们现在可以知道Route中的matchers对应的是一个routeRegexpaddRegexpMatcher函数实现如下:

    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对象的重中之重。函数实现和注释如下:

    func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
        // Check if it is well-formed.
        idxs, errBraces := braceIndices(tpl)   //获取大括号在tpl的下标
        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]+ 这样的字符串根据:切分,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注册一个回调函数。HandlerFunc函数实现是

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

    r.Handler(http.HandlerFunc(f))中, 再次调用了 RouteHandler函数, 其中http.HandlerFunc是一个类型。官网文档是这样写的,可以看出,对自定义的回调函数是需要进行这种转换的。

    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启动服务

    注册完单条路由后,就开始处理http请求啦。

    http.ListenAndServe(":8000", r)
    

    其函数签名是,

    func ListenAndServe(addr string, handler Handler) error
    

    而Handler是一个接口,它定义了ServeHTTP方法,而我们在ListenAndServe中把Router的一个实例传进去了。所以在这里会调用Router的ServeHTTP方法

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

    ServeHTTP实现如下

    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默认为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的函数签名中的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从哪里来呢?

    相关文章

      网友评论

        本文标题:mux源码解读

        本文链接:https://www.haomeiwen.com/subject/wqmmbttx.html