Go http server (I) 源碼閱讀

這個系列會寫三到四篇文章,第一篇是 go sdk 里 net/http/server.go 的閱讀筆記,之后會寫一下如何利用 server.go 的接口自定義一個簡易通用的 HTTP server 框架。

example

先從一個簡單的例子開始吧:

package main

import (
    "net/http"
    "fmt"
    "log"
)

//開啟web服務(wù)
func test() {
    http.HandleFunc("/", sayHello)
    err := http.ListenAndServe(":9090", nil) // 注意這里第二個參數(shù)為 nil
    if err != nil {
        log.Fatal("ListenAndServer:", err)
    }
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello Guest!")
}

func main() {
    test()
}

運行代碼,此時瀏覽器訪問localhost:9090就會看到輸出 “Hello Guest!”,其實訪問localhost:9090/+任意字符串,都能得到結(jié)果。這段代碼先用http.HandleFunc注冊了一個處理函數(shù),然后調(diào)用http.ListenAndServe監(jiān)聽端口,當(dāng)有請求到來時,會根據(jù)訪問路徑找到并執(zhí)行對應(yīng)的處理函數(shù)。

我們通常還能看到另一種寫法:

package main

import (
    "net/http"
    "fmt"
    "log"
)

//開啟web服務(wù)
func test() {
  http.Handle("/", &handler{})
    err := http.ListenAndServe(":9090", nil) //
    if err != nil {
        log.Fatal("ListenAndServer:", err)
    }
}

func sayHello(w http.ResponseWriter, r *http.Request) {...}

func main() {
    test()
}

type handler struct{}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    sayHello(w, r)
}

這段代碼效果一樣。區(qū)別就是http.HandleFunchttp.Handle需要的第二個參數(shù),前者要一個func (w http.ResponseWriter, r *http.Request)函數(shù),后者要一個實現(xiàn)了該函數(shù)的結(jié)構(gòu)體。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

可以看到,兩個函數(shù)都會調(diào)用mux.handle

func (mux *ServeMux) Handle(pattern string, handler Handler)

第二個參數(shù)是Handler,是一個接口:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

現(xiàn)在回到上面的HandleFunc,注意這個:HandlerFunc(handler),這里很容易讓人誤以為HandlerFunc是一個函數(shù)并且包裝了傳入的handler,再返回一個Handler類型。而實際上這里是類型轉(zhuǎn)換,來看HandlerFunc的定義:

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

雖然HandlerFunc的類型是一個函數(shù),但它是一種類型,因為是以type來定義而不是func,并且實現(xiàn)了ServeHTTP(w ResponseWriter, r *Request),在這個函數(shù)里,它又調(diào)用了自身。這個細節(jié)是十分重要的,因為這一步關(guān)乎到當(dāng)路由規(guī)則匹配時,相應(yīng)的響應(yīng)方法是否會被調(diào)用的問題!這里的類型轉(zhuǎn)換用法使一個函數(shù)自身實現(xiàn)了一個接口,就不用每次都要先寫一個本身無用結(jié)構(gòu)體,再用結(jié)構(gòu)體實現(xiàn)接口。請仔細體會這種技巧!

。。。有點扯偏了,這里記住 Handler 這個接口是 go 語言 HTTP 服務(wù)最最最重要的接口,官方庫和第三方庫都按照這個接口來擴展。

Server

來看一下 Server 這個結(jié)構(gòu)體吧, 這里我只列出了幾個核心的域:

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 ServeTLS and ListenAndServeTLS

    listeners  map[net.Listener]struct{}
    onShutdown []func()
}

handler

這里主要關(guān)注 Handler,這個 Handler 就是剛剛的那個接口,可以在創(chuàng)建 Server 時傳入,也可以在調(diào)用 Server.ListenAndServe 時傳入:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

這個 handler 是在建立連接后收到客戶端請求時用到:

func (c *conn) serve(ctx context.Context) { // conn 指當(dāng)前連接
    ...
    for {
        w, err := c.readRequest(ctx)
        ...
        serverHandler{c.server}.ServeHTTP(w, w.req)
        ...
    }
}

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)
}

從 serverHandler 的 ServeHTTP 函數(shù)可以看到,當(dāng) server.handler==nil 時,使用內(nèi)部全局變量,也就是前面提到過的 DefaultServeMux。也就是說,我們在收到請求時通過這個 handler 來執(zhí)行自己的邏輯代碼,所以這個 handler 必須包含路由功能,并且能夠執(zhí)行路由對應(yīng)的處理函數(shù)。同時我們用的第三方 HTTP server 框架(echo、beego…)也是通過自定義 handler 來實現(xiàn)功能擴展。這也是 Handler 這個接口是最最重要的接口的原因。

關(guān)于 DefaultServeMux 和 自定義的 handler,會在之后詳細討論。接下來回到 Server 本身。

Server.Serve

在主函數(shù)中可以調(diào)用 http.ListenAndServe 或者 http.Serve 來開始 HTTP 服務(wù), 原理都一樣:

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)})
}

仔細看下 srv.Serve 的實現(xiàn):

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    
    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2_Serve(); err != nil {// 如果設(shè)置了 http2,就使用 http2 服務(wù),
        return err
    }

    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept() // 這里會等待新的連接的建立,會阻塞在這里。
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

這里要詳細解釋一下的就是 Accept 返回的 error 了。有以下幾種可能:

  • Accept 的時候 Server 由于某種原因停止了
  • 收到系統(tǒng)信號產(chǎn)生中斷,當(dāng)然如果 返回的是 EINTR 表示可以重新調(diào)用
  • 之前斷掉的連接在短時間被重用了,此時該連接處于 TIME_WAIT 狀態(tài),新連接暫時不可用??蓞⒖?a target="_blank" rel="nofollow">這里

對于暫時性的錯誤,可以稍等一會兒,所以會出現(xiàn) sleep。如果成功拿到 conn,先標(biāo)記連接狀態(tài),然后創(chuàng)建新 goroutine 開始對連接服務(wù)。

conn.serve

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            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)
        }
    }()

    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        // 處理 https
    }

    // HTTP/1.x from here on.

    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()

    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for { // 同一個連接有多個請求,循環(huán)處理
        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)
        }
        if err != nil {
            // handle error
            return
        }

        // Expect 100 Continue support
        req := w.req
        if req.expectsContinue() {
            if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
                // Wrap the Body reader with one that replies on the connection
                req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
            }
        } else if req.Header.get("Expect") != "" {
            w.sendExpectationFailed()
            return
        }

        c.curReq.Store(w)

        if requestBodyRemains(req.Body) { // 支持管線化,處理當(dāng)前請求時可能還在接收請求
            registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
        } else {
            if w.conn.bufr.Buffered() > 0 {
                w.conn.r.closeNotifyFromPipelinedRequest()
            }
            w.conn.r.startBackgroundRead()
        }

        serverHandler{c.server}.ServeHTTP(w, w.req) // 這里就是之前提到的,自定義處理的入口
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest() // 把數(shù)據(jù) flush 到網(wǎng)絡(luò)層,此次請求在應(yīng)用層結(jié)束
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait() // 發(fā)送 TCP FIN ,關(guān)閉連接
            }
            return
        }
        
        ...
        
        if d := c.server.idleTimeout(); d != 0 { // 設(shè)置空閑超時,超時后關(guān)閉連接
            c.rwc.SetReadDeadline(time.Now().Add(d))
            if _, err := c.bufr.Peek(4); err != nil {
                return
            }
        }
        c.rwc.SetReadDeadline(time.Time{})
    }
}

這里代碼比較復(fù)雜,包含了比較完整的 HTTP、HTTPs、HTTP2 協(xié)議的實現(xiàn),建議了解了協(xié)議的內(nèi)容再來看具體實現(xiàn)。代碼協(xié)議的細節(jié)部分代碼就不詳細談了,我們需要理解的是 創(chuàng)建 listener,從 Accept 拿到連接,等待并讀取到 request,用 handler 處理 request 并把結(jié)果或錯誤信息寫到 response 的過程。

需要注意的是,我們所討論的是 go 語言官方庫的 HTTP 的實現(xiàn),這里的發(fā)送和接收數(shù)據(jù)都是指的發(fā)給下層傳輸層和從傳輸層接收,也就是調(diào)用 socket 接口,一定要分清楚各個層次。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容