type Server struct {
Addr string
Handler Handler // 指定 HTTP 服务处理器
// ...
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // Handle 和 HandleFunc 都只是一层封装,最终都会调用到 ServeHTTP
}
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // 使用 map 存储查询树
es []muxEntry // 存储查询树
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "*" {
mux.serveWildCard(w, r) // 如果是通配符请求,直接处理
return
}
h, _ := mux.Handler(r) // 查找路由处理器
h.ServeHTTP(w, r) // 调用路由处理器的 ServeHTTP 方法
}
func (mux *ServeMux) Handler(r *http.Request) (h Handler, pattern string) {
// 1. 没有查询树,说明还没有注册路由规则
if mux.m == nil {
return NotFoundHandler(), ""
}
// 2. 根据 path 查找路由规则,然后返回处理器 h 和路由路径 pattern
path := cleanPath(r.URL.Path)
vs := strings.Split(path, "/")
for _, e := range mux.es {
if e.match(path, vs) {
return e.h, e.pattern
}
}
return NotFoundHandler(), ""
}
func (e *muxEntry) match(path string, vs []string) bool {
if e.pattern == path {
return true
}
if !e.explicit {
// 如果是通配符则进行模式匹配
segs1 := strings.Split(e.pattern, "/")
segs2 := vs
if len(segs1) != len(segs2) {
return false
}
for i, v := range segs1 {
if v != segs2[i] && !strings.HasPrefix(v, ":") && !strings.HasPrefix(v, "*") {
return false
}
}
return true
}
return false
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
if handler == nil {
panic("http: nil handler")
}
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if pattern[0] != '/' {
pattern = "/" + pattern
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{explicit: true, h: handler, pattern: pattern}
mux.es = append(mux.es, e)
mux.m[pattern] = e
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
http 包中的处理器都是通过 ServeHTTP 方法进行处理的,ServeMux 也不例外。
ServeMux 的核心实现是一种基于前缀树的路由匹配查找方式。当请求进来时,先对请求的 URL 进行解析,然后再利用前缀树进行路由的查找。匹配的过程是从前往后以“/”为分隔符一个个匹配,若路径参数在创建路由的时候使用了“:”或者“*”作为前缀,则表示当前该参数为路径参数或捕获所有参数。在查找完成后,将匹配到相应的处理器进行分发处理。