gin 源碼閱讀 -2- - http 請求是如何流入 gin 的?

本篇文章是 gin 源碼分析系列的第二篇,這篇文章我們主要弄清一個問題:一個請求通過 net/http 的 socket 接收到請求後, 是如何回到 gin 中處理邏輯的?

我們仍然以 net/http 的例子開始

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    if err := http.ListenAndServe(":8000", nil); err != nil {
        fmt.Println("start http server fail:", err)
    }
}

這個例子中 http.HandleFunc 通過看源碼,可以看到 URI "/" 被註冊到了 DefaultServeMux 上。

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

net/http ServeHTTP 的作用

net/http 裏面有個非常重要的 Handler interface。只有實現了這個方法才能請求的處理邏輯引入自己的處理流程中。

// https://github.com/golang/go/blob/master/src/net/http/server.go#L86-L88
type Handler interface {
   ServeHTTP(ResponseWriter, *Request)
}

默認的 DefaultServeMux 就實現了這個 ServeHTTP

這個 request 的流轉過程:

  1. socket.accept 接收到客戶端請求後,啓動 go c.serve(connCtx) [net/http server.go:L3013] 行,專門處理這次請求,server 繼續等待客戶端連接

  2. 獲取能處理這次請求的 handler -> serverHandler{c.server}.ServeHTTP(w, w.req) [net/http server.go:L1952]

  3. 跳轉到真正的 ServeHTTP 去匹配路由,獲取 handler

  4. 由於並沒有自定義路由,於是使用的是 net/http 默認路由 [net/http server.go:L2880-2887]

  5. 所以最終調用去 DefaultServeMux 匹配路由,輸出返回對應的結果

探究 gin ServeHTTP 的調用鏈路

下面是 gin 的官方 demo, 僅僅幾行代碼,就啓動了一個 echo server。

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message""pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

這段代碼的大概流程:

  1. r := gin.Default() 初始化了相關的參數

  2. 將路由 /ping 以及對應的 handler 註冊到路由樹中

  3. 使用 r.Run() 啓動 server

r.Run 的底層依然是 http.ListenAndServe

func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    trustedCIDRs, err := engine.prepareTrustedCIDRs()
    if err != nil {
        return err
    }
    engine.trustedCIDRs = trustedCIDRs
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

所以 gin 建立 socket 的過程,accept 客戶端請求的過程與 net/http 沒有差別,會同樣重複上面的過程。唯一有差別的位置就是在於獲取 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)
}

由於 sh.srv.Handler 是 interface 類型,但是其真正的類型是 gin.Engine,根據 interace 的動態轉發特性,最終會跳轉到 gin.Engine.ServeHTTP 函數中。

gin.ServeHTTP 的實現

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

至此,終於我們看到了 gin.ServeHTTP 的全貌了

  1. 從 sync.pool 裏面拿去一塊內存

  2. 對這塊內存做初始化工作,防止數據污染

  3. 處理請求 handleHTTPRequest

  4. 請求處理完成後,把這塊內存歸還到 sync.pool 中

現在看起來這個實現很簡單,其實不然,這纔是 gin 能夠處理數據的第一步,也僅僅將請求流轉入 gin 的處理流程而已。

這裏做個結論:通過上面的源碼流程分析,我們知道 net/http.ServeHTTP 這個函數相當重要性, 主要有這個函數的存在, 才能將請求流轉入目前 Go 的這些框架裏面,同學們有興趣的話,可以去看看 echo, iris, go-zero 等框架是如何實現 ServeHTTP 的。

有關 gin 如何匹配路由,獲取 handler 請關注後續文章。如果你覺得文章還不錯的,歡迎點贊 + 再看 + 轉發。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Fa7dkrAP3uApgiCQssyu5w