`DefaultServeMux` 与 `gorilla/mux`部分源码阅读

DefaultServeMuxgorilla/mux 对比阅读

Table of Contents

DefaultServeMux 的实现

查阅 net.http 的源码,可以得知 DefaultServeMux 的实现如下:DefaultServeMux 是库中 ServeMux 结构体的一个默认实例 defaultServeMux 的指针。

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

ServeMux 结构体的定义如下。其中,m 字段是一个 map(路由表),实现了用户请求中 path 映射到 muxEntrymuxEntryh 字段存放了对应的 Handler 处理方法,从而实现了路径到处理方法的映射。

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

muxEntry 中的 Handler 是一个接口,其中包含了 ServeHTTP 用于处理 http 请求。调用 HandlerFunc 时可以给 DefaultServeMux 对特定的 pattern 增加对应的 handler

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

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

路由器处理 http 请求时,会调用 match 函数匹配相应的处理方法。若可以成功匹配,返回对应的处理方法;否则,最长有效匹配的处理方法:

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

gorilla/mux 的实现

gorilla/mux 定义的路由器如下。其中,最主要的字段为 routes 切片,该切片存储了包括了路由的约束条件以及处理函数的路由信息:

type Router struct {
    NotFoundHandler http.Handler
    MethodNotAllowedHandler http.Handler
    routes []*Route
    namedRoutes map[string]*Route
    KeepContext bool
    middlewares []middleware
    routeConf
}

type Route struct {
    handler http.Handler
    buildOnly bool
    name string
    err error
    namedRoutes map[string]*Route
    routeConf
}

当服务器接收到请求后,路由器会遍历路由表进行匹配。若匹配成功,函数返回真值;否则,判断默认的处理器 NotFoundHandler 是否为空。NotFoundHandler 不为空可以进行处理,返回 true;否则,返回 false

func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
    for _, route := range r.routes {
        if route.Match(req, match) {
            if match.MatchErr == nil {
                for i := len(r.middlewares) - 1; i >= 0; i-- {
                    match.Handler = r.middlewares[i].Middleware(match.Handler)
                }
            }
            return true
        }
    }
    if match.MatchErr == ErrMethodMismatch {
        if r.MethodNotAllowedHandler != nil {
            match.Handler = r.MethodNotAllowedHandler
            return true
        }
        return false
    }
    if r.NotFoundHandler != nil {
        match.Handler = r.NotFoundHandler
        match.MatchErr = ErrNotFound
        return true
    }
    match.MatchErr = ErrNotFound
    return false
}

此外,gorilla/mux 实现了正则表达式的相关功能,其中一个示例如下:

func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
    if r.err != nil {
        return r.err
    }
    if typ == regexpTypePath || typ == regexpTypePrefix {
        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, typ, routeRegexpOptions{
        strictSlash:    r.strictSlash,
        useEncodedPath: 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 typ == regexpTypeHost {
        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 typ == regexpTypeQuery {
            r.regexp.queries = append(r.regexp.queries, rr)
        } else {
            r.regexp.path = rr
        }
    }
    r.addMatcher(rr)
    return nil
}

DefaultServeMuxgorilla/mux 的比较

DefaultServeMux:

  • 不支持正则表达式进行路由,只支持路径匹配
  • 路由过程使用顺序遍历的方式进行匹配,时间复杂度较高

gorilla/mux:

  • 实现了 http.Handler 接口,与标准库的 http.ServeMux 兼容
  • 请求可以基于 URL 主机、路径、路径前缀、scheme、请求头、请求参数、请求方法或其他匹配方法进行路由匹配
  • URL 主机、路径、查询字符串支持可选的正则匹配
  • 支持构建或反转已注册的 URL 主机,以便维护对资源的引用
  • 支持路由嵌套,以便不同路由可以共享通用条件,比如主机、路径前缀或其他重复的属性。通过这个功能可以优化请求的匹配速率
上一篇:Go Web编程(三——Web基础)


下一篇:Linux数组array基础