DefaultServeMux 与 gorilla/mux 对比阅读
DefaultServeMux 实现
查阅 net.http 源码,可以找到 DefaultServeMux
的实现:指向 ServeMux
默认实例 defaultServeMux
;
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
结构体 ServeMux
的定义如下;其中用于路由的字段有 m
和 es
;字段 m
是映射 map[string]muxEntry
,将用户请求中的 path
映射到 muxEntry
,用于完全匹配;字段 es
是 muxEntry
由长到短排列的切片,用于最长有效匹配;
muxEntry
定义如下;字段 h
用于请求处理的,是所匹配路径对应的 Handle
处理方法;字段 pattern
指明可以匹配的模式,适用于前缀匹配;
// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
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
}
Handle
定义如下;是包含 ServeHTTP
的接口;在函数 ServeHTTP
中将会对请求进行具体处理;
// A Handler responds to an HTTP request.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
HandleFunc
可以给 DefaultServeMux
对特定的 pattern
增加对应的 handler
;首先用输入的 pattern
和 handler
生成新的 muxEntry
,然后在映射 m
中增加该 pattern
到新的 muxEntry
的映射,并按照由长到短的顺序放入 es
中;
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
match
实现路径匹配;先是在映射 m
中查找请求的路径 path
有无相应的 muxEntry
,有则说明 path
和该 muxEntry
的 pattern
完全匹配,返回;然后在切片 es
中按照前缀匹配,由长到短寻找 pattern
,有则说明 path
和该 muxEntry
的 pattern
前缀匹配,返回;最后匹配失败,返回空;
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 定义的路由器 Router
如下;其中用于路由匹配的主要字段是 routes
是 *Route
切片,有序排列,类似于 ServeMux
中的字段 es
;
Route
定义如下;字段 handler
用于 http 请求处理;注意到还有字段 namedRoutes
,是映射 map[string]*Route
,用于实现嵌套路由;
type Router struct {
NotFoundHandler http.Handler
MethodNotAllowedHandler http.Handler
// Routes to be matched, in order.
routes []*Route
// Routes by name for URL building.
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
}
Match
实现路径匹配;先遍历 routes
进行匹配,匹配成功则返回 true
;然后检查默认处理函数 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
}
DefaultServeMux 与 gorilla/mux 对比
DefaultServeMux
- 只支持路径匹配路由,不支持正则表达式路由
- 路由过程使用顺序遍历的方式进行匹配,缺乏效率
gorilla/mux
- 实现了
http.Handler
接口,与标准库的http.ServeMux
兼容 - 可以基于URL主机,路径,路径前缀,方案,标头和查询值,HTTP方法或使用自定义匹配器来匹配请求
- URL主机,路径和查询值可以具有带可选正则表达式的变量
- 可以构建或“反转”已注册的URL,这有助于维护对资源的引用
- 路由可用作子路由:仅在父路由匹配时才测试嵌套路由;对于具有共同条件(例如主机,路径前缀或其他重复属性)的路由组,可以优化请求匹配