handler的存储及匹配
前言
我们在前一节的http ListenAndServe相关的源码中,我们简单提到了在HandleFunc及DefaultServeMux中完成pattern及handler的存储及匹配,具体的过程我们现在详细研究下。
一、ServerMux-结构
ServerMux是路由pattern及handler存储的结构,请求路由的匹配也是在此中的func中完成的。
type ServeMux struct {
mu sync.RWMutex//读写锁
m map[string]muxEntry//pattern及handler存储map
hosts bool // whether any patterns contain hostnames(是否包含hostnames)
}
type muxEntry struct {
explicit bool //当前pattern是否注册
h Handler //我们自定义处理的handler
pattern string //我们定义的pattern
}
ServeMux主要的存储机制就是一个map[string]muxEntry,key存储pattern,value 存储一个包含handler及pattern等信息的muxEntry。mu作为读写锁,为了保证map的读写安全。
二、HandleFunc-存储
http.HandleFunc->DefaultServeMux.HandleFunc->mux.Handle
这几步初步是完成了转入DefaultServeMux处理及handler的HandleFunc转换,然后调用mux.Handle具体处理。
mux.Handle的源码如下:
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()//写操作时加锁
defer mux.mu.Unlock()//完成后解锁
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)//初始化map
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}//添加pattern至mux.m中,并以pattern为key
if pattern[0] != '/' {//如果不以'/'开头,则带有host
mux.hosts = true
}
// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
//处理'/'结尾的pattern,可以被结尾不带'/'的同名pattern覆盖
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]//path必须以'/'开头
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}//添加不带'/'的pattern的handler,并重定向至带'/'结尾的pattern。如果请求时不带'/',则会重定向至带'/',查看请求的header可以在Referer中查看到原始的请求url
}
}
总结:
以上就是一个简单的map操作过程,不过添加了对pattern的处理:
1.针对pattern及handler的要求不可为零值,且同一pattern且不可重复添加。
2.直接添加pattern及对应的handler,pattern不以’/'开头则存在host。
3.如果pattern及’/‘结尾,且未存储不带’/‘的同样pattern,则添加不带’/‘的pattern的handler,并重定向至带’/‘结尾的pattern。注意,如果后面还存在不带’/'的pattern,会覆盖此条信息。
三、ServeHTTP-匹配
serverHandler{c.server}.ServeHTTP(w, w.req)->handler.ServeHTTP(rw, req)->(mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
以上几步主要是将server、请求r及响应w经过转换转入ServeMux的ServeHTTP处理(转换的逻辑见之前的分析文章)。
ServeMux的ServeHTTP源码如下:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {//原始请求URI为*
if r.ProtoAtLeast(1, 1) {//http协议版本1.1及以上时添加header参数
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)//返回400
return
}
//正常请求
h, _ := mux.Handler(r)//获取请求对应的handler
h.ServeHTTP(w, r)//调用handler处理
}
Handler源码如下:
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {//请求方式非CONNECT时
if p := cleanPath(r.URL.Path); p != r.URL.Path {//cleanPath获取绝对路径,如果pattern不是绝对路径格式,则重定向绝对路径的格式处理
_, pattern = mux.handler(r.Host, p)
url := *r.URL
url.Path = p
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
}
//绝对路径或CONNECT请求
return mux.handler(r.Host, r.URL.Path)
}
handler源码如下:
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()//读锁
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {//存在host时,匹配路径
h, pattern = mux.match(host + path)
}
if h == nil {//未匹配,则只匹配请求绝对路径
h, pattern = mux.match(path)
}
if h == nil {//仍未匹配,则返回404
h, pattern = NotFoundHandler(), ""
}
return
}
match源码如下:
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {//path是否匹配存储map中的key
continue
}
if h == nil || len(k) > n {//获取最长的匹配的pattern及对应的handler
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
pathMatch源码如下:
//比较请求存储支持的pattern及请求的路径
// Does path match pattern?
func pathMatch(pattern, path string) bool {
if len(pattern) == 0 {
// should not happen
return false
}
n := len(pattern)
if pattern[n-1] != '/' {//不以'/'结尾则必须相同才能匹配
return pattern == path
}
return len(path) >= n && path[0:n] == pattern//处理以'/'结尾的patten比较,path必须以pattern开头,注意此处,只要求请求的path以pattern为前缀即可,即/tree/list的请求会匹配到/tree/,所以如果要求精确匹配pattern尽量不以'/'结尾
}
总结:
以上调用过程虽然看起来长,实际上也还是map的取值过程O(∩_∩)O哈哈~。主要针对请求url的处理如下:
1.如果存在host,先匹配host+path;
2.如果不存在host或者host匹配结果为空,则匹配path。
具体的匹配逻辑:
1.如果存储的pattern不是以’/'结尾,则请求path必须与pattern完全一致才能匹配成功。
2.如果存储的pattern是以’/'结尾,则请求path必须以pattern为前缀即可,此处会存在多个匹配的情况,如:
/tree/list/及/tree/的请求都会匹配到/tree/这个pattern。
3.如果存在多个匹配的情况,则以匹配到最长的pattern(匹配度最高)为最终匹配结果。
如:存在/tree/list/这个pattern时,请求/tree/list/最终会匹配到/tree/list/,虽然中间也会匹配到/tree/这个pattern。
四、总结
golang底层的pattern的存储与匹配是基于简易的map结构来完成,其实第三方的基于golang的网络架构也是基于这一套,不一样的是map的复杂度及pattern的自由度等地方,如果你感兴趣的话,不妨去看看一些框架的源码。
从以上的源码分析中,我们可以了解到对我们代码有益的地方,如:
1.使用HandleFunc时pattern尽量不以’/'结尾,可以调高存储及匹配的效率,同时,可以避免误匹配的情况。
2.map存在读写都有的情况,使用读写锁。