Go標(biāo)準(zhǔn)庫(kù)-ServeMux的使用與模式匹配深入探究
引言
本篇為【深入理解Go標(biāo)準(zhǔn)庫(kù)】系列第二篇
第二篇:ServeMux的使用與模式匹配??
根據(jù) Golang 文檔 中的介紹,ServeMux
是一個(gè) HTTP 請(qǐng)求多路復(fù)用器(HTTP Request multiplexer
)。它按照一定規(guī)則匹配請(qǐng)求URL和已注冊(cè)的模式,并執(zhí)行其中最匹配的模式的Handler
如果你還不知道什么是Handler
,強(qiáng)烈建議你先閱讀下:第一篇:http server的啟動(dòng)
基本使用
http.ServeMux
實(shí)現(xiàn)了Handler
接口
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
http.ServeMux
提供兩個(gè)函數(shù)用于注冊(cè)不同Path的處理函數(shù)
ServeMux.Handle
接收的是Handler
接口實(shí)現(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) }
?? 通過(guò)類(lèi)型轉(zhuǎn)換實(shí)現(xiàn)接口
值得一提的是ServeMux.HandleFunc
的實(shí)現(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)用,而是類(lèi)型轉(zhuǎn)換
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
通過(guò)把handler func(ResponseWriter, *Request)
轉(zhuǎn)換成類(lèi)型HandlerFunc
,而類(lèi)型HandlerFunc
實(shí)現(xiàn)了Handler
接口
?? 全局默認(rèn)值
當(dāng)沒(méi)有設(shè)置http.Server.Handler
屬性時(shí),http.Server
就會(huì)使用一個(gè)全局的變量DefaultServeMux *ServeMux
來(lái)作為http.Server.Handler
的值
下面的代碼和上面的沒(méi)有區(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匹配
預(yù)處理
預(yù)處理的是請(qǐng)求的url,以方便匹配,在注冊(cè)時(shí)是不會(huì)做任何處理的
移除host中的端口號(hào)
針對(duì) URL 中包含
..
或者.
的請(qǐng)求,ServeMux 會(huì)對(duì)其 Path 進(jìn)行整理,并匹配到合適的路由模式上針對(duì) URL 中包含重復(fù)
/
的請(qǐng)求,ServeMux 會(huì)對(duì)其進(jìn)行重定向
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) }
?? 預(yù)處理的是請(qǐng)求的url
pattern是不會(huì)被處理的,而請(qǐng)求的url都是被處理成標(biāo)準(zhǔn)格式
所以如果注冊(cè)如下的pattern,無(wú)論如何也是無(wú)法被命中的
func main() { mx := http.NewServeMux() mx.HandleFunc("/abc//def", func(writer http.ResponseWriter, request *http.Request) { fmt.Fprintln(writer, request.Host, request.URL.Path) }) }
無(wú)論是/abc/def
還是/abc//def
都無(wú)法被命中
$ 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>.
?? 帶 ..
或者.
請(qǐng)求與重復(fù)/
請(qǐng)求的處理不同
包含..
或者.
整理之后匹配到合適的路由模式上,并不會(huì)重定向
$ curl 127.0.0.1:8009/ccc/../abc/./def 127.0.0.1:8009 /abc/def
含重復(fù)/
,會(huì)重定向
$ 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 注冊(cè)路由模式的方式有兩種,固定根路徑
例如"/favicon.ico",與以根路徑開(kāi)始的子樹(shù)
,例如"/images/"
?? 固定路徑(fixed, rooted paths)
固定根路徑
就是指定一個(gè)固定的 URL 和請(qǐng)求進(jìn)行精確匹配
?? 以根路徑開(kāi)始的子樹(shù)(rooted subtrees)
以根路徑開(kāi)始的子樹(shù)
是符合最長(zhǎng)路徑匹配的原則的,例如我們注冊(cè)了兩個(gè)子路徑,/image/gif/
和/image/
,URL 為/image/gif/
的請(qǐng)求會(huì)優(yōu)先匹配第一個(gè)路由模式,其他路徑會(huì)匹配/image/
?? 注意:
1、凡是/
結(jié)尾的路徑都被看作以根路徑開(kāi)始的子樹(shù),因此 /
也被看作以根路徑開(kāi)始的子樹(shù),它不僅匹配/
,而且也會(huì)匹配所有未被其他路由模式匹配的請(qǐng)求。
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、如果只注冊(cè)了一個(gè)子樹(shù)路徑(/
結(jié)尾)并且請(qǐng)求URL沒(méi)有/
結(jié)尾,ServeMux會(huì)返回重定向。如果再增加一個(gè)沒(méi)有/
結(jié)尾的模式的話(huà),就會(huì)精確匹配,也就不會(huì)有這種行為了
例如我們只注冊(cè)了子路徑/abc/
,服務(wù)器會(huì)自動(dòng)將/abc
請(qǐng)求重定向?yàn)?code>/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
如果我們不想讓服務(wù)器自動(dòng)重定向的話(huà),只需要再添加一個(gè)/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ù)主機(jī)名精確匹配,匹配時(shí)會(huì)嚴(yán)格匹配host,path的匹配則還遵循上面的原則
?? 注意:
有域名的優(yōu)先級(jí)會(huì)更高,所以可以注冊(cè)一個(gè)帶域名的路徑和不帶域名的路徑
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會(huì)匹配第一個(gè)handler,其他域名則匹配第二個(gè)
$ 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會(huì)像下面這樣
https://github.com/golang/go/discussions/60227
/item/ POST /item/{user} /item/{user} /item/{user}/{id} /item/{$} POST alt.com/item/{user}
以上就是Go標(biāo)準(zhǔn)庫(kù)-ServeMux的使用與模式匹配深入探究的詳細(xì)內(nèi)容,更多關(guān)于Go ServeMux模式匹配的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)sync.Once使用場(chǎng)景及性能優(yōu)化詳解
- Golang標(biāo)準(zhǔn)庫(kù)os/exec執(zhí)行外部命令并獲取其輸出包代碼示例
- Go?1.21.0?新增結(jié)構(gòu)化日志記錄標(biāo)準(zhǔn)庫(kù)log/slog使用詳解
- Go標(biāo)準(zhǔn)庫(kù)strconv實(shí)現(xiàn)string類(lèi)型與其他基本數(shù)據(jù)類(lèi)型之間轉(zhuǎn)換
- Go 標(biāo)準(zhǔn)庫(kù)增加metrics指標(biāo)探討分析
- Golang標(biāo)準(zhǔn)庫(kù)unsafe源碼解讀
- go語(yǔ)言標(biāo)準(zhǔn)庫(kù)fmt包的一鍵入門(mén)
相關(guān)文章
golang進(jìn)行簡(jiǎn)單權(quán)限認(rèn)證的實(shí)現(xiàn)
本文主要介紹了golang簡(jiǎn)單權(quán)限認(rèn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解
這篇文章主要為大家介紹了go?sync?Waitgroup數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)基本操作詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Ubuntu18.04 LTS搭建GO語(yǔ)言開(kāi)發(fā)環(huán)境過(guò)程解析
這篇文章主要介紹了Ubuntu18.04 LTS搭建GO語(yǔ)言開(kāi)發(fā)環(huán)境過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Golang結(jié)構(gòu)化日志包log/slog的使用詳解
官方提供的用于打印日志的包是標(biāo)準(zhǔn)庫(kù)中的 log 包,該包雖然被廣泛使用,但是缺點(diǎn)也很多,所以Go 1.21新增的 log/slog 完美解決了以上問(wèn)題,下面我們就來(lái)看看log/slog包的使用吧2023-09-09go語(yǔ)言入門(mén)環(huán)境搭建及GoLand安裝教程詳解
這篇文章主要介紹了go語(yǔ)言入門(mén)環(huán)境搭建及GoLand安裝教程詳解,需要的朋友可以參考下2020-12-12