Go標準庫-ServeMux的使用與模式匹配深入探究
引言
本篇為【深入理解Go標準庫】系列第二篇
第一篇:http server的啟動
第二篇:ServeMux的使用與模式匹配??
根據(jù) Golang 文檔 中的介紹,ServeMux
是一個 HTTP 請求多路復用器(HTTP Request multiplexer
)。它按照一定規(guī)則匹配請求URL和已注冊的模式,并執(zhí)行其中最匹配的模式的Handler
如果你還不知道什么是Handler
,強烈建議你先閱讀下:第一篇:http server的啟動
基本使用
http.ServeMux
實現(xiàn)了Handler
接口
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
http.ServeMux
提供兩個函數(shù)用于注冊不同Path的處理函數(shù)
ServeMux.Handle
接收的是Handler
接口實現(xiàn)ServeMux.HandleFunc
接收的是匿名函數(shù)
type PathBar struct { } func (m PathBar) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path bar")) return } func main() { mx := http.NewServeMux() mx.Handle("/bar/", PathBar{}) mx.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) }) http.ListenAndServe(":8009", mx) }
?? 通過類型轉(zhuǎn)換實現(xiàn)接口
值得一提的是ServeMux.HandleFunc
的實現(xiàn),底層還是調(diào)用了ServeMux.Handle
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
HandlerFunc(handler)
這里并不是函數(shù)調(diào)用,而是類型轉(zhuǎn)換
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
通過把handler func(ResponseWriter, *Request)
轉(zhuǎn)換成類型HandlerFunc
,而類型HandlerFunc
實現(xiàn)了Handler
接口
?? 全局默認值
當沒有設置http.Server.Handler
屬性時,http.Server
就會使用一個全局的變量DefaultServeMux *ServeMux
來作為http.Server.Handler
的值
下面的代碼和上面的沒有區(qū)別
func main() { http.Handle("/bar/", PathBar{}) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) }) http.ListenAndServe(":8009", nil) }
Pattern匹配
預處理
預處理的是請求的url,以方便匹配,在注冊時是不會做任何處理的
移除host中的端口號
針對 URL 中包含
..
或者.
的請求,ServeMux 會對其 Path 進行整理,并匹配到合適的路由模式上針對 URL 中包含重復
/
的請求,ServeMux 會對其進行重定向
func main() { mx := http.NewServeMux() mx.HandleFunc("/abc/def", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.Host, request.URL.Path) }) http.ListenAndServe(":8009", mx) }
?? 預處理的是請求的url
pattern是不會被處理的,而請求的url都是被處理成標準格式
所以如果注冊如下的pattern,無論如何也是無法被命中的
func main() { mx := http.NewServeMux() mx.HandleFunc("/abc//def", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.Host, request.URL.Path) }) }
無論是/abc/def
還是/abc//def
都無法被命中
$ curl 127.0.0.1:8009/abc/def 404 page not found $ curl 127.0.0.1:8009/abc//def <a href="/abc/def" rel="external nofollow" rel="external nofollow" >Moved Permanently</a>.
?? 帶 ..
或者.
請求與重復/
請求的處理不同
包含..
或者.
整理之后匹配到合適的路由模式上,并不會重定向
$ curl 127.0.0.1:8009/ccc/../abc/./def 127.0.0.1:8009 /abc/def
含重復/
,會重定向
$ curl -v 127.0.0.1:8009/abc//def * Trying 127.0.0.1:8009... * Connected to 127.0.0.1 (127.0.0.1) port 8009 (#0) > GET /abc//def HTTP/1.1 > Host: 127.0.0.1:8009 > User-Agent: curl/7.79.1 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 301 Moved Permanently < Content-Type: text/html; charset=utf-8 < Location: /abc/def < Date: Thu, 10 Nov 2022 16:05:13 GMT < Content-Length: 43 < <a href="/abc/def" rel="external nofollow" rel="external nofollow" >Moved Permanently</a>. * Connection #0 to host 127.0.0.1 left intact
路徑匹配
ServeMux 注冊路由模式的方式有兩種,固定根路徑
例如"/favicon.ico",與以根路徑開始的子樹
,例如"/images/"
?? 固定路徑(fixed, rooted paths)
固定根路徑
就是指定一個固定的 URL 和請求進行精確匹配
?? 以根路徑開始的子樹(rooted subtrees)
以根路徑開始的子樹
是符合最長路徑匹配的原則的,例如我們注冊了兩個子路徑,/image/gif/
和/image/
,URL 為/image/gif/
的請求會優(yōu)先匹配第一個路由模式,其他路徑會匹配/image/
?? 注意:
1、凡是/
結(jié)尾的路徑都被看作以根路徑開始的子樹,因此 /
也被看作以根路徑開始的子樹,它不僅匹配/
,而且也會匹配所有未被其他路由模式匹配的請求。
func main() { mx := http.NewServeMux() mx.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.URL.EscapedPath()) }) http.ListenAndServe(":8009", mx) }
$ curl 127.0.0.1:8009/abc /abc
2、如果只注冊了一個子樹路徑(/
結(jié)尾)并且請求URL沒有/
結(jié)尾,ServeMux會返回重定向。如果再增加一個沒有/
結(jié)尾的模式的話,就會精確匹配,也就不會有這種行為了
例如我們只注冊了子路徑/abc/
,服務器會自動將/abc
請求重定向為/abc/
。
func main() { mx := http.NewServeMux() mx.HandleFunc("/abc/", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.URL.EscapedPath()) }) http.ListenAndServe(":8009", mx) }
$ curl -v 127.0.0.1:8009/abc * Trying 127.0.0.1:8009... * Connected to 127.0.0.1 (127.0.0.1) port 8009 (#0) > GET /abc HTTP/1.1 > Host: 127.0.0.1:8009 > User-Agent: curl/7.79.1 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 301 Moved Permanently < Content-Type: text/html; charset=utf-8 < Location: /abc/ < Date: Thu, 10 Nov 2022 15:30:13 GMT < Content-Length: 40 < <a href="/abc/" rel="external nofollow" >Moved Permanently</a>. * Connection #0 to host 127.0.0.1 left intact
如果我們不想讓服務器自動重定向的話,只需要再添加一個/abc
模式就好了
func main() { mx := http.NewServeMux() mx.HandleFunc("/abc/", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.URL.EscapedPath()) }) mx.HandleFunc("/abc", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.URL.EscapedPath()) }) http.ListenAndServe(":8009", mx) }
$ curl 127.0.0.1:8009/abc /abc
域名匹配(Host-specific patterns)
ServeMux 還支持根據(jù)主機名精確匹配,匹配時會嚴格匹配host,path的匹配則還遵循上面的原則
?? 注意:
有域名的優(yōu)先級會更高,所以可以注冊一個帶域名的路徑和不帶域名的路徑
func main() { mx := http.NewServeMux() mx.HandleFunc("example01.com/abc/", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.Host, request.URL.EscapedPath()) }) mx.HandleFunc("/abc/", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.Host, request.URL.EscapedPath()) }) http.ListenAndServe(":8009", mx) }
example01.com會匹配第一個handler,其他域名則匹配第二個
$ curl -H 'HOST:example01.com' 127.0.0.1:8009/abc/ example01.com /abc/ $ curl -H 'HOST:example02.com' 127.0.0.1:8009/abc example02.com /abc
Method和路徑參數(shù)匹配(method, path specificity patterns)
最新的特性還在討論中,大致的patterns會像下面這樣
https://github.com/golang/go/discussions/60227
/item/ POST /item/{user} /item/{user} /item/{user}/{id} /item/{$} POST alt.com/item/{user}
以上就是Go標準庫-ServeMux的使用與模式匹配深入探究的詳細內(nèi)容,更多關(guān)于Go ServeMux模式匹配的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實現(xiàn)基本操作詳解
這篇文章主要為大家介紹了go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實現(xiàn)基本操作詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01Ubuntu18.04 LTS搭建GO語言開發(fā)環(huán)境過程解析
這篇文章主要介紹了Ubuntu18.04 LTS搭建GO語言開發(fā)環(huán)境過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11Golang結(jié)構(gòu)化日志包log/slog的使用詳解
官方提供的用于打印日志的包是標準庫中的 log 包,該包雖然被廣泛使用,但是缺點也很多,所以Go 1.21新增的 log/slog 完美解決了以上問題,下面我們就來看看log/slog包的使用吧2023-09-09