新聞中心
如何用最少的代碼創(chuàng)建一個HTTP server?

公司主營業(yè)務(wù):成都做網(wǎng)站、成都網(wǎng)站設(shè)計、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。成都創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊有機(jī)會用頭腦與智慧不斷的給客戶帶來驚喜。成都創(chuàng)新互聯(lián)公司推出武陟免費(fèi)做網(wǎng)站回饋大家。
package main
import (
"net"
"net/http"
)
func main() {
// 方式1
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}點開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()如其名會干兩件事
- 監(jiān)聽一個端口,即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!
按上面的代碼啟動HTTP Server沒有太大意義,因為我們還沒有設(shè)定路由,所以無法正常響應(yīng)請求
$ curl 127.0.0.1:8080
404 page not found暫停思考一下,服務(wù)器返回404是因為沒有設(shè)定路由么?no,no,no,你需要轉(zhuǎn)變一下思維。服務(wù)器返回404不是因為我們沒有設(shè)置路由,而是因為沒有設(shè)置請求的處理程序,這個處理程序在Go中叫作:Handler!
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}怎么定義請求的處理程序?
由上可知,僅需要實現(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è)置請求的處理程序?
圖片
三種方式本質(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è)置請求的處理程序之后的效果
handlerImp只針對Method做了不同的響應(yīng),沒有對PATH做任何的判斷,所以無論請求什么樣的路徑都能拿到一個預(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è)置的不是路由,而是設(shè)置請求的處理程序
再聊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等,所有的請求都會被handlerImp.ServeHTTP處理。
我們可以判斷PATH、Method等,根據(jù)不同的請求特征執(zhí)行不同的邏輯,并且全部在這一個函數(shù)中全部完成
很明顯,這違反了高內(nèi)聚,低耦合的編程范式
停下來思考下,如何編寫一個高內(nèi)聚,低耦合的handlerImp.ServeHTTP,使之針對不同HTTP請求執(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
}如果你的答案和上面的代碼類似,那么我對于這段代碼的點評是:不太高明?
如何編寫一個高內(nèi)聚,低耦合的ServeHTTP,針對不同HTTP請求執(zhí)行不同的邏輯?
不知道你有沒有聽過設(shè)計模式中,組合模式。沒有了解可以去了解下,或者看下圖
圖片
經(jīng)過組合模式重新設(shè)計的handlerImp,已經(jīng)不再包含具體的邏輯了,它先搜索有沒有針對PATH處理的邏輯,再搜索有沒有針對Method處理的邏輯,它專注于邏輯分派,它是組合模式中的容器。
容器(Container):容器接收到請求后會將工作分配給自己的子項目, 處理中間結(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è)計的handlerImp不執(zhí)行邏輯,實際的邏輯被分離到每一個葉子結(jié)點中,而每一個葉子結(jié)點也都實現(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),通過對組合模式的運(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
上面實現(xiàn)的handlerImp利用組合設(shè)計模式,已經(jīng)能針對Path和Method設(shè)定和處理不同的邏輯,但整體功能略顯簡單。有哪些可以供我們使用且功能強(qiáng)大的Handlers呢?
http.ServeMux
Go標(biāo)準(zhǔn)庫中就提供了一個Handler實現(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提供兩個函數(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() {
// 方式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é)不是本篇重點,后續(xù)會單獨介紹
默認(rèn)的Handler
因為是標(biāo)準(zhǔn)庫內(nèi)置的實現(xiàn),當(dāng)沒有設(shè)置http.Server.Handler屬性時,http.Server就會使用一個全局的變量DefaultServeMux *ServeMux來作為http.Server.Handler的值
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMuxhttp包同時提供了兩個函數(shù)可以在DefaultServeMux注冊不同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接口實現(xiàn),對應(yīng)的是
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }http.HandleFunc 接收的是匿名函數(shù),對應(yīng)的是
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}gorilla/mux[2]
gorilla/mux是一個相當(dāng)流行的第三方庫,用法這里簡單寫下
除了經(jīng)典的Handle、HandleFunc函數(shù),gorilla/mux還提供了Methods、Schemes、Host等非常復(fù)雜的功能
但無論多復(fù)雜,其一定包含了ServeHTTP函數(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實現(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,還是一些第三方庫,實現(xiàn)上大多使用了組合設(shè)計模式
組合模式的魔力還不止于此。思考一下這個場景:目前已經(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"))
})但此時還有另外一組路由/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"))
})如何啟動這樣的服務(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è)計模式,h := http.NewServeMux()作為新的容器,將不同的路由分配給另外兩個容器
- mx := http.NewServeMux()
- r := mux.NewRouter()
圖片
總結(jié)
本文主要介紹了Go http server的啟動方式,重點介紹了http server的請求處理器
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}別看它僅包含一個方法,但在組合模式的加成下,可以實現(xiàn)千變?nèi)f化的形態(tài)。
除了Go標(biāo)準(zhǔn)庫中提供了http.ServeMux還有一系列開源庫gorilla/mux、go-chi/chi、julienschmidt/httprouter對Handler進(jìn)行了實現(xiàn)。
每一個庫具有的能力、使用方式、性能不同,但萬變不離其宗,都繞不開組合模式和Handler接口
參考資料
[1]net/http: add methods and path variables to ServeMux patterns #60227: https://github.com/golang/go/discussions/60227
[2]gorilla/mux: https://github.com/gorilla/mux#gorillamux
[3]設(shè)計模式/結(jié)構(gòu)型模式/組合模式: https://refactoringguru.cn/design-patterns/composite
網(wǎng)站題目:深入理解Go標(biāo)準(zhǔn)庫-HTTP-Server的啟動
網(wǎng)站路徑:http://m.fisionsoft.com.cn/article/dphgpjh.html


咨詢
建站咨詢
