一、执行流程
构建一个简单http server:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello world")) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
|
使用http://127.0.0.1:8080/
就可以看到输出了
通过跟踪http.go包代码,可以发现执行流程基本如下:
1.创建一个Listener
监听8080
端口
2.进入for
循环并Accept请求,没有请求则处于阻塞状态
3.接收到请求,并创建一个conn对象,放入goroutine处理(实现高并发关键)
4.解析请求来源信息获得请求路径等重要信息
5.请求ServerHTTP方法,已经通过上一步获得了ResponseWriter和Request对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
|
6.进入DefaultServeMux中的逻辑就是根据请求path在map中匹配查找handler,并交由handler处理
http请求处理流程更多信息可以参考[《Go Web 编程
》3.3 Go如何使得Web工作](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.3.md)
二、DefaultServeMux 路由匹配规则
先看几个路由规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello world")) }) http.HandleFunc("/path/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("pattern path: /path/ ")) }) http.HandleFunc("/path/subpath", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("pattern path: /path/subpath")) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
|
情景一:
访问:http://127.0.0.1:8080/
返回:hello world
情景二:
访问:http://127.0.0.1:8080/path
返回:pattern path: /path/
情景三:
访问:http://127.0.0.1:8080/path/subpath/
返回:pattern path: /path/
情景四:
访问:http://127.0.0.1:8080/hahaha/
返回:hello world
先说明一些规则吧,再看代码是怎么实现的:
1.如果匹配路径中后带有/
,则会自动增加一个匹配规则不带/
后缀的,并跳转转到path/
,解释了情景二的场景,为什么匹配到的/path/
2.我设置了这么多规则为什么规则一可以通用匹配未设置的路由信息,而且又不影响已经存在路由, 内部是怎么实现的?
2.1 添加路由规则
先看两个struct,这是存放默认路由规则的:
1 2 3 4 5 6 7 8 9 10 11
| type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool } type muxEntry struct { explicit bool h Handler pattern string }
|
通过跟踪http.HandleFunc
定位到如下代码,正是往上面两个struct
中增加规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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) } mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true } n := len(pattern) if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit{ path := pattern if pattern[0] != '/' { path = pattern[strings.Index(pattern, "/"):] } url := &url.URL{Path: path} mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern} } }
|
上面有个Helpful behavior
的注释行为,就是实现了情景二的情况,他是判断如果匹配的路径中最后含有/
,并且之前也不存在添加去除反斜杠的规则的话,就自动给他增加一个301的跳转指向/path/
2.2 查找路由规则
路由规则的查找就是从ServeMux
中的map去匹配查找的,的到这个handler并执行,只是会有一些处理机制,比如怎么样确保访问/path/subpath
的时候是先匹配/path/subpath
而不是匹配/path/
呢?
当一个请求过来的时候,跟踪到了mux.match
方法:
过程mux.ServerHTTP
->mux.Handler
->mux.handler
->mux.match
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 }
|
1.这里就解释了为什么设置的精确的path是最优匹配到的,因为它是根据path的长度判断。
当然也就解释了为什么/
可以匹配所有(看pathMatch
函数就知道了,/
是匹配所有的,只是这是最后才被匹配成功)
2.得到了处理请求的handler,再调用h.ServeHTTP(w, r)
,去执行相应的handler方法。
等一下,handler中哪里有ServeHTTP
这个方法??
因为在调用 http.HandleFunc
的时候已经将自定义的handler处理函数,强制转为HandlerFunc
类型的,就拥有了ServeHTTP
方法:
1 2 3 4 5 6
| type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
|
f(w,r)
就实现了handler的执行。
原文地址:silenceper.com