Go標(biāo)準(zhǔn)庫http?server優(yōu)雅啟動(dòng)深入理解
如何用最少的代碼創(chuàng)建一個(gè)HTTP server?
package main import ( "net" "net/http" ) func main() { // 方式1 err := http.ListenAndServe(":8080", nil) if err != nil { panic(err) } }
點(diǎn)開http.ListenAndServe
可以看到函數(shù)只是創(chuàng)建了Server
類型并調(diào)用server.ListenAndServe()
所以下面的和上面的代碼沒有區(qū)別
package main import ( "net" "net/http" ) func main() { // 方式2 server := &http.Server{Addr: ":8080"} err := server.ListenAndServe() if err != nil { panic(err) } }
ListenAndServe()
如其名會(huì)干兩件事
監(jiān)聽一個(gè)端口,即
Listen
的過程處理進(jìn)入端口的連接,即
Serve
的過程
所以下面的代碼和上面的代碼也沒區(qū)別
package main import ( "net" "net/http" ) func main() { // 方式3 ln, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } server := &http.Server{} err = server.Serve(ln) if err != nil { panic(err) } }
一張圖展示三種使用方式
路由?no!Handler!
按上面的代碼啟動(dòng)HTTP Server沒有太大意義,因?yàn)槲覀冞€沒有設(shè)定路由,所以無法正常響應(yīng)請(qǐng)求
$ curl 127.0.0.1:8080 404 page not found
暫停思考一下,服務(wù)器返回404是因?yàn)闆]有設(shè)定路由么?no,no,no,你需要轉(zhuǎn)變一下思維。服務(wù)器返回404不是因?yàn)槲覀儧]有設(shè)置路由,而是因?yàn)闆]有設(shè)置請(qǐng)求的處理程序,這個(gè)處理程序在Go中叫作:Handler
!
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
?? 怎么定義請(qǐng)求的處理程序?
由上可知,僅需要實(shí)現(xiàn)ServeHTTP(ResponseWriter, *Request)
接口即可
注意,示例代碼沒有判斷任何路由(PATH)
type handlerImp struct { } func (imp handlerImp) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { w.Write([]byte("Receive GET request")) return } if r.Method == "POST" { w.Write([]byte("Receive POST request")) return } return }
?? 怎么設(shè)置請(qǐng)求的處理程序?
三種方式本質(zhì)上都是把自定義的Handler
賦值到Server
的Handler
屬性中
func main() { // 方式1 // err := http.ListenAndServe(":8080", handlerImp{}) // if err != nil { // panic(err) // } // 方式2 // server := &http.Server{Addr: ":8080", Handler: handlerImp{}} // err := server.ListenAndServe() // if err != nil { // panic(err) // } // 方式3 ln, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } server := &http.Server{Handler:handlerImp{}} err = server.Serve(ln) if err != nil { panic(err) } }
?? 設(shè)置請(qǐng)求的處理程序之后的效果
handlerImp
只針對(duì)Method做了不同的響應(yīng),沒有對(duì)PATH做任何的判斷,所以無論請(qǐng)求什么樣的路徑都能拿到一個(gè)預(yù)期的響應(yīng)。
$ curl -X POST 127.0.0.1:8080/foo Receive POST request% $ curl 127.0.0.1:8080/foo/bar Receive GET request%
此時(shí)再體會(huì)一下這句話:我們?cè)O(shè)置的不是路由,而是設(shè)置請(qǐng)求的處理程序
再聊Handler
type handlerImp struct { } func (imp handlerImp) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { w.Write([]byte("Receive GET request")) return } if r.Method == "POST" { w.Write([]byte("Receive POST request")) return } return }
如上所述,無論任何PATH,任何Method等,所有的請(qǐng)求都會(huì)被handlerImp.ServeHTTP
處理。
我們可以判斷PATH、Method等,根據(jù)不同的請(qǐng)求特征執(zhí)行不同的邏輯,并且全部在這一個(gè)函數(shù)中全部完成
很明顯,這違反了高內(nèi)聚,低耦合的編程范式
停下來思考下,如何編寫一個(gè)高內(nèi)聚,低耦合的handlerImp.ServeHTTP
,使之針對(duì)不同HTTP請(qǐng)求執(zhí)行不同的邏輯呢
type handlerImp struct { } func (imp handlerImp) handleMethodGet(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive GET request")) return } func (imp handlerImp) handleMethodPost(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive POST request")) return } func (imp handlerImp) handlePathFoo(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) return } func (imp handlerImp) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/foo" { imp.handlePathFoo(w, r) return } if r.Method == "GET" { imp.handleMethodGet(w, r) return } if r.Method == "POST" { imp.handleMethodPost(w, r) return } return }
如果你的答案和上面的代碼類似,那么我對(duì)于這段代碼的點(diǎn)評(píng)是:不太高明??
?? 如何編寫一個(gè)高內(nèi)聚,低耦合的ServeHTTP
,針對(duì)不同HTTP請(qǐng)求執(zhí)行不同的邏輯?
不知道你有沒有聽過設(shè)計(jì)模式中,組合模式。沒有了解可以去了解下,或者看下圖
經(jīng)過組合模式重新設(shè)計(jì)的handlerImp
,已經(jīng)不再包含具體的邏輯了,它先搜索有沒有針對(duì)PATH處理的邏輯,再搜索有沒有針對(duì)Method處理的邏輯,它專注于邏輯分派,它是組合模式中的容器。
容器(Container):容器接收到請(qǐng)求后會(huì)將工作分配給自己的子項(xiàng)目, 處理中間結(jié)果, 然后將最終結(jié)果返回給客戶端。
type handlerImp struct { pathHandlers map[string]http.Handler methodHandlers map[string]http.Handler } func NewHandlerImp() handlerImp { return handlerImp{ pathHandlers: make(map[string]http.Handler), methodHandlers: make(map[string]http.Handler), } } func (imp handlerImp) AddPathHandler(path string, h http.Handler) { imp.pathHandlers[path] = h } func (imp handlerImp) AddMethodHandler(method string, h http.Handler) { imp.methodHandlers[method] = h } func (imp handlerImp) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h, ok := imp.pathHandlers[r.URL.Path]; ok { h.ServeHTTP(w, r) return } if h, ok := imp.methodHandlers[r.Method]; ok { h.ServeHTTP(w, r) return } return }
重新設(shè)計(jì)的handlerImp
不執(zhí)行邏輯,實(shí)際的邏輯被分離到每一個(gè)葉子結(jié)點(diǎn)中,而每一個(gè)葉子結(jié)點(diǎn)也都實(shí)現(xiàn)了ServeHTTP
函數(shù),即Handler
接口
type PathFoo struct { } func (m PathFoo) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) return } type MethodGet struct { } func (m MethodGet) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive GET request")) return } type MethodPost struct { } func (m MethodPost) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive POST request")) return }
再次強(qiáng)調(diào),通過對(duì)組合模式的運(yùn)用,我們把邏輯分派的功能聚合到handlerImp
,把具體的邏輯聚合到PathFoo
、MethodGet
、MethodPost
func main() { // 方式3 ln, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } h := NewHandlerImp() h.AddMethodHandler("GET", MethodGet{}) h.AddMethodHandler("POST", MethodPost{}) h.AddPathHandler("/foo", PathFoo{}) server := &http.Server{Handler: h} err = server.Serve(ln) if err != nil { panic(err) } }
一些Handlers
上面實(shí)現(xiàn)的handlerImp
利用組合設(shè)計(jì)模式,已經(jīng)能針對(duì)Path和Method設(shè)定和處理不同的邏輯,但整體功能略顯簡(jiǎn)單。有哪些可以供我們使用且功能強(qiáng)大的Handlers
呢?
http.ServeMux
Go標(biāo)準(zhǔn)庫中就提供了一個(gè)Handler
實(shí)現(xiàn)叫作http.ServeMux
?? 當(dāng)前(go1.21.*)版本僅支持匹配Path,但目前已經(jīng)在討論支持Method匹配和占位符了:net/http: add methods and path variables to ServeMux patterns #60227[1]
使用的方式如下
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() { // 方式3 ln, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } mx := http.NewServeMux() mx.Handle("/bar/", PathBar{}) mx.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) }) server := &http.Server{Handler: mx} err = server.Serve(ln) if err != nil { panic(err) } }
代碼mx.Handle("/bar/", PathBar{})
中/bar/
由/
結(jié)尾,所以它可以匹配/bar/*
所有的Path
關(guān)于http.ServeMux
的細(xì)節(jié)不是本篇重點(diǎn),后續(xù)會(huì)單獨(dú)介紹
?? 默認(rèn)的Handler
因?yàn)槭菢?biāo)準(zhǔn)庫內(nèi)置的實(shí)現(xiàn),當(dāng)沒有設(shè)置http.Server.Handler
屬性時(shí),http.Server
就會(huì)使用一個(gè)全局的變量DefaultServeMux *ServeMux
來作為http.Server.Handler
的值
var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux
http包同時(shí)提供了兩個(gè)函數(shù)可以在DefaultServeMux
注冊(cè)不同Path的處理函數(shù)
func main() { http.Handle("/bar/", PathBar{}) http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) }) // 方式1 err := http.ListenAndServe(":8080", nil) if err != nil { panic(err) } }
http.Handle
接收的是Handler
接口實(shí)現(xiàn),對(duì)應(yīng)的是
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
http.HandleFunc
接收的是匿名函數(shù),對(duì)應(yīng)的是
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
gorilla/mux
gorilla/mux是一個(gè)相當(dāng)流行的第三方庫,用法這里簡(jiǎn)單寫下
除了經(jīng)典的Handle
、HandleFunc
函數(shù),gorilla/mux還提供了Methods
、Schemes
、Host
等非常復(fù)雜的功能
但無論多復(fù)雜,其一定包含了ServeHTTP
函數(shù),即實(shí)現(xiàn)了Handler
接口
func main() { r := mux.NewRouter() r.Handle("/foo/{bar}", PathBar{}) r.Handle("/bar/", PathBar{}) r.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive path foo")) }) r.Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive GET request")) }) // 方式1 err := http.ListenAndServe(":8080", r) if err != nil { panic(err) } }
其他
還有很多其他優(yōu)秀的mux實(shí)現(xiàn),具體可以參考各自的官方文檔。
https://github.com/go-chi/chi star 15.9k
https://github.com/julienschmidt/httprouter star 15.6k
關(guān)于Go標(biāo)準(zhǔn)庫、第三方庫中這些結(jié)構(gòu)的關(guān)系通過下圖展示
再聊組合模式
無論是官方的http.ServeMux
,還是一些第三方庫,實(shí)現(xiàn)上大多使用了組合設(shè)計(jì)模式
組合模式的魔力還不止于此。思考一下這個(gè)場(chǎng)景:目前已經(jīng)存在路由servemux/*
,并且使用了ServeMux
mx := http.NewServeMux() mx.Handle("/servemux/bar/", PathBar{}) mx.HandleFunc("/servemux/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive servemux path foo")) })
但此時(shí)還有另外一組路由/gorilla/*
,使用了開源庫gorilla/mux
r := mux.NewRouter() r.Handle("/gorilla/bar/", PathBar{}) r.HandleFunc("/gorilla/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive gorilla path foo")) })
如何啟動(dòng)這樣的服務(wù)器呢?
func main() { mx := http.NewServeMux() mx.Handle("/servemux/bar/", PathBar{}) mx.HandleFunc("/servemux/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive servemux path foo")) }) r := mux.NewRouter() r.Handle("/gorilla/bar/", PathBar{}) r.HandleFunc("/gorilla/foo", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Receive gorilla path foo")) }) h := http.NewServeMux() h.Handle("/servemux/", mx) h.Handle("/gorilla/", r) // 方式1 err := http.ListenAndServe(":8080", h) if err != nil { panic(err) } }
利用組合設(shè)計(jì)模式,h := http.NewServeMux()
作為新的容器,將不同的路由分配給另外兩個(gè)容器
mx := http.NewServeMux()
r := mux.NewRouter()
總結(jié)
本文主要介紹了Go http server的啟動(dòng)方式,重點(diǎn)介紹了http server的請(qǐng)求處理器
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
別看它僅包含一個(gè)方法,但在組合模式的加成下,可以實(shí)現(xiàn)千變?nèi)f化的形態(tài)。
除了Go標(biāo)準(zhǔn)庫中提供了http.ServeMux
還有一系列開源庫gorilla/mux
、go-chi/chi
、julienschmidt/httprouter
對(duì)Handler
進(jìn)行了實(shí)現(xiàn)。
每一個(gè)庫具有的能力、使用方式、性能不同,但萬變不離其宗,都繞不開組合模式和Handler
接口
以上就是Go標(biāo)準(zhǔn)庫http server優(yōu)雅啟動(dòng)深入理解的詳細(xì)內(nèi)容,更多關(guān)于Go標(biāo)準(zhǔn)庫http server啟動(dòng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實(shí)現(xiàn)常見排序算法的示例代碼
現(xiàn)在的面試真的是越來越卷了,算法已經(jīng)成為了面試過程中必不可少的一個(gè)環(huán)節(jié),你如果想進(jìn)稍微好一點(diǎn)的公司,算法是必不可少的一個(gè)環(huán)節(jié)。本文為大家準(zhǔn)備了Golang實(shí)現(xiàn)常見排序算法的示例代碼,需要的可以參考一下2022-05-05golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray詳解
這篇文章主要介紹了golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09Go語言實(shí)現(xiàn)的web爬蟲實(shí)例
這篇文章主要介紹了Go語言實(shí)現(xiàn)的web爬蟲,實(shí)例分析了web爬蟲的原理與Go語言的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02golang?使用sort.slice包實(shí)現(xiàn)對(duì)象list排序
這篇文章主要介紹了golang?使用sort.slice包實(shí)現(xiàn)對(duì)象list排序,對(duì)比sort跟slice兩種排序的使用方式區(qū)別展開內(nèi)容,需要的小伙伴可以參考一下2022-03-03