從http.ListenAndServe開始看golang源碼

前言

我們在直接使用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中存儲着patternHandler

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,進而處連接請求。

下圖是一個簡易的思維導圖:
思維導圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章