實(shí)現(xiàn)一個(gè)最簡(jiǎn)單 一個(gè)Go最簡(jiǎn)單的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) } 上述代碼究竟有什么魔法呢?下面將會(huì)解密go的 HTTP網(wǎng)絡(luò)發(fā)展,很多網(wǎng)絡(luò)應(yīng)用都是構(gòu)建再 HTTP 服務(wù)基礎(chǔ)之上。HTTP 協(xié)議從誕生到現(xiàn)在,發(fā)展從1.0,1.1到2.0也不斷再進(jìn)步。除去細(xì)節(jié),理解 HTTP 構(gòu)建的網(wǎng)絡(luò)應(yīng)用只要關(guān)注兩個(gè)端---客戶端(clinet)和服務(wù)端(server),兩個(gè)端的交互來自 clinet 的 request,以及server端的response。所謂的http服務(wù)器,主要在于如何接受 clinet 的 request,并向client返回response。 接收request的過程中,最重要的莫過于路由( 簡(jiǎn)單總結(jié)就是這個(gè)流程為: Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet 因此,理解go中的http服務(wù),最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于
hander-handler處理器.jpeg Golang的http處理流程可以用下面一張圖表示,后面內(nèi)容是針對(duì)圖進(jìn)行說明: go-http.png HandlerGolang沒有繼承,類多態(tài)的方式可以通過接口實(shí)現(xiàn)。所謂接口則是定義聲明了函數(shù)簽名,任何結(jié)構(gòu)只要實(shí)現(xiàn)了與接口函數(shù)簽名相同的方法,就等同于實(shí)現(xiàn)了接口。go的http服務(wù)都是基于handler進(jìn)行處理。 type Handler interface { ServeHTTP(ResponseWriter, *Request) } 任何結(jié)構(gòu)體,只要實(shí)現(xiàn)了ServeHTTP方法,這個(gè)結(jié)構(gòu)就可以稱之為handler對(duì)象。ServeMux會(huì)使用handler并調(diào)用其ServeHTTP方法處理請(qǐng)求并返回響應(yīng)。 ServeMux了解了Handler之后,再看ServeMux。ServeMux的源碼很簡(jiǎn)單: 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)中最重要的字段為
Server除了ServeMux和Handler,還有一個(gè)結(jié)構(gòu)Server需要了解。從 func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } 查看server的結(jié)構(gòu)如下: type Server struct { Addr string Handler Handler 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 nextProtoErr error } server結(jié)構(gòu)存儲(chǔ)了服務(wù)器處理請(qǐng)求常見的字段。其中Handler字段也保留Handler接口。如果Server接口沒有提供Handler結(jié)構(gòu)對(duì)象,那么會(huì)使用DefautServeMux做multiplexer,后面再做分析。 創(chuàng)建HTTP服務(wù)創(chuàng)建一個(gè)http服務(wù),大致需要經(jīng)歷兩個(gè)過程,首先需要注冊(cè)路由,即提供url模式和handler函數(shù)的映射,其次就是實(shí)例化一個(gè)server對(duì)象,并開啟對(duì)客戶端的監(jiān)聽。 再看gohttp服務(wù)的代碼 http.HandleFunc("/", indexHandler) 即是注冊(cè)路由。 http.ListenAndServe("127.0.0.1:8000", nil) 或者: server := &Server{Addr: addr, Handler: handler} server.ListenAndServe() 注冊(cè)路由閱讀框架源碼是學(xué)習(xí)的好方式,通常閱讀也有兩個(gè)方法,一是不求甚解,框架的主要流程要清晰,別的細(xì)枝末節(jié),如果尚不能理解作者的用意,可以先忽略,不必馬上深究;其次,庖丁解牛,對(duì)于作者想要表達(dá)的主要流程,一定要明確,執(zhí)行的邏輯和結(jié)構(gòu)。兩者看起來略矛盾,其實(shí)不然。大體而言就是對(duì)主流程要清晰,主流程以外的細(xì)節(jié)需要先忽略。最簡(jiǎn)單實(shí)踐方式就是,看不懂的就先放一邊。直到所有的都看不懂,再回去看以前不懂的部分,搞懂為止。下面就查看http是如何注冊(cè)路由。 net/http包暴露的注冊(cè)路由的api很簡(jiǎn)單,http.HandleFunc選取了DefaultServeMux作為multiplexer: func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } 那么什么是DefaultServeMux呢?實(shí)際上,DefaultServeMux是ServeMux的一個(gè)實(shí)例。當(dāng)然http包也提供了 // 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
因此DefaultServeMux的HandleFunc(pattern, handler)方法實(shí)際是定義在ServeMux下的: func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { 上述代碼中,HandlerFunc是一個(gè)函數(shù)類型。同時(shí)實(shí)現(xiàn)了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數(shù),其目的就是為了讓這個(gè)函數(shù)也實(shí)現(xiàn)ServeHTTP方法,即轉(zhuǎn)變成一個(gè)handler處理器(函數(shù))。 type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { 一旦這樣做了,就意味著我們的 indexHandler 函數(shù)也有了ServeHTTP方法。 此外,ServeMux的Handle方法,將會(huì)對(duì)pattern和handler函數(shù)做一個(gè)map映射: func (mux *ServeMux) Handle(pattern string, handler Handler) { 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結(jié)構(gòu)嗎?Server的m字段就是map[string]muxEntry這樣一個(gè)map。 此時(shí),pattern和handler的路由注冊(cè)完成。接下來就是如何開始server的監(jiān)聽,以接收客戶端的請(qǐng)求。 #### 開啟監(jiān)聽 注冊(cè)好路由之后,啟動(dòng)web服務(wù)還需要開啟服務(wù)器監(jiān)聽。http的ListenAndServer方法中可以看到創(chuàng)建了一個(gè)Server對(duì)象,并調(diào)用了Server對(duì)象的同名方法: func ListenAndServe(addr string, handler Handler) error { func (srv Server) ListenAndServe() error { Server的ListenAndServe方法中,會(huì)初始化監(jiān)聽地址Addr,同時(shí)調(diào)用Listen方法設(shè)置監(jiān)聽。最后將監(jiān)聽的TCP對(duì)象傳入Serve方法: func (srv *Server) Serve(l net.Listener) error { 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) } } #### 處理請(qǐng)求 監(jiān)聽開啟之后,一旦客戶端請(qǐng)求到底,go就開啟一個(gè)協(xié)程處理請(qǐng)求,主要邏輯都在serve方法之中。 serve方法比較長(zhǎng),其主要職能就是,創(chuàng)建一個(gè)上下文對(duì)象,然后調(diào)用Listener的Accept方法用來 獲取連接數(shù)據(jù)并使用newConn方法創(chuàng)建連接對(duì)象。最后使用goroutein協(xié)程的方式處理連接請(qǐng)求。因?yàn)槊恳粋€(gè)連接都開起了一個(gè)協(xié)程,請(qǐng)求的上下文都不同,同時(shí)又保證了go的高并發(fā)。serve也是一個(gè)長(zhǎng)長(zhǎng)的方法: func (c *conn) serve(ctx context.Context) { ... 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很長(zhǎng),里面的結(jié)構(gòu)和邏輯還是很清晰的,使用defer定義了函數(shù)退出時(shí),連接關(guān)閉相關(guān)的處理。然后就是讀取連接的網(wǎng)絡(luò)數(shù)據(jù),并處理讀取完畢時(shí)候的狀態(tài)。接下來就是調(diào)用`serverHandler{c.server}.ServeHTTP(w, w.req)`方法處理請(qǐng)求了。最后就是請(qǐng)求處理完畢的邏輯。serverHandler是一個(gè)重要的結(jié)構(gòu),它近有一個(gè)字段,即Server結(jié)構(gòu),同時(shí)它也實(shí)現(xiàn)了Handler接口方法ServeHTTP,并在該接口方法中做了一個(gè)重要的事情,初始化multiplexer路由多路復(fù)用器。如果server對(duì)象沒有指定Handler,則使用默認(rèn)的DefaultServeMux作為路由Multiplexer。并調(diào)用初始化Handler的ServeHTTP方法。 type serverHandler struct { func (sh serverHandler) ServeHTTP(rw ResponseWriter, req Request) { 這里DefaultServeMux的ServeHTTP方法其實(shí)也是定義在ServeMux結(jié)構(gòu)中的,相關(guān)代碼如下: func (mux *ServeMux) (w ResponseWriter, r Request) { func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { // 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) { mux的ServeHTTP方法通過調(diào)用其Handler方法尋找注冊(cè)到路由上的handler函數(shù),并調(diào)用該函數(shù)的ServeHTTP方法,本例則是IndexHandler函數(shù)。 mux的Handler方法對(duì)URL簡(jiǎn)單的處理,然后調(diào)用handler方法,后者會(huì)創(chuàng)建一個(gè)鎖,同時(shí)調(diào)用match方法返回一個(gè)handler和pattern。 在match方法中,mux的m字段是map[string]muxEntry圖,后者存儲(chǔ)了pattern和handler處理器函數(shù),因此通過迭代m尋找出注冊(cè)路由的patten模式與實(shí)際url匹配的handler函數(shù)并返回。 返回的結(jié)構(gòu)一直傳遞到mux的ServeHTTP方法,接下來調(diào)用handler函數(shù)的ServeHTTP方法,即IndexHandler函數(shù),然后把response寫到http.RequestWirter對(duì)象返回給客戶端。 上述函數(shù)運(yùn)行結(jié)束即`serverHandler{c.server}.ServeHTTP(w, w.req)`運(yùn)行結(jié)束。接下來就是對(duì)請(qǐng)求處理完畢之后上希望和連接斷開的相關(guān)邏輯。 至此,Golang中一個(gè)完整的http服務(wù)介紹完畢,包括注冊(cè)路由,開啟監(jiān)聽,處理連接,路由處理函數(shù)。 ### 總結(jié) 多數(shù)的web應(yīng)用基于HTTP協(xié)議,客戶端和服務(wù)器通過request-response的方式交互。一個(gè)server并不可少的兩部分莫過于路由注冊(cè)和連接處理。Golang通過一個(gè)ServeMux實(shí)現(xiàn)了的multiplexer路由多路復(fù)用器來管理路由。同時(shí)提供一個(gè)Handler接口提供ServeHTTP用來實(shí)現(xiàn)handler處理其函數(shù),后者可以處理實(shí)際request并構(gòu)造response。 ServeMux和handler處理器函數(shù)的連接橋梁就是Handler接口。ServeMux的ServeHTTP方法實(shí)現(xiàn)了尋找注冊(cè)路由的handler的函數(shù),并調(diào)用該handler的ServeHTTP方法。ServeHTTP方法就是真正處理請(qǐng)求和構(gòu)造響應(yīng)的地方。 回顧go的http包實(shí)現(xiàn)http服務(wù)的流程,可見大師們的編碼設(shè)計(jì)之功力。學(xué)習(xí)有利提高自身的代碼邏輯組織能力。更好的學(xué)習(xí)方式除了閱讀,就是實(shí)踐,接下來,我們將著重討論來構(gòu)建http服務(wù)。尤其是構(gòu)建http中間件函數(shù)。 |
|