Mux 原始碼閱讀

Tommy_S00發表於2020-11-23

net/http/ServeMux 與 gorilla/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 伺服器時是在部落格上找到的,有時也會奇怪這些前輩是如何知道這些實用的函式的,現在看來,想必也是閱讀了原始碼或者標準庫文件,並且對自己的需求進行分析,最終得到解決方案的。


個人總結,如有差錯,敬請指正。

相關文章