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

