前言
我們在直接使用golang系統package搭建的http服務時,總會使用類似如下的代碼:
func main() {
http.HandleFunc("/hello", HelloServer)
http.ListenAndServe(":12345", nil)
}
但你知道這些代碼背後是以什麼的邏輯運行的嗎?
http.HandleFunc聲明的HelloServer是怎麼被系統調用到的呢?
爲何http.ListenAndServe的handler參數設置爲nil呢?
今天我們就從http.ListenAndServe入口查看源碼背後的邏輯。
爲避免文章大片的代碼影響可讀性,以下分析到的源碼只會保留重要部分,其餘部分省略。
一、http.ListenAndServe
1.http.ListenAndServe
(1)ListenAndServe
ListenAndServe是整個服務運行的總入口,我們從此出發。
代碼如下:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
此func構造了Server,並調用了server的ListenAndServe繼續處理。
看下func上方的註釋及示例,註釋中這一句比較重要:
// Handler is typically nil, in which case the DefaultServeMux is used.
Handler通常是nil,此時,會默認使用DefaultServeMux。
這也是我們示例中爲何Handler爲nil的原因,至於DefaultServeMux很重要,不過我們放到第二部分再講。
(2)Server
Server的結構如下:
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke http.DefaultServeMux if nil
TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS
...
此處再次提到Handler爲nil時會使用DefaultServeMux,由此可見DefaultServeMux的重要性。至於,何時使用DefaultServeMux,看後面的源碼分析DefaultServeMux。
2.server.ListenAndServe
監聽tcp端口
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)})
}
此段代碼主要是tcp端口的監聽Listener的獲取及轉爲tcpKeepAliveListener後繼續執行。
3.srv.Serve
新建連接,併發處理每個連接。
...
for {
...
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
...
此處有個無限循環來處理各個連接,並最終新啓協程來處理每個連接,這也就是服務端可以同時處理多請求的緣由。
我們重點看:c := srv.newConn(rw)
// Create new connection from rwc.
func (srv *Server) newConn(rwc net.Conn) *conn {
c := &conn{
server: srv,
rwc: rwc,
}
if debugServerConnections {
c.rwc = newLoggingConn("server", c.rwc)
}
return c
}
通過此func,將server及連接net.Conn傳入*conn,意味者針對每一連接,srv都會新建一個conn來處理,隨後開啓新的goroutine併發處理。
4.srv.Serve
(1)Serve
處理具體的請求。
這部分最重要的就一句
func (c *conn) serve(ctx context.Context) {
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
就是這一句負責路由的最終分發處理。
(2)server.Handler
查詢對應pattern的HandleFunc
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
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)
}
此func中sh.srv即爲最開始提到的Server,sh.srv.Handler即爲http.ListenAndServe中傳入的handler。同時,此方法中還說明了之前反覆提到的問題,即
(1)當傳入的handler爲nil時,將默認使用DefaultServeMux,這就是問題的根本原因。
(2)當傳入的handler不爲nil時,將直接交由我們自定義的Handler直接處理。那我們如何自定義Handler呢?
mux := http.NewServeMux()
mux.HandleFunc("/hello", HelloServer)
http.ListenAndServe(":12345", mux)
(3)通過(2)中自定義的Handler,我們可以看出,自定義Handler顯然比系統自動構建的要麻煩,同時還增加了代碼及邏輯,調用更內部的ServeMux,明顯增加了學習成本。因此,系統建議我們設置爲nil。
因此,爲了瞭解更多的內容,我們還需要看DefaultServeMux的實現了。
二、http.HandleFunc與http.DefaultServeMux
1.HandleFunc
調用http.Func後發生了什麼?
(1)http.HandleFunc
交由DefaultServeMux處理。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
(2)DefaultServeMux.HandleFunc
mux進一步處理pattern及handler,此處也對我們的handler轉換爲HandleFunc,爲最後調用ServerHTTP做準備。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
(3)mux.Handle
存儲pattern及handler信息到mux中
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
...
}
由此可以看到我們自定義的handler轉爲HandlerFunc後,根據pattern的不同,然後存儲在mux的m中,m格式是map[string]muxEntry,裏面存儲着pattern對應的muxEntry,而muxEntry中存儲着pattern及Handler。
type muxEntry struct {
explicit bool
h Handler
pattern string
}
2.DefaultServeMux
(1)DefaultServeMux實際是類型是ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
ServeMux針對ServeHTTP的實現如下:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
...
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
(2)mux.Handler
此處針對r,mux的Handler處理如下,主要是針對重定向和一般的處理,此處我們只討論一般的處理方式。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
...
return mux.handler(r.Host, r.URL.Path)
}
(3)mux.handler
mux.handler則是獲取host和path組成的pattern對應的Handler。
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
}
(4)mux.match
mux.match則是根據host和path的來匹配pattern和Handler。
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 //此處即爲muxEntry中的Handler
pattern = v.pattern
}
}
return
}
裏面最終要的一句:
h = v.h
此處即爲muxEntry中的Handler,根據之前我們關於muxEntry相關的瞭解知道,muxEntry中的h即爲我們在http.HandleFunc中傳入的自定義HandleFunc。
如示例中:
http.HandleFunc("/hello", HelloServer)
當我們請求/hello時,即可在mux.m根據/hello此key即可獲取對應的HandleFunc HelloServer。
三、總結
最後,我們再看下前言中提到的兩個問題,其實是一個問題:
1.http.HandleFunc將pattern及我們自定義的handler存儲在DefaultServeMux的一個map中。
2.當http.ListenAndServe的handler爲nil時,系統會從DefaultServeMux存儲信息的map中匹配pattern獲取對應的handler,進而處連接請求。
下圖是一個簡易的思維導圖: