以前出於好奇看了一下 Golang net/http
包下的部分源碼,今天想仍是總結一下吧。因爲是第一次寫文章且抱着忐忑的心情發表,可能有些語義上的不清楚,諒解一下,或者提出修改的建議!git
net/http
包裏的 server.go
文件裏註釋寫着:ServeMux is an HTTP request multiplexer. 即 ServeMux 是一個 HTTP 請求的 "多路處理器",由於 ServeMux 實現的功能就是將收到的 HTTP 請求的 URL 與註冊的路由相匹配,選擇匹配度最高的路由的處理函數來處理該請求。github
最簡單的栗子:瀏覽器
mux := http.NewServeMux()
mux.HandleFunc("/a/b", ab)
mux.HandleFunc("/a", a)
http.ListenAndServe(":8000", mux)
複製代碼
每一個路由對應了一個處理函數。app
先來看看 NewServeMux
函數框架
func NewServeMux() *ServeMux { return new(ServeMux) }
複製代碼
咱們知道 new
函數會爲傳入的類型分配空間並返回指向該空間首地址的指針,因而咱們就獲取了一個 ServeMux 實例。函數
接下來就是 ServeMux 的結構源碼分析
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry
hosts bool // 標記路由中是否帶有主機名
}
複製代碼
其中 m
就是用來存儲路由與處理函數映射關係的 map,es
按照路由長度從大到小的存放處理函數 (後面會講爲何要這樣),但 ServeMux 爲了方便,map存放的值實際上是放有處理函數和路由路徑的 muxEntry
結構體:post
type muxEntry struct {
h Handler // 處理函數
pattern string // 路由路徑
}
複製代碼
ServeMux 暴露的方法主要是下面 4 個:ui
func (mux *ServeMux) Handle(pattern string, handler Handler) func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) 複製代碼
Handle
方法經過將傳入的路由和處理函數存入 ServeMux 的映射表 m
中來實現 "路由註冊(register)"url
源碼具體實現以下:
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 檢查路由路徑是否爲空
if pattern == "" {
panic("http: invalid pattern")
}
// 檢查處理函數是否爲空
if handler == nil {
panic("http: nil handler")
}
// 檢查該路由是否已經註冊過
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
// 若是尚未任何路由註冊,就爲 mux.m 分配空間
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
// 實例化一個 muxEntry
e := muxEntry{h: handler, pattern: pattern}
// 將該路由與該 muxEntry 的實例存到 mux.m 中
mux.m[pattern] = e
// 若是該路由路徑以 "/" 結尾,就把該路由按照大到小的路徑長度插入到 mux.e 中
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
// 若是該路由路徑不以 "/" 開始,標記該 mux 中有路由的路徑帶有主機名
if pattern[0] != '/' {
mux.hosts = true
}
}
複製代碼
HandleFunc
方法接收一個具體的處理函數將其包裝成 Handler:
type HandlerFunc func(ResponseWriter, *Request) func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
複製代碼
其中 HandlerFunc(f)
起到的做用就是在 HandlerFunc 中執行 f
Handler
方法從傳入的請求(Request)中拿到 URL 進行匹配,返回對應的處理函數和路由
在看 Handler
的實現前,先看看它調用的 handler
方法:
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// 若當前 mux 中註冊有帶主機名的路由,就用"主機名+路由路徑"去匹配
// 也就是說帶主機名的路由優先於不帶的
if mux.hosts {
h, pattern = mux.match(host + path)
}
// 因此若沒有匹配到,就直接把路由路徑拿去匹配
if h == nil {
h, pattern = mux.match(path)
}
// 若都沒有匹配到,就默認返回 NotFoundHandler,該 Handler 會往
// 響應裏寫上 "404 page not found"
if h == nil {
h, pattern = NotFoundHandler(), ""
}
// 返回得到的 Handler 和路由路徑
return
}
複製代碼
好了,如今是 Handler
方法
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// 去掉主機名上的端口號
host := stripHostPort(r.Host)
// 整理 URL,去掉 ".", ".."
path := cleanPath(r.URL.Path)
// redirectToPathSlash 在 mux.m 中查看 path+"/" 是否存在
// 若是存在,RedirectHandler 就將該請求重定向到 path+"/"
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
// 若是整理後的 URL 與請求中的路徑不同,先調用 handler 進行匹配
// 在將請求裏的 URL 改爲整理後的 URL
// 最後將該請求重定向到整理後的 URL
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
// 若以上條件都不知足則返回匹配結果
return mux.handler(host, r.URL.Path)
}
複製代碼
咱們有必要看看 match
方法是怎麼進行匹配的
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// 若 mux.m 中已存在該路由映射,直接返回該路由的 Handler,和路徑
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// 找到路徑能最長匹配的路由。
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
複製代碼
注意這裏是在 mux.es 中進行查找,而不是映射表 mux.m 中,而 mux.es 是存放全部以 "/" 結尾的路由路徑的切片。由於只會在以 "/" 結尾的路由路徑中才會出現須要選擇最長匹配方案
好比註冊的路由有
mux.HandleFunc("/a/b/", ab)
mux.HandleFunc("/a/", a)
複製代碼
那麼當一個請求的 URL 爲 /a/b/c
的時候,咱們但願是由 ab 來處理這個請求。
另外,爲了減小在 mux.es 中的查詢時間, mux.es 中元素是按照它們的長度由大到小順序存放的。
咱們知道在 Go 中要實現一個處理請求的 handler 結構體須要讓該結構體現實 Handler
接口的 ServeHTTP
方法:
// Handler 接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type myHandler struct {}
func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("This message is from myHandler."))
}
func main() {
http.Handle("/", &helloHandler{}) // 路由註冊
}
複製代碼
咱們已經經過 Handler
方法拿到了請求(Request)和它對應的處理函數(Handler)
咱們的 ServeMux 是一個結構體,它的 ServeHTTP
方法要作的就是將每一個請求派遣(dispatch)到它們對應的處理函數上。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// 若是請求路徑爲 "*",告訴瀏覽器該鏈接已關閉並返回狀態碼 400
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
// 調用 mux.Handler 方法獲取請求和它對應的處理函數
h, _ := mux.Handler(r)
// 將 ResponseWriter 和 *Request 類型的參數傳給處理函數
h.ServeHTTP(w, r)
}
複製代碼
這樣,每收到一個請求就會調用對應的處理函數來處理該請求了。
但咱們一般會看到,一些簡單的示例代碼是下面這樣寫的:
func helloHandler(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func main() {
http.HandleFunc("/", helloHandler)
http.ListenAndServe(":8000", nil)
}
複製代碼
咱們能夠看一下 http.HandleFunc
方法作了些什麼:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
複製代碼
能夠看到該方法中使用了一個 DefaultServeMux
來註冊傳入的路由,繼續看:
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
複製代碼
能夠看到,http.HandleFunc
也是經過實例化一個全局的 ServeMux
來進行路由註冊的。
咱們已經瞭解了 ServeMux 是怎麼實現多路處理了,簡單歸納一下。Handle
和 HandleFunc
方法用來將路由路徑與處理函數的映射經過一個 map 記錄到當前的 mux 實例裏;Handler
方法將接收的請求中的 URL 預處理後拿去和記錄的映射匹配,若匹配到,就返回該路由的處理函數和路徑;ServeHTTP
方法將請求派遣給匹配到的處理函數處理。
可是 ServeMux 的多路處理實現並不支持請求方法判斷,也不能處理路由嵌套和URL變量值提取的功能
因此最近在分析 gin、kratos/blademaster 這樣的框架是如何實現這三個功能的,但願後面能有第二篇總結出現吧。。
其實寫到一半的時候發現掘金上已經有這部分的源碼分析了,因而就看了一下,和本身想的差很少,哈哈