Go 語言 Web 編程系列(五)—— 基於 gorilla/mux 包實現路由匹配:進階使用篇
- 2019 年 12 月 31 日
- 筆記
上篇教程我們介紹了 gorilla/mux
路由的基本使用,這篇教程繼續介紹它的更多匹配規則,實際上,它可能是一個比 Laravel 路由更加強大的存在。
1、限定請求方法
類似 Laravel 路由可以通過 Route::get
、Route::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
編寫並應用路由中間件。