Gin 框架核心架構解析
Gin 是一個用Go (Golang) 編寫的HTTP Web 框架,架構非常簡潔,得益于Go的net/http庫,不用處理http協議底層細節,可以更好的聚焦于應用層邏輯
net/http庫概覽
Gin是基于net/http實現的, 在介紹Gin之前,不妨先了解下net/http
net/http提供了HTTP客戶端和服務端的功能,這里主要關心服務端的功能
服務端核心設計
得益于Go的并發模型,這里并不需要關心像傳統處理網絡請求的Reactor或Proactor的I/O復用機制,而是為每個請求都創建一個獨立的goroutine來處理(對這部分有興趣可以了解下Go的調度器和goroutine)。
net/http服務端功能的核心設計是http.Hander接口
type Handler interface{
ServeHTTP(ResponseWriter, *Request)
}
任何實現了這個接口的類型都可以作為一個HTTP請求處理器。ServeHTTP 方法接收一個 http.ResponseWriter 和一個 *http.Request,分別用于寫入響應和讀取請求信息。
這種設計將業務邏輯與底層的網絡細節徹底解耦,開發者只需關注如何處理請求和生成響應即可。
流程說明
net/http可以通過調用ListenAndServe監聽端口,啟動HTTP服務
使用net/http啟動HTTP服務的簡單示例:
func main() {
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. Handle and HandleFunc add handlers to DefaultServeMux
流程處理示意圖:
http.ListenAndServe
// ListenAndServe listens on the TCP network address addr and then calls
// [Serve] with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case [DefaultServeMux] is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
ListenAndServe的第二個參數接收的就是前面說的實現了http.Handler接口的類型,默認的DefaultServeMux也是實現了該接口的一個類型,Gin的Engine也是一個實現了http.Handler的類型
DefaultServeMux:
// Handler returns the handler to use for the given request,
// consulting r.Method, r.Host, and r.URL.Path. It always returns
// a non-nil handler. If the path is not in its canonical form, the
// handler will be an internally-generated handler that redirects
// to the canonical path. If the host contains a port, it is ignored
// when matching handlers.
//
// The path and host are used unchanged for CONNECT requests.
//
// Handler also returns the registered pattern that matches the
// request or, in the case of internally-generated redirects,
// the path that will match after following the redirect.
//
// If there is no registered handler that applies to the request,
// Handler returns a “page not found” handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if use121 {
return mux.mux121.findHandler(r)
}
h, p, _, _ := mux.findHandler(r)
return h, p
}
總結
簡單的回顧下,net/http通過ListenAndServe啟動HTTP服務,監聽指定的端口,當有請求到達時,啟動一個goroutine來處理該請求(net/http/server.go),在完成HTTP的解析后,會調用Handler.ServeHTTP方法進行處理,該方法可以通過實現Handler接口來自定義,并在調用ListenAndServe時進行設置。
Engine: Gin的核心
上面已經說了,Gin是通過實現http.Handler接口實現的,在Gin中實現這個接口的就是Engine,所以要了解Gin的結構從Engine入手是比較方便的。
ServeHttp
net/http在接收到一個請求后,會創建一個goroutine處理該請求,在讀取數據并進行解析后,會調用ServeHttp
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
ServeHttp處理流程
Engine的ServeHttp的實現為:
// ServeHTTP conforms to the http.Handler interface.
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都會為該請求分配一個gin.Context對象用來管理請求的上下文。為了避免頻繁的創建和銷毀gin.Context,使用了engine.pool來緩存gin.Context對象,每個請求到達時,先從pool中獲取一個gin.Context對象,并重置gin.Context的狀態,之后將gin.Context傳遞下去,請求處理完成后再將gin.Context返回對象池中。
在獲取到gin.Context之后,通過HandleHttpRequest處理請求,HandleHttpRequest先根據URL確定路由,并獲取綁定到路由路徑上的所有handle,也就是middleWare,這些middleWare組成一個處理鏈,之后依次執行鏈上的handle
sync.pool 解析
engine.pool的類型是sync.Pool,是一個線程安全的對象池,提供了Get()和Put()方法,可以在多個goroutine中同時使用。
其內部設計優先考慮本地goroutine緩存以減少鎖競爭(每個goroutine都有一個私有的本地緩存),當 Get() 時會首先從本地緩存獲取,本地沒有再從共享池中獲取,Put() 時也優先放回本地緩存。
為什么要重置gin.Context?
sync.Pool不適合存放有狀態且不能被重置的對象。gin.Context就是一個典型的例子,它會存儲請求相關的狀態,例如Request、ResponseWriter以及在中間件中傳遞的Keys。如果不重置,下一個請求可能會使用到上一個請求的殘留數據,導致邏輯錯誤。
Gin通過在ServeHTTP方法中調用c.reset()來解決這個問題。reset方法會將Context的狀態(如Request、ResponseWriter、Keys、index等)恢復到初始狀態,確保每個請求都能獲得一個干凈的上下文。
路由和中間件
Gin的核心是Engine和RouterGroup,實際上,Engine嵌入了RouterGroup,也是一個RouterGroup
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
type Engine struct {
RouterGroup
...
}
Gin中的路由通過前綴樹(Radix Tree)進行保存, Engine就是根RouterGroup
// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
Handlers 存儲了該層級注冊的中間件
basePath 用于管理路由組的前綴路徑,方便路由組織
Gin中的路由通過前綴樹(Radix Tree)進行保存,這種數據結構能夠高效地進行動態路由匹配。
當你在 GET, POST 等方法中注冊一個路由時,Gin 會執行以下步驟來生成完整的處理鏈:
- 獲取父級中間件:首先,它會從當前的
RouterGroup(或Engine)中獲取其Handlers切片。 - 拼接處理函數:然后,它會將本次注冊的路由處理函數(
handler...)追加到這個切片的末尾。 - 存儲到路由樹:最終,這個完整的處理鏈會作為一個整體,與請求方法和路由路徑一起,存儲到 Gin 的路由樹(一個前綴樹
Radix Tree)中。
路由機制的優勢
使用Radix Tree作為路由匹配的核心,帶來了以下好處:
- 高效匹配:能快速定位到匹配的路由,尤其是在路由數量龐大時。
- 支持動態路由:可以輕松處理
/users/:id這類帶有參數的路由。 - 支持通配符:可以處理
/static/*filepath這類通配符路由。
Context
gin.Context貫穿整個HTTP請求生命周期,上下文對象除了保存數據用于在不同的中間件之間傳遞數據外,還提供了許多方法方便解析數據和響應數據,以及提供Next()和Abort()來控制流程
傳遞數據
gin.Context通過一個map[string]any對象來保存數據,并提供了Set和Get方法存取其中的數據
type Context struct {
...
// Keys is a key/value pair exclusively for the context of the request.
// New objects are not accepted.
Keys map[string]any
...
}
封裝當前請求和響應
c.writermem.reset(w)
c.Request = req
上面ServeHTTP的實現中可以看到,會將net/http傳遞過來的http.ResponseWriter和*http.Request 保存到gin.Context中。
gin.Context提供和許多方法方便獲取請求數據和返回響應,而不用直接操作http.ResponseWriter和*http.Request。
在讀取數據時,如使用c.ShouldBindJSON(data)獲取數據時,其實現就需要用到*http.Request解析數據:
// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
Name() string
Bind(*http.Request, any) error
}
// --- binding/json.go
func (jsonBinding) Bind(req *http.Request, obj any) error {
if req == nil || req.Body == nil {
return errors.New("invalid request")
}
return decodeJSON(req.Body, obj)
}
要返回JSON格式的響應數據,則可以調用c.JSON()將對象格式化為JSON格式。
Gin使用Render來格式化數據,Render的接口定義為:
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
type Render interface {
// Render writes data with custom ContentType.
Render(http.ResponseWriter) error
// WriteContentType writes custom ContentType.
WriteContentType(w http.ResponseWriter)
}
其中的Render方法負責將數據格式化并寫到http.ResponseWriter中
JSON的Render:
// Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) error {
return WriteJSON(w, r.Data)
}
// WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj any) error {
writeContentType(w, jsonContentType)
jsonBytes, err := json.Marshal(obj)
if err != nil {
return err
}
_, err = w.Write(jsonBytes)
return err
}
流程控制
請求到達后,會根據url進行路由,將匹配到的路由節點中的handlers的函數處理鏈保存到Context中的handlers屬性中:
type Context struct {
...
handlers HandlersChain
index int8
...
}
// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc
Next()方法實際上只是沿著函數處理鏈往下走:
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
中間件處理流程示意圖:
Abort()將index設置為math.MaxInt8 >> 1來終止整個調用鏈:
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
// Let's say you have an authorization middleware that validates that the current request is authorized.
// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
// for this request are not called.
func (c *Context) Abort() {
c.index = abortIndex
總結
Gin框架通過以下核心機制實現了簡潔高效的Web服務:
- 基于
net/http:利用其Handler接口和并發模型,解耦了網絡細節和業務邏輯。 Engine和sync.Pool:通過Engine作為核心處理器,并使用對象池高效管理gin.Context對象。Radix Tree路由:實現了高性能、支持動態路由的請求匹配。Context對象:貫穿請求生命周期,封裝了請求和響應,提供了流程控制和數據傳遞能力。- 中間件機制:通過
HandlersChain和Next()、Abort()實現了靈活的請求處理管道。
這些設計共同構成了Gin簡潔而強大的架構,使其成為Go語言Web開發中的熱門選擇。
倉庫地址:lapluma
微信公眾號:午夜游魚

浙公網安備 33010602011771號