Go 語言 Web 編程系列(五)—— 基於 gorilla/mux 包實現路由匹配:進階使用篇

  • 2019 年 12 月 31 日
  • 筆記

上篇教程我們介紹了 gorilla/mux 路由的基本使用,這篇教程繼續介紹它的更多匹配規則,實際上,它可能是一個比 Laravel 路由更加強大的存在。

1、限定請求方法

類似 Laravel 路由可以通過 Route::getRoute::post 這種方式來限定 HTTP 請求方法,gorilla/mux 支援通過 Methods 方法來限定請求方法,我們可以通過鏈式調用將其應用到上篇教程定義的基礎路由規則上:


r := mux.NewRouter()  r.HandleFunc("/hello/{name:[a-z]+}", sayHelloWorld).Methods("GET", "POST")  r.Handle("/zh/hello/{name}", &HelloWorldHandler{}).Methods("GET")  log.Fatal(http.ListenAndServe(":8080", r))

下面我們通過 cURL 在命令行測試路由訪問,當我們試圖對 http://localhost:8080/zh/hello/golang 發起 POST 請求時,結果為空,表示不支援該方法:

2、路由前綴

和 Laravel 路由一樣,gorilla/mux 路由也支援路由前綴:


r.PathPrefix("/hello").HandlerFunc(sayHelloWorld)

不過,路由前綴通常不會單獨使用,而是和子路由結合使用,從而實現對路由的分組。

3、域名匹配

此外,gorilla/mux 路由還支援域名匹配,這和 Laravel 路由的子域名路由功能非常相似,只需在原來的路由規則基礎上追加 Host 方法調用並指定域名即可:


r.HandleFunc("/hello/{name:[a-z]+}",    sayHelloWorld).Methods("GET").Host("goweb.test")

這樣一來,只有當請求 URL 的域名為 goweb.test 時才會匹配到對應路由映射:

當然,傳入的域名參數值為子域名時,就是子域名匹配了:

r.Handle("/zh/hello/{name}", &HelloWorldHandler{}).Methods("GET").Host("zh.goweb.test")

效果和上面一樣:

為了保證上述測試成功,需要在本地 hosts 文件中添加相應的域名映射:

127.0.0.1 goweb.test  127.0.0.1 zh.goweb.test

限定 Scheme

gorilla/mux 路由支援通過 Schemes 方法設置 Scheme 匹配:


r.Handle("/zh/hello/{name}", &HelloWorldHandler{}).Methods("GET").Host("zh.goweb.test").Schemes("https")

這樣一來,只有 HTTPS 請求才能訪問對應路由,對於 HTTP 請求,會返回 404 錯誤:

4、限定請求參數

接下來的幾個路由匹配規則是 Laravel 不支援的,我們可以在 gorilla/mux 路由定義中通過 Headers 方法設置請求頭匹配,比如下面這個示例,請求頭必須包含 X-Requested-With 並且值為 XMLHttpRequest 才可以訪問指定路由 /request/header


r.HandleFunc("/request/header", func(w http.ResponseWriter, r *http.Request) {      header := "X-Requested-With"      fmt.Fprintf(w, "包含指定請求頭[%s=%s]", header, r.Header[header])  }).Headers("X-Requested-With", "XMLHttpRequest")

這樣做的意義是限定客戶端只能通過 Ajax 請求訪問該路由,測試命令如下:

除了請求頭之外,還可以通過 Queries 方法限定查詢字元串,比如下面這個示例,查詢字元串必須包含 token 且值為 test 才可以匹配到給定路由 /query/string


r.HandleFunc("/query/string", func(w http.ResponseWriter, r *http.Request) {      query := "token"      fmt.Fprintf(w, "包含指定查詢字元串[%s=%s]", query, r.FormValue(query))  }).Queries("token", "test")

這在一些需要訪問令牌的請求中非常有用,可以規避掉無效的請求。測試命令如下:

在 Laravel 中,可以通過中間件完成類似的功能,不過 gorilla/mux 可以更早地規避這種非法請求。

5、自定義匹配規則

最後,gorilla/mux 路由支援通過 MatcherFunc 方法自定義路由匹配規則,在該方法中,可以獲取到請求實例 request,這樣我們就可以拿到所有的用戶請求資訊,並對其進行判斷,符合我們預期的請求才能匹配並訪問該方法應用到的路由。

比如下面這個示例,我們限定只有來自 https://xueyuanjun.com 域名的請求才可以匹配到 /custom/matcher 路由:


r.HandleFunc("/custom/matcher", func(w http.ResponseWriter, r *http.Request) {      fmt.Fprintf(w, "請求來自指定域名: %s", r.Referer())  }).MatcherFunc(func(request *http.Request, match *mux.RouteMatch) bool {      return request.Referer() == "https://xueyuanjun.com"  })

如果不是的話,會返回 404 響應:

6、路由分組

作為路由匹配進階使用教程的收尾,我們來看下如何在 gorilla/mux 路由中實現路由分組和命名,以及根據命名路由生成對應的 URL。

首先來看路由分組,gorilla/mux 沒有直接提供類似路由分組的術語,這裡我們借鑒 Laravel 路由的表述,以方便理解。

gorilla/mux 中,可以基於子路由器(Subrouter)來實現路由分組的功能,具體使用時,還可以藉助前面介紹的路由前綴和域名匹配來對不同分組路由進行特性區分。

下面,我們以文章增刪改查為例,將文章相關路由規則劃分到路由前綴為 /posts 的子路由中:

func listPosts(w http.ResponseWriter, r *http.Request) {      fmt.Fprintf(w, "文章列表")  }    func createPost(w http.ResponseWriter, r *http.Request) {      fmt.Fprintf(w, "發布文章")  }    func updatePost(w http.ResponseWriter, r *http.Request) {      fmt.Fprintf(w, "修改文章")  }    func deletePost(w http.ResponseWriter, r *http.Request) {      fmt.Fprintf(w, "刪除文章")  }    func showPost(w http.ResponseWriter, r *http.Request) {      fmt.Fprintf(w, "文章詳情")  }    ...    // 路由分組(基於子路由+路徑前綴)  postRouter := r.PathPrefix("/posts").Subrouter()  postRouter.HandleFunc("/", listPosts).Methods("GET")  postRouter.HandleFunc("/create", createPost).Methods("POST")  postRouter.HandleFunc("/update", updatePost).Methods("PUT")  postRouter.HandleFunc("/delete", deletePost).Methods("DELETE")  postRouter.HandleFunc("/show", showPost).Methods("GET")

這樣,/posts 前綴會應用到後面所有基於 postRouter 子路由定義的路由規則上,並且針對不同的操作,我們還限定了對應的請求方法,我們可以像這樣測試上述路由的訪問:

如果上述路由是管理後台路由,還可以結合子域名做進一步劃分:


postRouter := r.PathPrefix("/posts").Host("admin.goweb.test").Subrouter()

這樣一來,只有域名為 admin.goweb.test 時才可以訪問對應路由,提高了安全性:

7、路由命名

最後我們來看一下 gorilla/mux 中的路由命名,和 Laravel 路由命名一樣,也是通過 Name 方法在路由規則中指定:


postRouter := r.PathPrefix("/posts").Subrouter()  postRouter.HandleFunc("/", listPosts).Methods("GET").Name("posts.index")  postRouter.HandleFunc("/create", createPost).Methods("POST").Name("posts.create")  postRouter.HandleFunc("/update", updatePost).Methods("PUT").Name("posts.update")  postRouter.HandleFunc("/delete", deletePost).Methods("DELETE").Name("posts.delete")  postRouter.HandleFunc("/show/{id:[0-9]+}", showPost).Methods("GET").Name("posts.show")

然後我們可以像下面這樣根據上述路由命名生成與之對應的 URL:


// 列印路由對應的 URL  indexUrl, _ := r.Get("posts.index").URL()  log.Println("文章列錶鏈接:", indexUrl)    createUrl, _ := r.Get("posts.create").URL()  log.Println("發布文章鏈接:", createUrl)    showUrl, _ := r.Get("posts.show").URL("id", "1")  log.Println("文章詳情鏈接:", showUrl)

列印結果如下:

gorilla/mux 路由也支援中間件,下篇教程,我們就來介紹如何基於 gorilla/mux 編寫並應用路由中間件。