从源码对比DefaultServeMux 与 gorilla/mux
DefaultServeMux
Golang自带的net/http库中包含了DefaultServeMux方法,以此可以搭建一个稳定的高并发的web server。
DefaultServeMux源码分析
-
路由表是实现路由功能的重要结构。muxEntry是存放具体的路由信息的一个map,key是路径,而value是该路径所对应的处理函数。
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry } type muxEntry struct { explicit bool h Handler pattern string }
-
路由注册就是往map中插入数据,如果注册路径在当前路由表中不存在,则会在路由表中增加这一条路径数据,且处理函数是重定向到该路径,要注意的是注册路径要以
/
结尾才会添加到路由表中。func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} n := len(pattern) if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { path := pattern fmt.Printf("redirect for :%s to :%s", pattern, path) mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern} } }
-
路由查找的过程实际上就是遍历路由表的过程,返回最长匹配请求路径的路由信息,找不到则返回NotFoundHandler。如果路径以
xxx/
结尾,则只要满足/xxx/* 就符合该路由。func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() 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) { var n = 0 for k, v := range mux.m { if !pathMatch(k, path) { continue } //找出匹配度最长的 if h == nil || len(k) > n { n = len(k) h = v.h pattern = v.pattern } } return } func pathMatch(pattern, path string) bool { n := len(pattern) if pattern[n-1] != '/' { return pattern == path } return len(path) >= n && path[0:n] == pattern }
DefaultServeMux缺陷
-
不支持正则路由
-
只支持路径匹配,不支持按照Method,header,host等信息匹配,所以也就没法实现RESTful架构
gorilla/mux
-
路由信息存放在数组中,每一条路由信息都包括了路由的约束条件以及上层处理函数,当请求到来时,路由会遍历数组,找到第一个匹配的路由,并执行对应的处理函数,如果找不到则执行NotFoundHandler。
type Router struct { routes []*Route } // Match matches registered routes against the request. func (r *Router) Match(req *http.Request, match *RouteMatch) bool { for _, route := range r.routes { //Route.Match会检查http.Request是否满足其设定的各种条件(路径,Header,Host..) if route.Match(req, match) { return true } } return false } func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { var match RouteMatch var handler http.Handler if r.Match(req, &match) { handler = match.Handler } if handler == nil { handler = http.NotFoundHandler() } handler.ServeHTTP(w, req) }
-
当请求到来时,Route.Match()会遍历matcher数组,只有数组中所有的元素都返回true时则说明此请求满足该路由的限定条件。
type Route struct { // Request handler for the route. handler http.Handler // List of matchers. matchers []matcher }
-
gorilla/mux使用了一个第三方模块gorilla/context。当http请求到来时,mux.Router会选择合适的路由,并提取出一些参数信息,将这些参数信息与http.Request对象在gorilla/context中建立映射关系,上层处理函数根据http.Request对象到context中找到该http.Request所对应的参数信息。
var data = make(map[*http.Request]map[interface{}]interface{}) func Set(r *http.Request, key, val interface{}) { mutex.Lock() if data[r] == nil { data[r] = make(map[interface{}]interface{}) datat[r] = time.Now().Unix() } data[r][key] = val mutex.Unlock() } func Get(r *http.Request, key interface{}) interface{} { mutex.RLock() if ctx := data[r]; ctx != nil { value := ctx[key] mutex.RUnlock() return value } mutex.RUnlock() return nil }
-
上层处理函数中调用mux.Vars(r)则可以取出该http.Request所关联的参数信息。val实际上是一个map[string][string],存放该请求对应的变量值集合。
func setVars(r *http.Request, val interface{}) { context.Set(r, varsKey, val) } func Vars(r *http.Request) map[string]string { if rv := context.Get(r, varsKey); rv != nil { //类型转换,如果失败直接panic return rv.(map[string]string) } return nil }