http.ServeMux 的源码实现

88 min read
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 进行解析,然后再利用前缀树进行路由的查找。匹配的过程是从前往后以“/”为分隔符一个个匹配,若路径参数在创建路由的时候使用了“:”或者“*”作为前缀,则表示当前该参数为路径参数或捕获所有参数。在查找完成后,将匹配到相应的处理器进行分发处理。