欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你吃透Golang中net/http標準庫服務端

 更新時間:2024年03月25日 11:07:02   作者:小許code  
這篇文章將從服務端(Server)作為切入點和大家分享一下Go語言net/http標準庫的實現邏輯,進而一步步分析http標準庫內部是如何運作的,感興趣的可以了解下

前言

今天分享下Go語言net/http標準庫的實現邏輯,文章將從客戶端(Client)--服務端(Server)兩個方向作為切入點,進而一步步分析http標準庫內部是如何運作的。

由于會涉及到不少的代碼流程的走讀,寫完后覺得放在一篇文章中會過于長,可能在閱讀感受上會不算很好,因此分為【Server--Client兩個篇文章】進行發(fā)布。

本文內容是【服務端Server部分】,文章代碼版本是Golang 1.19,文中會涉及較多的代碼,需要耐心閱讀,不過我會在盡量將注釋也邏輯闡述清楚。先看下所有內容的大綱:

Go 語言的 net/http 中同時封裝好了 HTTP 客戶端和服務端的實現,這里分別舉一個簡單的使用示例。

Server啟動示例

Server和Client端的代碼實現來自net/http標準庫的文檔,都是簡單的使用,而且用很少的代碼就可以啟動一個服務!

http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "xiaoxu code")
})
http.ListenAndServe(":8080", nil)

上面代碼中:

HandleFunc 方法注冊了一個請求路徑 /hello 的 handler 函數

ListenAndServe指定了8080端口進行監(jiān)聽和啟動一個HTTP服務端

Client發(fā)送請求示例

HTTP 包一樣可以發(fā)送請求,我們以Get方法來發(fā)起請求,這里同樣也舉一個簡單例子:

resp, err := http.Get("http://example.com/")
if err != nil {
    fmt.Println(err)
    return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))

是不是感覺使用起來還是很簡單的,短短幾行代碼就完成了http服務的啟動和發(fā)送http請求,其背后是如何進行封裝的,在接下的章節(jié)會講清楚!

服務端 Server

我們先預覽下圖過程,對整個服務端做的事情有個了解

從圖中大致可以看出主要有這些流程:

1. 注冊handler到map中,map的key是鍵值路由

2. handler注冊完之后就開啟循環(huán)監(jiān)聽,監(jiān)聽到一個連接就會異步創(chuàng)建一個 Goroutine

3. 在創(chuàng)建好的 Goroutine 內部會循環(huán)的等待接收請求數據

4. 接受到請求后,根據請求的地址去處理器路由表map中匹配對應的handler,然后執(zhí)行handler

Server結構體

type Server struct {
    Addr string
    Handler Handler 
    mu         sync.Mutex
    ReadTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    TLSConfig *tls.Config
    ConnState func(net.Conn, ConnState)
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    listeners  map[*net.Listener]struct{}
    ...
}

我們在下圖中解釋了部分字段代表的意思

ServeMux結構體

type ServeMux struct {
    mu sync.RWMutex   
    m map[string]muxEntry 
    es []muxEntry    
    hosts bool     
}

字段說明:

• sync.RWMutex:這是讀寫互斥鎖,允許goroutine 并發(fā)讀取路由表,在修改路由map時獨占

• map[string]muxEntry:map結構維護pattern (路由) 到 handler (處理函數) 的映射關系,精準匹配

• []muxEntry:存儲 "/" 結尾的路由,切片內按從最長到最短的順序排列,用作模糊匹配patter的muxEntry

• hosts:是否有任何模式包含主機名

Mux是【多路復用器】的意思,ServeMux就是服務端路由http請求的多路復用器。

作用: 管理和處理程序來處理傳入的HTTP請求

原理:內部通過一個 map類型 維護了從 pattern (路由) 到 handler (處理函數) 的映射關系,收到請求后根據路徑匹配找到對應的處理函數handler,處理函數進行邏輯處理。

路由注冊

通過對HandleFunc的調用追蹤,內部的調用核心實現如下:

了解完流程之后接下來繼續(xù)追函數看代碼

var DefaultServeMux = &defaultServeMux
// 默認的ServeMux
var defaultServeMux ServeMux

// HandleFunc注冊函數
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

DefaultServeMux是ServeMux的默認實例。

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

//HandlerFunc為函數類型
type HandlerFunc func(ResponseWriter, *Request)
//實現了Handler接口
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}


func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    ...
    // handler是真正處理請求的函數
    mux.Handle(pattern, HandlerFunc(handler))
}

HandlerFunc函數類型是一個適配器,是Handler接口的具體實現類型,因為它實現了ServeHTTP方法。

HandlerFunc(handler), 通過類型轉換的方式【handler -->HandlerFunc】將一個出入參形式為func(ResponseWriter, *Request)的函數轉換為HandlerFunc類型,而HandlerFunc實現了Handler接口,所以這個被轉換的函數handler可以被當做一個Handler對象進行賦值。

好處:HandlerFunc(handler)方式實現靈活的路由功能,方便的將普通函數轉換為Http處理程序,兼容注冊不同具體的業(yè)務邏輯的處理請求。

你看,mux.Handle的第二個參數Handler就是個接口,ServeMux.Handle就是路由模式和處理函數在map中進行關系映射。

ServeMux.Handle

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    // 檢查路由和處理函數
    ...
    //檢查pattern是否存在
    ...
    //如果 mux.m 為nil 進行make初始化 map
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    //注冊好路由都會存放到mux.m里面
    mux.m[pattern] = e
    //patterm以'/'結尾
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

Handle的實現主要是將傳進來的pattern和handler保存在muxEntry結構中,然后將pattern作為key,把muxEntry添加到DefaultServeMux的Map里。

如果路由表達式以 '/' 結尾,則將對應的muxEntry對象加入到[]muxEntry切片中,然后通過appendSorted對路由按從長到短進行排序。

注:

  • map[string]muxEntry 的map使用哈希表是用于路由精確匹配
  • []muxEntry用于部分匹配模式

到這里就完成了路由和handle的綁定注冊了,至于為什么分了兩個模式,在后面會說到,接下來就是啟動服務進行監(jiān)聽的過程。

監(jiān)聽和服務啟動

同樣的我用圖的方式監(jiān)聽和服務啟動的函數調用鏈路畫出來,讓大家先有個印象。

結合圖會對后續(xù)結合代碼邏輯更清晰,知道這塊代碼調用屬于哪個階段!

ListenAndServe啟動服務:

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    // 指定網絡地址并監(jiān)聽
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    // 接收處理請求
    return srv.Serve(ln)
}

net.Listen 實現了TCP協議上監(jiān)聽本地的端口8080 (ListenAndServe()中傳過來的),Server.Serve接受 net.Listener實例傳入,然后為每個連接創(chuàng)建一個新的服務goroutine

使用net.Listen函數實現網絡監(jiān)聽需要經過以下幾個步驟:

1. 調用net.Listen函數,指定網絡類型和監(jiān)聽地址。

2. 使用listener.Accept函數接受客戶端的連接請求。

3. 在一個獨立的goroutine中處理每個連接。

4. 在處理完連接后,調用conn.Close()來關閉連接

Server.Serve:

func (srv *Server) Serve(l net.Listener) error {
    origListener := l
    //內部實現Once是只執(zhí)行一次動作的對象
    l = &onceCloseListener{Listener: l}
    defer l.Close()
    ...
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        //rw為可理解為tcp連接
        rw, err := l.Accept()
        ...
        connCtx := ctx
        ...
        c := srv.newConn(rw)
        //
        go c.serve(connCtx)
    }
}

使用 for + listener.accept 處理客戶端請求

• 在for 循環(huán)調用 Listener.Accept 方法循環(huán)讀取新連接

• 讀取到客戶端請求后會創(chuàng)建一個 goroutine 異步執(zhí)行 conn.serve 方法負責處理

type onceCloseListener struct {
    net.Listener
    once     sync.Once
    closeErr error
}

onceCloseListener 是sync.Once的一次執(zhí)行對象,當且僅當第一次被調用時才執(zhí)行函數。

*conn.serve():

func (c *conn) serve(ctx context.Context) {
    ...
    // 初始化conn的一些參數
    c.remoteAddr = c.rwc.RemoteAddr().String()
    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
    for {
        // 讀取客戶端請求
        w, err := c.readRequest(ctx)
        ...
        // 調用ServeHTTP來處理請求
        serverHandler{c.server}.ServeHTTP(w, w.req)
    }
}

conn.serve是處理客戶端連接的核心方法,主要是通過for循環(huán)不斷循環(huán)讀取客戶端請求,然后根據請求調用相應的處理函數。

c.readRequest(ctx)方法是用來讀取客戶端的請求,然后返回一個response類型的w和一個錯誤err

最終是通過serverHandler{c.server}.ServeHTTP(w, w.req) 調用ServeHTTP處理連接客戶端發(fā)送的請求。

OK,經歷了前面監(jiān)聽的過程,現在客戶端請求已經拿到了,接下來就是到了核心的處理請求的邏輯了,打起十二分精神哦!

serverHandler.ServeHTTP:

上面說到的 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傳的是nil就執(zhí)行 DefaultServeMux.ServeHTTP() 方法
    handler.ServeHTTP(rw, req)
}

獲取Server的handler流程:

1. 先獲取 sh.srv.Handler 的值,判斷是否為nil

2. 如果為nil則取全局單例 DefaultServeMux這個handler

3. PTIONS Method 請求且 URI 是 *,就使用globalOptionsHandler

注:這個handler其實就是在ListenAndServe()中的第二個參數

ServeMux.ServeHTTP

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    ....
    h, _ := mux.Handler(r)
    // 執(zhí)行匹配到的路由的ServeHTTP方法
    h.ServeHTTP(w, r)
}

ServeMux.ServeHTTP()方法主要代碼可以分為兩步:

1. 通過 ServerMux.Handler() 方法獲取到匹配的處理函數 h

2. 調用 Handler.ServeHTTP() 執(zhí)行匹配到該路由的函數來處理請求 (h實現了ServeHTTP方法)

ServerMux.Handler():

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    ...
    //在mux.m和mux.es中
    //根據host/url.path尋找對應的handler
    return mux.handler(host, r.URL.Path)
}

在 ServeMux.Handler() 方法內部,會調用 ServerMux.handler(host, r.URL.Path) 方法來查找匹配的處理函數。

ServeMux.match

ServeMux.match()方法用于根據給定的具體路徑 path 找到最佳匹配的路由,并返回Handler和路徑。

值得一提的是,如果 mux.m 中不存在 path 完全匹配的路由時,會繼續(xù)遍歷 mux.es 字段中保存的模糊匹配路由。

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // 是否完全匹配
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }
    // mux.es是按pattern從長到短排列
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

最后調用 handler.ServeHTTP 方法進行請求的處理和響應,而這個被調用的函數就是我們之前在路由注冊時對應的函數。

type HandlerFunc func(ResponseWriter, *Request)

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

到這里整個服務的流程就到這里了,現在有對這塊有印象了嗎?

以上就是一文帶你吃透Golang中net/http標準庫服務端的詳細內容,更多關于Go net/http標準庫的資料請關注腳本之家其它相關文章!

您可能感興趣的文章:

相關文章

  • Golang小數操作指南之判斷小數點位數與四舍五入

    Golang小數操作指南之判斷小數點位數與四舍五入

    這篇文章主要給大家介紹了關于Golang小數操作指南之判斷小數點位數與四舍五入的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2022-03-03
  • sublime安裝支持go和html的插件

    sublime安裝支持go和html的插件

    這篇文章主要介紹了sublime安裝支持go和html的插件,需要的朋友可以參考下
    2015-01-01
  • 深入刨析Golang-map底層原理

    深入刨析Golang-map底層原理

    這篇文章主要介紹了深入刨析Golang-map底層原理,Go 語言的 map 的使用非常簡易, 但其內部實現相對比較復雜,文中有相關的代碼示例,,需要的朋友可以參考下
    2023-05-05
  • Go語言如何高效的進行字符串拼接(6種方式對比分析)

    Go語言如何高效的進行字符串拼接(6種方式對比分析)

    本文主要介紹了Go語言如何高效的進行字符串拼接(6種方式對比分析),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • Go語言設計模式之結構型模式

    Go語言設計模式之結構型模式

    本文主要聚焦在結構型模式(Structural Pattern)上,其主要思想是將多個對象組裝成較大的結構,并同時保持結構的靈活和高效,從程序的結構上解決模塊之間的耦合問題
    2021-06-06
  • Golang?Gin?中間件?Next()方法示例詳解

    Golang?Gin?中間件?Next()方法示例詳解

    這篇文章主要介紹了Golang?Gin?中間件?Next()方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Go語言基于viper實現apollo多實例快速

    Go語言基于viper實現apollo多實例快速

    viper是適用于go應用程序的配置解決方案,這款配置管理神器,支持多種類型、開箱即用、極易上手。本文主要介紹了如何基于viper實現apollo多實例快速接入,感興趣的可以了解一下
    2023-01-01
  • Golang極簡入門教程(二):方法和接口

    Golang極簡入門教程(二):方法和接口

    這篇文章主要介紹了Golang極簡入門教程(二):方法和接口,本文同時講解了錯誤、匿名域等內容,需要的朋友可以參考下
    2014-10-10
  • Go項目開發(fā)中如何讀取應用配置詳解

    Go項目開發(fā)中如何讀取應用配置詳解

    本文主要介紹了Go項目開發(fā)中如何讀取應用配置詳解,Go生態(tài)中有很多包可以加載并解析配置,最受歡迎的是Viper包,下面就來詳細的介紹一下
    2024-05-05
  • 深入理解golang的異常處理機制

    深入理解golang的異常處理機制

    Go語言追求簡潔優(yōu)雅,所以,Go語言不支持傳統(tǒng)的 try…catch…finally 這種異常,下面這篇文章主要給大家介紹了關于golang的異常處理機制,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-07-07

最新評論