因爲Golang優秀的併發處理,不少公司使用Golang編寫微服務。對於Golang來講,只須要短短几行代碼就能夠實現一個簡單的Http服務器。加上Golang的協程,這個服務器能夠擁有極高的性能。然而,正是由於代碼過於簡單,咱們才應該去研究他的底層實現,作到會用,也知道爲何這麼用。程序員
在本文中,會以自頂向下的方式,從如何使用,到如何實現,一點點的分析Golang中net/http這個包中關於Http服務器的實現方式。內容可能會愈來愈難理解,做者會盡可能把這些源碼講的更清楚一些,但願對各位有所幫助。數組
首先,咱們以怎麼用爲起點。緩存
畢竟,知道了怎麼用,才能一步一步的深刻挖掘爲何這麼用。服務器
先來看第一種最簡單的建立方式(省略了導包):併發
func helloWorldHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World !") } func main() { http.HandleFunc("/", helloWorldHandler) http.ListenAndServe(":8000", nil) }
其實在這一部分中,代碼應該很容易理解。就是先作一個映射,把須要訪問的地址,和訪問後執行的函數,寫在一塊兒。而後再加上監聽的端口,就能夠了。app
若是你是一個Java程序員,你應該能發覺這個和Java中的Servlet很類似。也是建立一個個的Servlet,而後註冊。框架
再來看看第二種建立方式,也同樣省略了導包:tcp
type helloWorldHandler struct { content string } func (handler *helloWorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, handler.content) } func main() { http.Handle("/", &helloWorldHandler{content: "Hello World!"}) http.ListenAndServe(":8000", nil) }
在這裏,咱們能發現相較於第一種方法,有些許的改動。函數
咱們定義了一個結構體,而後又給這個結構體編寫了一個方法。根據咱們以前對於接口的概念:要實現一個接口必需要實現這個接口的全部方法。微服務
那麼咱們是否是能夠推測:存在這麼一個接口A,裏面有一個名爲ServeHTTP的方法,而咱們所編寫的這個結構體,他已經實現了這個接口A了,他如今是屬於這個A類型的一個結構體了。
type A interface{ ServeHTTP() }
而且,在main函數中關於映射URI和方法的參數部分,須要調用實現了這個接口A的一個對象。
帶着這個問題,咱們能夠繼續往下。
在第一部分,咱們提到了兩種註冊方式,一種是傳入一個函數,一種是傳入一個結構體指針。
http.HandleFunc("/", helloWorldHandler) http.Handle("/", &helloWorldHandler{content: "Hello World!"})
咱們來看看http包內的源碼:
package http func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
先看一下這裏的代碼,他們被稱爲註冊函數。
首先研究一下HandleFunc
這個函數。在main
函數中,調用了這個具備func(pattern string, handler func(ResponseWriter, *Request))
簽名的函數,這裏的pattern
是string
類型的,指的是匹配的URI,這個很容易理解。第二個參數是一個具備func(ResponseWriter, *Request)
簽名的函數。
而後咱們繼續看,在這個函數中,調用了這個方法:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
咱們能夠看到,最終是調用了DefaultServeMux
對象的Handle方法。
好,先到這裏,咱們再看一看剛剛提到的簽名爲func (pattern string, handler Handler)
另一個函數。在這個函數裏面,一樣是調用了DefaultServeMux
對象的Handle方法。
也就是說,不管咱們使用哪一種註冊函數,最終調用的都是這個函數:
func (mux *ServeMux) Handle(pattern string, handler Handler)
這裏涉及到了兩種對象,第一是ServeMux
對象,第二是Handler
對象。
ServeMux
對象咱們一會再聊,先聊聊Handler
對象。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
在Golang中,Handler是一種接口類型,只要實現了ServeHTTP
這個方法,那個就能夠稱這個結構體是Handler
類型的。
注意到,在前面有一行代碼是這樣的:
mux.Handle(pattern, HandlerFunc(handler))
有人可能會想,HandlerFunc func(ResponseWriter, *Request)
這個函數,是輸入一個函數,返回一個Handler
類型的對象,其實這是不對的。咱們來看看這個函數的源碼:
type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
咱們能夠發現,這個函數,他是一個結構體類型,並且這個結構體也是實現了ServeHTTP
方法的,也就是說,這個結構體也是一個Handler
類型。因此,這個方法其實並非輸入一組參數,返回一個Handler
類型,而是他自己就是一個Handler
類型,能夠直接調用ServeHTTP
方法。
這裏比較繞,可是相信當你理解了以後,會感受妙啊。
說完了Handler
,咱們再來聊聊ServeMux
。先來看看他的結構:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames } type muxEntry struct { h Handler pattern string }
咱們先關注一下這個結構裏面的m
字段。這個字段是一個map
類型,key是URI
,value是muxEntry
類型。而這個muxEntry
類型,裏面包含了一個Handler
和URI
。也就是說,經過這個m
字段,咱們能夠用URI
找到對應的Handler
對象。
繼續說回上面提到的func (mux *ServeMux) Handle(pattern string, handler Handler)
方法。咱們已經知道了調用這個方法的對象是ServeMux
,也知道了這個方法的參數中的Handler
是什麼,下面讓咱們來看看這個方法的詳細實現:
func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic("http: invalid pattern") } if handler == nil { panic("http: nil handler") } if _, exist := mux.m[pattern]; exist { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) } e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e if pattern[len(pattern)-1] == '/' { mux.es = appendSorted(mux.es, e) } if pattern[0] != '/' { mux.hosts = true } }
在這個方法中,咱們能夠看到,Handle方法會先判斷傳入的URI
和handler
是否合法,而後判斷這個URI
對應的處理器是否已經註冊,而後將這個URI
和handler
對應的map
寫入ServeMux
對象中。
注意,這裏還有一個步驟。若是這個URI
是以/
結尾的,將會被送入es數組
中,按長度排序。至於爲何會這麼作,咱們在後面的內容將會提到。
說完了這些,咱們應該能夠猜到這個ServeMux
對象的做用了。他能夠存儲咱們註冊的URI
和Handler
,以實現當有請求進來的時候,能夠委派給相對應的Handler
的功能。
考慮到這個功能,那麼咱們也能夠推斷出,這個ServeMux
也是一個Handler
,只不過他和其餘的Handler
不一樣。其餘的Handler
處理的是具體的請求,而這個ServeMux
處理的是請求的分配。
因此,ServeMux也實現了ServeHTTP方法,他也是一個Handler。而對於他是怎麼實現ServeHTTP方法的,咱們也在後面的內容提到。
如今,讓咱們來聊聊main函數中的第二行:
http.ListenAndServe(":8000", nil)
按照慣例,咱們來看一看這個方法的實現:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
這裏的Server,是一個複雜的結構體,裏面包含了設置服務器的不少參數,可是這裏咱們只聊Addr
和Handler
這兩個屬性。
Addr
很容易理解,就是這個服務器所監聽的地址。
Handler
是處理器,負責把請求分配給各個對應的handler
。在這裏留空,則使用Golang默認的處理器,也就是上文中咱們提到的實現了ServeHTTP
方法的ServeMux
。
知道了這些,咱們繼續往下看server.ListenAndServe()
的實現:
func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln) }
這裏比較重要的有兩行,第一是ln, err := net.Listen("tcp", addr)
,也就是說,開始監聽addr
這個地址的tcp鏈接。
而後,調用srv.Serve(ln)
,咱們來看看代碼(省略部分,只保留與本文有關的邏輯):
func (srv *Server) Serve(l net.Listener) error { ... for{ ... c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(connCtx) } }
簡單來說,在這個方法中,有一個死循環,他不斷接收新的鏈接,而後啓動一個協程,處理這個鏈接。咱們來看看c.serve(connCtx)
的具體實現:
func (c *conn) serve(ctx context.Context) { ... serverHandler{c.server}.ServeHTTP(w, w.req) ... }
省略其餘全部的細節,最關鍵的就是這一行代碼了,而後咱們再看看這個ServeHTTP
方法。注意,這裏的c.server
,仍是指的是最開始的那個Server結構體。堅持一下下,立刻就到最關鍵的地方啦:
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) }
這裏的ServeHTTP
方法邏輯很容易看出,若是最開始沒有定義一個全局處理的Handler
,則會使用Golang的默認handler
:DefaultServeMux
。
假設,咱們這裏使用的是DefaultServeMux
,執行ServeHTTP
方法。說到這裏你是否有印象,咱們在上一個章節裏提到的:
因此,ServeMux也實現了ServeHTTP方法,他也是一個Handler。而對於他是怎麼實現ServeHTTP方法的,咱們也在後面的內容提到。
就是這裏,對於ServeMux
來講,他就是一個處理請求分發的Handler
。
若是你學過Java,我跟你說他和ServletDispatcher
很類似,你應該能理解吧。
到了這裏,就是最後一步了,咱們來看看這裏處理請求分發的ServeHTTP
方法具體實現:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { ... h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
在省去其餘細節以後咱們應該能夠推斷,這個mux.Handler(r)
方法返回的h
,應該是所請求的URI
所對應的Handler
。而後,執行這個Handler
所對應的ServeHTTP
方法。咱們來看看mux.Handler(r)
這個方法:
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { ... host := stripHostPort(r.Host) path := cleanPath(r.URL.Path) ... return mux.handler(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 }
到了這裏,代碼就變得簡潔明瞭了。重點就是這個mux.match
方法,會根據地址,來返回對應的Handler。咱們來看看這個方法:
func (mux *ServeMux) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. mux.es contains all patterns // that end in / sorted from longest to shortest. for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" }
這段代碼也應該很容易理解。若是在ServeMux
中存儲了key
爲這個URI的路由規則的映射,則直接返回這個URI
對應的Handler
。
不然,就去匹配es數組
。還記得嗎,這個數組是以前註冊路由的時候提到的,若是URI
是以/
結尾的,就會把這個路由映射添加到es數組中
,並由長到短進行排序。
這樣的做用是,能夠優先匹配到最長的URI
,以達到近似匹配的時候可以匹配到最合適的路由的目的。
至此,返回對應的Handler
,而後執行,就成功的實現了處理相對應的請求了。
首先,謝謝你能看到這裏!
不知道你有沒有理解我所說的內容,但願這篇文章能夠給你一些幫助。
其實寫這篇文章的目的是這樣的,學完了Golang的基礎以後做者準備開始研究Golang Web。可是查找各類資料後發現,並無找到一條很合適的學習路線。而後原本做者打算去直接研究一個框架,如MeeGo,Gin等。可是又考慮到,框架只是用來解決問題的,學會了框架殊不知道基礎內容,有種知其然不知其因此然的感受。
因此,做者打算從Golang的net/http包的源碼開始,慢慢去了解怎麼用原生的Go語言去創建一個HTTP服務器,而後去了解一下怎麼進行緩存,作持久化等,這也是做者思考以後決定的一條學習路線。當可以把這些內容都研究明白以後,再去研究框架,去看這些框架是怎麼解決問題的,可能纔是比較合適的。
固然了,做者也是剛入門。因此,可能會有不少的疏漏。若是在閱讀的過程當中,有哪些解釋不到位,或者理解出現了誤差,也請你留言指正。
再次感謝~