从一个例子分析gorilla/mux源码

来源:互联网 发布:网络上很红的长发男模 编辑:程序博客网 时间:2024/05/21 17:51

最近终于闲下来,有时间阅读gorilla/mux。这个是我们在用Golang搭建Web服务器的时候经常用到的库,因此非常重要。这里是我阅读源码和参考了别人的博客之后得到一些总结。

使用的例子

我们从下面这个简单的代码开始追踪分析。

package mainimport ("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()    r.Path("/").HandlerFunc(HomeHandler)    r.Path("/articles/{category}/{id:[0-9]+}").    HandlerFunc(YourHandler).    Name("article")    log.Fatal(http.ListenAndServe(":8000", r))}

从例子中可以看出,我们定义了两个处理函数,然后用NewRouter实例化了一个Router,接着设置相应路径对应的处理函数,并启动端口进行监听。这个过程非常简单。

Router实现

Router是一个结构体,如下:

type Router struct {    // Configurable Handler to be used when no route matches.    NotFoundHandler http.Handler    // Configurable Handler to be used when the request method does not match the route.    MethodNotAllowedHandler http.Handler    // Parent route, if this is a subrouter.    parent parentRoute    // Routes to be matched, in order.    routes []*Route    // Routes by name for URL building.    namedRoutes map[string]*Route    // See Router.StrictSlash(). This defines the flag for new routes.    strictSlash bool    // See Router.SkipClean(). This defines the flag for new routes.    skipClean bool    // If true, do not clear the request context after handling the request.    // This has no effect when go1.7+ is used, since the context is stored    // on the request itself.    KeepContext bool    // see Router.UseEncodedPath(). This defines a flag for all routes.    useEncodedPath bool}

我们调用了NewRouter来实例化一个Router,而在源码中,NewRouter函数只有简单的一行代码。

// NewRouter returns a new router instance.func NewRouter() *Router {    return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}}

这里可以看见,它开辟了一个装Route指针的map,然后默认该Router的KeepContextfalse,意思是在请求被处理完之后清除该请求的上下文。

接下来我们调用了Router的Path方法,为Router新建一个Route。Router的Path方法源码如下:

// Path registers a new route with a matcher for the URL path.// See Route.Path().func (r *Router) Path(tpl string) *Route {    return r.NewRoute().Path(tpl)}

可以看到,这个函数调用NewRoute返回一个Route对象,然后再调用Route的Path方法。

// NewRoute registers an empty route.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}

Route实现

上面我们调用了Route的Path函数,那么让我们来看看Route的Path函数的源码。

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

可以看到,这个函数很简单地调用了addRegexpMatcher函数,加入一个Match到Route中。
能够支持正则路由是gorilla/mux的一个很重要的特性,因为原生的http.ServerMux并不支持正则路由。

// addRegexpMatcher adds a host or path matcher and builder to a route.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}

addRegexpMatcher方法是Route对象的比较核心的部分.
r.getRegexpGroup()方法继承父路由中的routeRegexpGroup或者新建一个空的routeRegexpGroup
接下来是调用newRouteRegexp方法,根据request生成一个routeRegexp,最后再把生成的routeRegexp追加到Route的matchers中,所以我们现在可以知道Route中的matchers对应的是一个routeRegexp

其中,newRouteRegexp是一个很重要的方法。源码如下,代码中有中文注释:

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]] // 没有匹配到变量的字符串,如例子中的 articles        end = idxs[i+1]        //把如 id:[0-9]+ 这样的字符串根据:切分,id赋给name, [0-9]+赋给patt        parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)        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    }    // Check for capturing groups which used to work in older versions    if reg.NumSubexp() != len(idxs)/2 {        panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +            "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")    }    // Done!    return &routeRegexp{        template:       template,        matchHost:      matchHost,        matchQuery:     matchQuery,        strictSlash:    strictSlash,        useEncodedPath: useEncodedPath,        regexp:         reg,        reverse:        reverse.String(),        varsN:          varsN,        varsR:          varsR,    }, nil}

说完Path的一系列调用,我们来看HandlerFunc
HandlerFunc是Route对象的方法,可以给一条Route注册一个回调函数。

// HandlerFunc sets a handler function for the route.func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {    return r.Handler(http.HandlerFunc(f))}

r.Handler(http.HandlerFunc(f))中, 再次调用了 Route的 Handler函数, 其中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来监听端口。而该函数第二个参数应该传如一个Handler类型的变量。我们知道,Handler是一个接口,它要求实现ServeHTTP方法。我们传了一个Router的实例进去,为什么可以呢?这是因为Router里面实现了ServeHTTP的方法。实现如下:

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {    if !r.skipClean {        path := req.URL.Path        if r.useEncodedPath {            path = req.URL.EscapedPath()        }        // 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 && match.MatchErr == ErrMethodMismatch {        handler = methodNotAllowedHandler()    }    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        }    }    if match.MatchErr == ErrMethodMismatch {        if r.MethodNotAllowedHandler != nil {            match.Handler = r.MethodNotAllowedHandler            return true        } else {            return false        }    }    // Closest match for a router (includes sub-routers)    if r.NotFoundHandler != nil {        match.Handler = r.NotFoundHandler        match.MatchErr = ErrNotFound        return true    }    match.MatchErr = ErrNotFound    return false}

从这个例子追踪的源码分析到这里就结束了,但是,gorilla/mux仍然有很多的部分我没有阅读到。并且,gorilla/mux仍然在不断修改完善,所以过一段时间看到的源码和现在可能又不一样了。
这是一个很简单的例子,希望以后有机会再深入研究。

原创粉丝点击