golang構建HTTP服務的實現步驟

   一個go最簡單的Http服務器程序

package main
 
import (
    "fmt"
    "net/http"
)
 
func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}
 
func main() {
    http.HandleFunc("/", IndexHandler)
    http.ListenAndServe("127.0.0.0:8000", nil)
}

HTTP

   除去細節,理解HTTP構建的網絡應用隻要關註兩個端–客戶端(client)和服務端(server),兩個端的交互來自client的request,以及server端的response。所謂的http服務器,主要在於如何接受client的request,並向client返回response。

   接收request的過程中,最重要的莫過於路由(router),即實現一個Multiplexer器。Go中既可以使用內置的mutilplexer–DefaultServeMux,也可以自定義。Multiplexer路由的目的就是為瞭找到處理器函數(hander),後者將對request進行處理,同時構建response。

  簡單總結就是這個流程:

  因此,理解go中的http服務,,最重要的就是要理解Multiplexer和hander,Golang中的Multiplexer基於ServerMux結構,同時也實現瞭Handler接口。

  ·hander函數:具有func(w http.ResponseWriter, r *http.Requests)簽名的函數

  ·handler處理器(函數):經過HanderFunc結構包裝的handler函數,它實現瞭ServeHTTP接口方法的函數。調用handler處理器的ServeHTTP方法時,即調用handler函數本身。

  ·handler對象:實現瞭Hander接口ServeHTTP方法的結構。

   Golang的http處理流程可以用下面一張圖表示,後面內容是針對圖進行說明:

Handler

   Golang沒有繼承,類多態的方法可以通過接口實現。所謂接口則是定義聲明瞭函數簽名,任何結構隻要實現瞭與接口函數簽名相同的方法,就等同於實現瞭接口。go的http服務都是基於handler進行處理的。

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

    任何結構體,隻要實現瞭ServeHTTP方法,這個結構就可以稱之為handler對象。ServeMux會使用handler並調用其ServeHTTP方法處理請求並返回響應。

ServeMux

   瞭解瞭Handler之後,再看ServeMux。ServeMux源碼很簡單:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool 
}
 
type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

   ServeMux結構中最重要的字段為m,這是一個map,key是一些url模式,value是一個muxEntry結構,後者裡定義存儲瞭具體的url模式和handler。 

   當然,所謂的ServeMux也實現瞭ServeHTTP接口,也算是一個handler,不過ServeMux的ServeHTTP方法不是用來處理request和respone,而是用來找到路由註冊的handler。

Server

   除瞭ServeMux和Handler,還有一個結構Server需要瞭解。從http.ListenAndServe的源碼可以看出,它創建瞭一個server對象,並調用server對象的ListenAndServe方法:

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

   查看server的結構如下:

type Server struct {
    Addr         string        
    Handler      Handler       
    ReadTimeout  time.Duration 
    WriteTimeout time.Duration 
    TLSConfig    *tls.Config   
 
    MaxHeaderBytes int
 
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
 
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
    disableKeepAlives int32     nextProtoOnce     sync.Once 
    nextProtoErr      error     
}

   server結構存儲瞭服務器處理請求常見的字段。其中Handler字段也保留Hander接口。如果Server接口沒有提供Handler結構對象,那麼會使用DefaultServeMux做Multiplexer。

創建HTTP服務

    創建一個http服務,大致需要經歷兩個過程,首先需要註冊路由,即提供url模式和handler函數的映射,其次就是實例化一個server對象,並開啟對客戶端的監聽。

http.HandleFunc("/", indexHandler)
http.ListenAndServe("127.0.0.1:8000", nil)
 
或者:
 
server := &Server{Addr: addr, Handler: handler}
 
server.ListenAndServe()

http註冊路由

    net/http包暴露的註冊路由的api很簡單,http.HandleFunc選取瞭DefaultServeMux作為multiplexer:

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

   實際上,DefaultServeMux是ServeMux的一個實例。當然http包也提供瞭NewServeMux方法創建一個ServeMux實例,默認則創建一個DefaultServeMux:

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux {
    return new(ServeMux)
}
 
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
 
var defaultServeMux ServeMux

   註意,go創建實例的過程中,也可以使用指針方式,即

   type Server struct{}
   server := Server{}

   和下面的一樣都可以創建Server的實例

 var DefaultServer Server
  var server = &DefalutServer

   因此DefaultServeMux的HandleFunc(pattern,handler)方法實際是定義在ServeMux下的:

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

   上述代碼中,HandlerFunc是一個函數類型。同時實現瞭Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數,其目的就是為瞭讓這個函數也實現ServeHTTP方法,即轉變成一個handler處理器(函數)。

type HandlerFunc func(ResponseWriter, *Request)
 
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

   一旦這樣做瞭,就意味著我們的indexHandler函數也有瞭ServeHTTP方法。
   此外,ServeMux的Handle方法,將會對pattern和handler函數做一個map映射:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
 
    if pattern == "" {
        panic("http: invalid pattern " + pattern)
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }
 
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
 
    if pattern[0] != '/' {
        mux.hosts = true
    }
 
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
 
        path := pattern
        if pattern[0] != '/' {
            path = pattern[strings.Index(pattern, "/"):]
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
    }
}

   由此可見,Handle函數的主要目的在於把handler和pattern模式綁定到map[string]muxEntry的map上,其中muxEntry保存瞭更多pattern和handler的信息。Server的m字段就是map[string]muxEntry這樣一個map。
   此時,pattern和handler的路由註冊完成。

開啟監聽

    註冊好路由之後,啟動web服務還需要開啟服務器監聽。http的ListenAndServer方法中可以看到創建瞭一個Server對象,並調用瞭Server對象的同名方法:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
 
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)})
}

   Server的ListenAndServer方法中,會初始化監聽地址Addr,同時調用Listen方法設置監聽。最後將監聽的TCP對象傳入Serve方法:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    ...
 
    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        ...
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

處理請求

   監聽開啟之後,一旦客戶端請求到底,go就開啟一個協程處理請求,主要邏輯都在server方法之中。

   serve方法比較長,其主要職能就是,創建一個上下文對象,然後調用Listener的Accept方法用來獲取連接數據並使用newConn方法創建連接對象。最後使用goroutein協程的方式處理連接請求。因此每一個連接都開啟瞭一個協程,請求的上下文都不同,同時又保證瞭go的高並發。serve也是一個長長的方法:

func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    defer func() {
        if err := recover(); err != nil {
            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)
        }
    }()
 
    ...
 
    for {
        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)
        }
        ...
        
        }
        
        ...
    
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
    }
}

   盡管serve很長,裡面的結構和邏輯還是很清晰的,使用defer定義瞭函數退出時,連接關閉相關的處理。然後就是讀取連接的網絡數據,並處理讀取完畢時候的狀態。接下來就是調用serverHandler{c.server}.ServeHTTP(w,w.req)方法處理請求瞭。最後就是請求處理完畢的邏輯。serverHandler是一個重要的結構,它僅有一個字段,即Server結構,同時它也實現瞭Hander接口方法ServeHTTP,同時它也實現瞭Handler接口方法ServeHTTP,並在該接口方法中做瞭一個重要的事情,初始化multiplexer路由多路復用器。如果server對象沒有制定Handler,則使用默認的DefaultServeMux作為路由Multiplexer。並調用初始化Handler的ServeHTTP方法。

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

   這裡DefaultServeMux的ServeHTTP方法其實也是定義在ServeMux結構中的,相關代碼如下:

func (mux *ServeMux) (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)
}
 
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {
        if p := cleanPath(r.URL.Path); p != r.URL.Path {
            _, pattern = mux.handler(r.Host, p)
            url := *r.URL
            url.Path = p
            return RedirectHandler(url.String(), StatusMovedPermanently), pattern
        }
    }
    return mux.handler(r.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
}
 
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
        pattern = v.pattern
        }
    }
    return
}

   mux的ServeHTTP方法通過調用其Handler方法尋找註冊到路由上的handler函數,並調用該函數的ServeHTTP方法。 

   mux的Handler方法對URL簡單的處理,然後調用handler方法,後者會創建一個鎖,同時調用match方法返回一個handler和pattern。
   在match方法中,mux的m字段是map[string]muxEntry,後者存儲瞭pattern和handler處理器函數,因此通過迭代m尋找出註冊路由的pattern模式與實際url匹配的handler函數並返回。
   返回的結構一直傳遞到mux的ServeHTTP方法,接下來調用handler函數的ServeHTTP方法,即IndexHandler函數,然後把reponse寫到http.RequestWirter對象返回給客戶端。

   上述函數運行結束即serverHandler{c.server}.ServeHTTP(w, w.req)運行結束。接下來就是對請求處理完畢之後上希望和連接斷開的相關邏輯。

   至此,Golang中一個完整的http服務介紹完畢,包括註冊路由,開啟監聽,處理連接,路由處理函數。

參考:

https://www.yuque.com/docs/share/24a1bd1c-e32d-4268-9115-bf03f53677d3

到此這篇關於golang構建HTTP服務的實現步驟的文章就介紹到這瞭,更多相關golang構建HTTP服務內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: