golang構(gòu)建HTTP服務(wù)的實現(xiàn)步驟
???一個go最簡單的Http服務(wù)器程序
package main import ( "fmt" "net/http" ) func IndexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world") } func main() { http.HandleFunc("/", IndexHandler) http.ListenAndServe("127.0.0.0:8000", nil) }
HTTP
? ?除去細節(jié),理解HTTP構(gòu)建的網(wǎng)絡(luò)應(yīng)用只要關(guān)注兩個端--客戶端(client)和服務(wù)端(server),兩個端的交互來自client的request,以及server端的response。所謂的http服務(wù)器,主要在于如何接受client的request,并向client返回response。
? ?接收request的過程中,最重要的莫過于路由(router),即實現(xiàn)一個Multiplexer器。Go中既可以使用內(nèi)置的mutilplexer--DefaultServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函數(shù)(hander),后者將對request進行處理,同時構(gòu)建response。
? 簡單總結(jié)就是這個流程:
? 因此,理解go中的http服務(wù),,最重要的就是要理解Multiplexer和hander,Golang中的Multiplexer基于ServerMux結(jié)構(gòu),同時也實現(xiàn)了Handler接口。
? ·hander函數(shù):具有func(w http.ResponseWriter, r *http.Requests)簽名的函數(shù)
? ·handler處理器(函數(shù)):經(jīng)過HanderFunc結(jié)構(gòu)包裝的handler函數(shù),它實現(xiàn)了ServeHTTP接口方法的函數(shù)。調(diào)用handler處理器的ServeHTTP方法時,即調(diào)用handler函數(shù)本身。
? ·handler對象:實現(xiàn)了Hander接口ServeHTTP方法的結(jié)構(gòu)。
? ?Golang的http處理流程可以用下面一張圖表示,后面內(nèi)容是針對圖進行說明:
Handler
? ?Golang沒有繼承,類多態(tài)的方法可以通過接口實現(xiàn)。所謂接口則是定義聲明了函數(shù)簽名,任何結(jié)構(gòu)只要實現(xiàn)了與接口函數(shù)簽名相同的方法,就等同于實現(xiàn)了接口。go的http服務(wù)都是基于handler進行處理的。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
??? 任何結(jié)構(gòu)體,只要實現(xiàn)了ServeHTTP方法,這個結(jié)構(gòu)就可以稱之為handler對象。ServeMux會使用handler并調(diào)用其ServeHTTP方法處理請求并返回響應(yīng)。
ServeMux
? ?了解了Handler之后,再看ServeMux。ServeMux源碼很簡單:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool } type muxEntry struct { explicit bool h Handler pattern string }
???ServeMux結(jié)構(gòu)中最重要的字段為m,這是一個map,key是一些url模式,value是一個muxEntry結(jié)構(gòu),后者里定義存儲了具體的url模式和handler。?
? ?當(dāng)然,所謂的ServeMux也實現(xiàn)了ServeHTTP接口,也算是一個handler,不過ServeMux的ServeHTTP方法不是用來處理request和respone,而是用來找到路由注冊的handler。
Server
? ?除了ServeMux和Handler,還有一個結(jié)構(gòu)Server需要了解。從http.ListenAndServe的源碼可以看出,它創(chuàng)建了一個server對象,并調(diào)用server對象的ListenAndServe方法:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
???查看server的結(jié)構(gòu)如下:
type Server struct { Addrstring HandlerHandler ReadTimeout time.Duration WriteTimeout time.Duration TLSConfig *tls.Config MaxHeaderBytes int TLSNextProto map[string]func(*Server, *tls.Conn, Handler) ConnState func(net.Conn, ConnState) ErrorLog *log.Logger disableKeepAlives int32 nextProtoOnce sync.Once nextProtoErrerror }
???server結(jié)構(gòu)存儲了服務(wù)器處理請求常見的字段。其中Handler字段也保留Hander接口。如果Server接口沒有提供Handler結(jié)構(gòu)對象,那么會使用DefaultServeMux做Multiplexer。
創(chuàng)建HTTP服務(wù)
??? 創(chuàng)建一個http服務(wù),大致需要經(jīng)歷兩個過程,首先需要注冊路由,即提供url模式和handler函數(shù)的映射,其次就是實例化一個server對象,并開啟對客戶端的監(jiān)聽。
http.HandleFunc("/", indexHandler)
http.ListenAndServe("127.0.0.1:8000", nil) 或者: server := &Server{Addr: addr, Handler: handler} server.ListenAndServe()
http注冊路由
? ? net/http包暴露的注冊路由的api很簡單,http.HandleFunc選取了DefaultServeMux作為multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
???實際上,DefaultServeMux是ServeMux的一個實例。當(dāng)然http包也提供了NewServeMux方法創(chuàng)建一個ServeMux實例,默認(rèn)則創(chuàng)建一個DefaultServeMux:
// NewServeMux allocates and returns a new ServeMux. func NewServeMux() *ServeMux { return new(ServeMux) } // DefaultServeMux is the default ServeMux used by Serve. var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux
???注意,go創(chuàng)建實例的過程中,也可以使用指針方式,即
type Server struct{} server := Server{}
? ?和下面的一樣都可以創(chuàng)建Server的實例
var DefaultServer Server var server = &DefalutServer
???因此DefaultServeMux的HandleFunc(pattern,handler)方法實際是定義在ServeMux下的:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) }
???上述代碼中,HandlerFunc是一個函數(shù)類型。同時實現(xiàn)了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數(shù),其目的就是為了讓這個函數(shù)也實現(xiàn)ServeHTTP方法,即轉(zhuǎn)變成一個handler處理器(函數(shù))。
type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
???一旦這樣做了,就意味著我們的indexHandler函數(shù)也有了ServeHTTP方法。
? ?此外,ServeMux的Handle方法,將會對pattern和handler函數(shù)做一個map映射:
func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic("http: invalid pattern " + pattern) } if handler == nil { panic("http: nil handler") } if mux.m[pattern].explicit { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) } mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true } n := len(pattern) if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { path := pattern if pattern[0] != '/' { path = pattern[strings.Index(pattern, "/"):] } url := &url.URL{Path: path} mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern} } }
???由此可見,Handle函數(shù)的主要目的在于把handler和pattern模式綁定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息。Server的m字段就是map[string]muxEntry這樣一個map。
? ?此時,pattern和handler的路由注冊完成。
開啟監(jiān)聽
? ? 注冊好路由之后,啟動web服務(wù)還需要開啟服務(wù)器監(jiān)聽。http的ListenAndServer方法中可以看到創(chuàng)建了一個Server對象,并調(diào)用了Server對象的同名方法:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } func (srv Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(net.TCPListener)}) }
???Server的ListenAndServer方法中,會初始化監(jiān)聽地址Addr,同時調(diào)用Listen方法設(shè)置監(jiān)聽。最后將監(jiān)聽的TCP對象傳入Serve方法:
func (srv *Server) Serve(l net.Listener) error { defer l.Close() ... baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr()) for { rw, e := l.Accept() ... c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) } }
處理請求
???監(jiān)聽開啟之后,一旦客戶端請求到底,go就開啟一個協(xié)程處理請求,主要邏輯都在server方法之中。
? ?serve方法比較長,其主要職能就是,創(chuàng)建一個上下文對象,然后調(diào)用Listener的Accept方法用來獲取連接數(shù)據(jù)并使用newConn方法創(chuàng)建連接對象。最后使用goroutein協(xié)程的方式處理連接請求。因此每一個連接都開啟了一個協(xié)程,請求的上下文都不同,同時又保證了go的高并發(fā)。serve也是一個長長的方法:
func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if !c.hijacked() { c.close() c.setState(c.rwc, StateClosed) } }() ... for { w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } ... } ... serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle) } }
???盡管serve很長,里面的結(jié)構(gòu)和邏輯還是很清晰的,使用defer定義了函數(shù)退出時,連接關(guān)閉相關(guān)的處理。然后就是讀取連接的網(wǎng)絡(luò)數(shù)據(jù),并處理讀取完畢時候的狀態(tài)。接下來就是調(diào)用serverHandler{c.server}.ServeHTTP(w,w.req)方法處理請求了。最后就是請求處理完畢的邏輯。serverHandler是一個重要的結(jié)構(gòu),它僅有一個字段,即Server結(jié)構(gòu),同時它也實現(xiàn)了Hander接口方法ServeHTTP,同時它也實現(xiàn)了Handler接口方法ServeHTTP,并在該接口方法中做了一個重要的事情,初始化multiplexer路由多路復(fù)用器。如果server對象沒有制定Handler,則使用默認(rèn)的DefaultServeMux作為路由Multiplexer。并調(diào)用初始化Handler的ServeHTTP方法。
type serverHandler struct { srv *Server } func (sh serverHandler) ServeHTTP(rw ResponseWriter, req Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
???這里DefaultServeMux的ServeHTTP方法其實也是定義在ServeMux結(jié)構(gòu)中的,相關(guān)代碼如下:
func (mux *ServeMux) (w ResponseWriter, r Request) { if r.RequestURI == "" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) } func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { if r.Method != "CONNECT" { if p := cleanPath(r.URL.Path); p != r.URL.Path { _, pattern = mux.handler(r.Host, p) url := *r.URL url.Path = p return RedirectHandler(url.String(), StatusMovedPermanently), pattern } } return mux.handler(r.Host, r.URL.Path) } func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return } func (mux *ServeMux) match(path string) (h Handler, pattern string) { var n = 0 for k, v := range mux.m { if !pathMatch(k, path) { continue } if h == nil || len(k) > n { n = len(k) h = v.h pattern = v.pattern } } return }
???mux的ServeHTTP方法通過調(diào)用其Handler方法尋找注冊到路由上的handler函數(shù),并調(diào)用該函數(shù)的ServeHTTP方法。?
? ?mux的Handler方法對URL簡單的處理,然后調(diào)用handler方法,后者會創(chuàng)建一個鎖,同時調(diào)用match方法返回一個handler和pattern。
? ?在match方法中,mux的m字段是map[string]muxEntry,后者存儲了pattern和handler處理器函數(shù),因此通過迭代m尋找出注冊路由的pattern模式與實際url匹配的handler函數(shù)并返回。
? ?返回的結(jié)構(gòu)一直傳遞到mux的ServeHTTP方法,接下來調(diào)用handler函數(shù)的ServeHTTP方法,即IndexHandler函數(shù),然后把reponse寫到http.RequestWirter對象返回給客戶端。
? ?上述函數(shù)運行結(jié)束即serverHandler{c.server}.ServeHTTP(w, w.req)運行結(jié)束。接下來就是對請求處理完畢之后上希望和連接斷開的相關(guān)邏輯。
? ?至此,Golang中一個完整的http服務(wù)介紹完畢,包括注冊路由,開啟監(jiān)聽,處理連接,路由處理函數(shù)。
參考:
https://www.yuque.com/docs/share/24a1bd1c-e32d-4268-9115-bf03f53677d3
到此這篇關(guān)于golang構(gòu)建HTTP服務(wù)的實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)golang構(gòu)建HTTP服務(wù)內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。