Mux 原始碼閱讀
前言
在使用 GoLang 編寫 Web 伺服器程式時,需要用到 Mux(路由),對瀏覽器請求的 URL 進行路由,根據不同的 URL,使用不同的 handlerFunc 進行處理。
這裡我們可以使用 net/http 包自帶的 ServeMux,也可以使用第三方開源軟體包 gorilla/mux 提供的 Mux。
那麼,本文將分析兩者有何不同,以及 mux 的原理是怎樣的。
Compare
以下比較過程將預設以 net/http/ServeMux 在前,gorilla/mux 在後的順序進行展示。
使用
import "net/http"
mux := http.NewServeMux()
mux.Handle("/api/", apiHandler{})
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
// The "/" pattern matches everything, so we need to check
// that we're at the root here.
if req.URL.Path != "/" {
http.NotFound(w, req)
return
}
fmt.Fprintf(w, "Welcome to the home page!")
})
import "github.com/gorilla/mux"
mx := mux.NewRouter()
mx.HandleFunc("/loginPost", func(w http.ResponseWriter, req *http.Request) {
// 解析 url 傳遞的引數,對於 POST 則解析響應包的主體(request body)
req.ParseForm()
// 模板
formatter.HTML(w, http.StatusOK, "info", struct {
Name string `json:"name"`
ID string `json:"id"`
Phone string `json:"phone"`
Email string `json:"email"`
}{Name: req.Form.Get("username"), ID: req.Form.Get("id"), Phone: req.Form.Get("tel"), Email: req.Form.Get("email")})
}).Methods("POST")
mx.PathPrefix("/file/").Handler(http.StripPrefix("/file/", http.FileServer(http.Dir(webRoot+"/file/"))))
可以看到,兩個 Mux 的使用差不多,都是先建立一個 mux 物件,然後給 mux 新增 handler。
原始碼分析
無論是哪個 Mux,主要結構都包括:Mux Struct,Handle | HandleFunc,Handler | HandlerFunc三個部分。
-
Mux Struct 作為 路由的容器,實現了 Handle,HandleFunc 等方法。其中定義了一個路由陣列,每次呼叫 Handle | HandleFunc 都會給這個陣列新增一個新的路由。
-
Handle 與 HandleFunc 的功能很相似,都是接收一個 pattern string,然後為其匹配一個 服務提供模組,只是前者匹配的是一個處理器,而後者是一個處理器函式。
-
Handler(處理器),兩個 Mux 使用的都是 net/http/Handler,這是一個介面:
該介面包含一個 ServeHTTP 方法:將回復的頭域和資料寫入 ResponseWriter 介面,然後返回。 -
type HandlerFunc func(ResponseWriter, *Request)(處理器函式),該函式型別實現了 ServeHTTP 方法:
這使得該型別可以作為一個介面卡,通過型別轉換讓我們可以將普通的函式(但簽名必須符合func(ResponseWriter, *Request)
)作為HTTP處理器使用。 -
match 函式,路由器開啟監聽請求後,每當有請求(URL)進入,首先就是在 match 中進行處理。 match 將遍歷路由器下所有路由,看是否存在匹配,如果有,返回對應的 Handler。
gorilla/mux 特性分析
對比了兩個 Mux 的原始碼,我知道了 gorilla 的取名原因:
首先 net/http 的路由器只有一層,那就是一個根 ServeMux,然後包含一個 ServeMux 的陣列,所有註冊的路由全部放在這個陣列裡。
gorilla 採用路由樹的形式組織路由器,根路由(Router)為 “/”,之後每次註冊路由都在根路由下建立一條路徑(或重用),比如:建立一個 “/user/info/name” 的路由,gorilla 首先在 “/” 路由器下建立並新增一個 Match “user” 的路由器,然後在 “user” 路由器下建立並新增一個 Match “info“ 的路由器,依次類推。
在對 URL 進行解析時,gorilla 使用 Walk 函式,進行深度遍歷,像大猩猩一樣逐漸向下爬樹,最終找到符合整個 URL 的 “葉子路由器”,並返回該路由器內部的 Handler。
結語
通過閱讀 Mux 原始碼,我瞭解之前不太熟悉的函式,如:net/http 內建的 StripPrefix 函式,可以將 URL 的字首部分剝除後,返回一個 Handler。
這個函式我在做 Web 伺服器時是在部落格上找到的,有時也會奇怪這些前輩是如何知道這些實用的函式的,現在看來,想必也是閱讀了原始碼或者標準庫文件,並且對自己的需求進行分析,最終得到解決方案的。
個人總結,如有差錯,敬請指正。
相關文章
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- ReactorKit原始碼閱讀React原始碼
- Vollery原始碼閱讀(—)原始碼
- NGINX原始碼閱讀Nginx原始碼
- ThreadLocal原始碼閱讀thread原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- Runtime 原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- AmplifyImpostors原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- fuzz原始碼閱讀原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- AQS原始碼閱讀AQS原始碼
- ConcurrentHashMap原始碼閱讀HashMap原始碼
- HashMap原始碼閱讀HashMap原始碼
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- JDK原始碼閱讀:String類閱讀筆記JDK原始碼筆記
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- 如何閱讀Java原始碼?Java原始碼
- buffer 原始碼包閱讀原始碼
- 使用OpenGrok閱讀原始碼原始碼
- express 原始碼閱讀(全)Express原始碼
- Kingfisher原始碼閱讀(一)原始碼
- 如何閱讀框架原始碼框架原始碼
- 如何閱讀jdk原始碼?JDK原始碼
- ArrayList原始碼閱讀(增)原始碼
- snabbdom 原始碼閱讀分析原始碼
- Appdash原始碼閱讀——reflectAPP原始碼
- React原始碼閱讀:setStateReact原始碼
- 如何快速閱讀原始碼原始碼
- 原始碼閱讀工具-understand原始碼
- koa原始碼閱讀[0]原始碼
- basictracer原始碼閱讀——TracerImpl原始碼