Go的http包中默認路由匹配規則
# 一、執行流程
首先我們構建一個簡單http server:
```go
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對象
```go
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
//此handler即為http.ListenAndServe 中的第二個參數
handler := sh.srv.Handler
if handler == nil {
//如果handler為空則使用內部的DefaultServeMux 進行處理
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
//這里就開始處理http請求
//如果需要使用自定義的mux,就需要實現ServeHTTP方法,即實現Handler接口。
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 路由匹配規則
先看幾個路由規則:
```go
package main
import (
"log"
"net/http"
)
func main() {
//規則1
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
//規則2
http.HandleFunc("/path/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pattern path: /path/ "))
})
//規則3
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,這是存放默認路由規則的:
```go
type ServeMux struct {
mu sync.RWMutex //處理并發,增加讀寫鎖
m map[string]muxEntry //存放規則map,key即為設置的path
hosts bool // whether any patterns contain hostnames(是否包含host)
}
type muxEntry struct {
explicit bool //是否完全匹配
h Handler//相應匹配規則的handler
pattern string//匹配路徑
}
```
通過跟蹤`http.HandleFunc`定位到如下代碼,正是往上面兩個`struct`中增加規則:
```go
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")
}
//如果已經匹配到了則panic
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
//增加一個新的匹配規則
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
//根據path的第一個字母判斷是否有host
if pattern[0] != '/' {
mux.hosts = true
}
//!!這里看清楚 就是實現了情景二的情況 ,看判斷條件
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, "/"):]
}
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`
```go
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
//如果匹配到了一個規則,并沒有馬上返回handler,而且繼續匹配并且判斷path的長度是否是最長的,這是關鍵!!!
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`方法:
```go
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的執行。
> 關注"學點程序"公眾號,了解更多干貨內容 
浙公網安備 33010602011771號