golang啟動http服務(wù)

golang的net/http包提供了啟動一個http服務(wù)簡單的方法,我們直接從調(diào)用流程追蹤代碼查看http包是怎樣提供http服務(wù)的,下面是一個簡單的deamo:

package main

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

func loginHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "login success")
}

func startServer(addr string) {
    http.HandleFunc("/login", loginHandler)
    log.Fatal(http.ListenAndServe(addr, nil))
}

func main() {
    startServer(":10080")
}

因為http是建立在tcp傳輸協(xié)議之上的,所以啟動http服務(wù)器本質(zhì)上是啟動一個tcp服務(wù)器,只是需要對http應(yīng)用層的協(xié)議進行解析;對http request的處理是根據(jù)request路徑來選擇處理方式的,上面代碼也就做了這兩件事:

  1. 注冊http request的處理函數(shù),這里是注冊了/login的處理函數(shù)loginHandler
  2. 啟動一個server接收http request,并根據(jù)request path找到注冊的處理函數(shù)

接下來跟代碼,目的是搞清楚http.HandleFunc()注冊的處理函數(shù)保存到哪里了?server接收到http request后又是怎樣找到對應(yīng)的處理函數(shù)的。

一. Handler/HandlerFunc

首先介紹下Handler類型,因為后面分析代碼需要它,看看它的聲明:

1. Handler

// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

只要實現(xiàn)了ServeHTTP(w http.ResponseWriter, r *http.Request)就是Handler類型,ServeHTTP用來對回復(fù)http請求。

3. HandlerFunc

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

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

HandlerFunc本身是函數(shù)類型,實現(xiàn)了ServeHTTP()方法,同時ServeHTTP()內(nèi)部調(diào)用的又是函數(shù)本身,HandlerFunc其實就是函數(shù)適配器。

二. DefaultServeMux

接下來看看是如何注冊處理函數(shù)的

http.HandleFunc("/login", loginHandler)

調(diào)用的是DefaultServeMux的HandleFunc()函數(shù)

// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

對handler進行判空再處理,注意這里把傳入的handler參數(shù)強制轉(zhuǎn)換成HandlerFunc類型,HandlerFunc又實現(xiàn)ServeHTTP()方法,所以本質(zhì)是把一個處理函數(shù)最終轉(zhuǎn)換成Handler類型。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

從函數(shù)原型可以知道DefaultServeMux是ServeMux類型的實例,我們看下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
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

知道了DefaultServeMux是ServeMux類型的實例,接下來看看DefaultServeMux是如何保存這些pattern和處理函數(shù)的。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
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
    }
}

原來是把pattern和Handler保存到DefaultServeMux的成員m中了,我們可以猜測匹配過程是通過pattern在m中找到對應(yīng)Handler去處理http request,那es成員有什么用呢?這就要說道ServeMux的pattern匹配規(guī)則了

  1. 如果pattern不以/結(jié)尾,如:/index,則只會匹配/index的請求
  2. 如果pattern以/結(jié)尾,如:/index/,則該pattern能匹配/index/,/index/info,但是優(yōu)先匹配長的路徑
  3. 所以pattern/能匹配所有不能被其他pattern匹配的路徑
  4. 如果pattern不以/結(jié)尾,如:/login,如果匹配不到/login,則會去匹配/login/

es保存的就是以/結(jié)尾的pattern,并且按字典從大到小排序,所以匹配時遍歷es就能優(yōu)先遍歷較長的路徑。
到這里注冊處理函數(shù)就結(jié)束了,主要是把處理函數(shù)強制轉(zhuǎn)成Handler類型,并把pattern和Handler保存到DefaultServeMux中,我們可以預(yù)料在某個時刻http server會通過DefaultServeMux找到對用的Handler

三. ListenAndServe

log.Fatal(http.ListenAndServe(addr, nil))

創(chuàng)建一個Server,監(jiān)聽addr中的地址和端口,注意這里的handler參數(shù)傳的是nil,看來是默認(rèn)會使用DefaultServeMux中的Handler,只是要搞清楚它們是怎么關(guān)聯(lián)上的。

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

接下來就是傳統(tǒng)的server建立了,開始監(jiān)聽,接收連接,處理請求

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

Serve函數(shù)比較長,主要是調(diào)用accept()創(chuàng)建連接,并啟動一個goroutine去處理新連接的事件

func (srv *Server) Serve(l net.Listener) error {
  ......
  ......
  ......
    for {
        rw, err := l.Accept()
        if err != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := err.(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", err, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return err
        }
  ....
  ....
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        go c.serve(connCtx)
    }
}

這里accpet處理挺有意思,accept失敗的時,如果不是致命錯誤,則依次休眠5ms,10ms,20ms....1s..1s..1s再進行accept()操作,想到的一個場景是文件句柄打開太多了導(dǎo)致accept()創(chuàng)建fd失敗,sleep一段時間后系統(tǒng)回收一定的文件句柄,再次調(diào)用accpet()就能成功。
接下來看看goroutine是怎么處理http請求的,還是去除不相關(guān)代碼

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    ....
    ....
    // 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 {
        w, err := c.readRequest(ctx)
        ....
        ....
        ....
        c.curReq.Store(w)
        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining. We could let them all process
        // in parallel even if their responses need to be serialized.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.
        serverHandler{c.server}.ServeHTTP(w, w.req)
        ....
        ....
        ....
    }
}

for{}循環(huán)中讀取http請求,再把解析出來的數(shù)據(jù)保存到response對象w中,response類型實現(xiàn)了ResponseWriter的方法,最終調(diào)用到serverHandler{c.server}.ServeHTTP(w, w.req),繼續(xù)看看serverHandler是怎么實現(xiàn)ServeHTTP方法的

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

還記得我們啟動server時傳的參數(shù)嗎?http.ListenAndServe(addr, nil) handler參數(shù)傳的就是nil,所以用DefaultServeMux作為handler,因為DefaultServeMux也實現(xiàn)了ServeHTTP方法,所以最終會調(diào)用到DefaultServeMux的ServeHTTP()方法中

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

最終會調(diào)用到DefaultServeMux的match()方法,并返回真正處理http請求的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, ""
}

可以看到首先從m中完全匹配pattern,如果匹配不到再到es中去匹配。

實現(xiàn)Handler結(jié)構(gòu)的類型有:


image.png
  1. HandlerFunc類型是個函數(shù)適配器,把處理函數(shù)轉(zhuǎn)成Handler類型
  2. ServeMux是DefaultServeMux的類型,用來通過path找到對應(yīng)的handler
  3. serverHandler類型只有一個Server成員,http服務(wù)器就是通過Server中handler成員去處理http請求的,如果handler成員為nil,則通過DefaultServeMux去處理

下面是http.HandleFunc(path, handler)的流程圖:


image.png

下面是http.ListenAndServe(addr, nil)的流程圖:


image.png

除了可以用默認(rèn)的DefaultServeMux作為http復(fù)用器之外,還能自定義實現(xiàn)一個,只要實現(xiàn)ServeHTTP()方法就行,因為本質(zhì)上是傳入一個Handler實例到http.ListenAndServe(string, Handler)

package main

import (
    "fmt"
    "net/http"
)

type MyServeMux struct{}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "loginHandler\n")
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "indexHandler\n")
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "defaultHandler\n")
}

func (m *MyServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/login":
        loginHandler(w, r)
    case "/index":
        indexHandler(w, r)
    default:
        defaultHandler(w, r)
    }
}

func startServer(addr string) {
    http.ListenAndServe(addr, &MyServeMux{})
}

func main() {
    startServer(":12345")
}

用curl測試結(jié)果如下:

$$ curl -X GET http://localhost:12345/login
loginHandler
$$:~$ curl -X GET http://localhost:12345/index
indexHandler
$$:~$ curl -X GET http://localhost:12345/
defaultHandler
最后編輯于
?著作權(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ù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

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

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