首頁 後端開發 Golang 深入探討 Gin:Golang 的領先框架

深入探討 Gin:Golang 的領先框架

Jan 01, 2025 am 05:16 AM

A Deep Dive into Gin: Golang

介紹

A Deep Dive into Gin: Golang

Gin 是一個用 Go(Golang)寫的 HTTP Web 框架。它具有類似 Martini 的 API,但效能比 Martini 快 40 倍。如果您需要精彩的表演,那就給自己點杜松子酒吧。

Gin 官網介紹自己是一個具有「高效能」和「良好生產力」的 Web 框架。它還提到了另外兩個庫。第一個是Martini,它也是一個Web框架,並且有一個酒的名字。 Gin 表示它使用其 API,但速度快了 40 倍。使用httprouter是它比Martini快40倍的重要原因。
官網的「Features」中列出了八個關鍵功能,稍後我們將逐步看到這些功能的實現。

  • 中介軟體支援
  • 無崩潰
  • JSON 驗證
  • 路線分組
  • 錯誤管理
  • 渲染內建/可擴充

從一個小例子開始

讓我們來看看官方文件中給出的最小範例。

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
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

執行這個例子,然後用瀏覽器存取http://localhost:8080/ping,就會得到「乒乓」聲。
這個例子非常簡單。它可以分為三個步驟:

  1. 使用 gin.Default() 建立具有預設配置的 Engine 物件。
  2. 在Engine的GET方法中註冊「/ping」位址的回呼函數。此函數將傳回一個“pong”。
  3. 啟動Engine,開始監聽連接埠並提供服務。

HTTP方法

從上面小例子中的GET方法我們可以看出,在Gin中,HTTP方法的處理方法需要使用對應的同名函數來註冊。
HTTP 方法有九種,最常用的四種是 GET、POST、PUT 和 DELETE,分別對應查詢、插入、更新和刪除四種功能。需要注意的是,Gin還提供了Any接口,可以直接將所有HTTP方法處理方法綁定到一個位址。
傳回的結果一般包含兩部分或三部分。代碼和訊息始終存在,資料通常用於表示附加資料。如果沒有額外的資料要返回,則可以省略。在範例中,200 是 code 欄位的值,「pong」是 message 欄位的值。

創建引擎變數

在上面的範例中,gin.Default() 用來建立引擎。然而,這個函數是 New 的包裝。其實Engine就是透過New介面創建的。

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
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

暫時簡單看一下建立過程,不要注意Engine結構體中各個成員變數的意義。可以看到,New除了建立並初始化一個Engine類型的引擎變數外,還將engine.pool.New設定為一個呼叫engine.allocateContext()的匿名函數。這個函數的作用後面會講。

註冊路由回呼函數

Engine 中有一個嵌入的 struct RouterGroup。 Engine的HTTP方法相關介面皆繼承自RouterGroup。官網提到的功能點中的「路由分組」是透過RouterGroup結構體實現的。

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            //... Initialize the fields of RouterGroup
        },
        //... Initialize the remaining fields
    }
    engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup
    engine.pool.New = func() any {
        return engine.allocateContext()
    }
    return engine
}
登入後複製
登入後複製
登入後複製
登入後複製

每個 RouterGroup 都與一個基本路徑 basePath 相關聯。 Engine 中嵌入的 RouterGroup 的基本路徑是「/」。
還有一組處理函數Handlers。所有與該群組關聯的路徑下的請求都會額外執行該群組的處理函數,主要用於中間件呼叫。 Engine建立時Handlers為nil,可以透過Use方法匯入一組函數。我們稍後會看到這個用法。

type RouterGroup struct {
    Handlers    HandlersChain // Processing functions of the group itself
    basePath    string        // Associated base path
    engine      *Engine       // Save the associated engine object
    root        bool          // root flag, only the one created by default in Engine is true
}
登入後複製
登入後複製
登入後複製

RouterGroup的handle方法是註冊所有HTTP方法回呼函數的最終入口。初始範例中呼叫的 GET 方法和其他與 HTTP 方法相關的方法只是對 handle 方法的包裝。
handle方法會根據RouterGroup的basePath和相對路徑參數計算出絕對路徑,同時呼叫combineHandlers方法得到最終的handlers陣列。這些結果會作為參數傳遞給Engine的addRoute方法來註冊處理函數。

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}
登入後複製
登入後複製
登入後複製

combineHandlers方法的作用是建立一個切片mergedHandlers,然後將RouterGroup本身的Handler複製到其中,然後將參數的handler複製到其中,最後傳回mergedHandlers。也就是說,當使用handle註冊任何方法時,實際的結果都包含了RouterGroup本身的Handler。

使用Radix Tree加速路由檢索

在官網提到的「快速」特徵點中,提到網路請求的路由是基於基數樹(Radix Tree)實現的。這部分並不是Gin實現的,而是由一開始介紹Gin時提到的httprouter實現的。 Gin使用httprouter來實現這部分功能。基數樹的實現這裡暫時不講。我們現在只關注它的用法。也許稍後我們會單獨寫一篇文章來介紹基數樹的實作。
在引擎中,有一個 trees 變量,它是 methodTree 結構的切片。正是這個變數保存了對所有基數樹的引用。

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
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

引擎為每個 HTTP 方法維護一個基數樹。這棵樹的根節點和方法的名稱一起保存在一個methodTree變數中,所有methodTree變數都在樹中。

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            //... Initialize the fields of RouterGroup
        },
        //... Initialize the remaining fields
    }
    engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup
    engine.pool.New = func() any {
        return engine.allocateContext()
    }
    return engine
}
登入後複製
登入後複製
登入後複製
登入後複製

可以看到,在Engine的addRoute方法中,首先會使用trees的get方法來取得此方法對應的radix樹的根節點。如果沒有取得到基數樹的根節點,則表示先前沒有為該方法註冊過任何方法,將會建立一個樹節點作為樹的根節點,並加入到樹中。
取得根節點後,使用根節點的addRoute方法註冊一組針對路徑path的處理函數handler。這一步驟是為路徑和處理程序建立一個節點並將其儲存在基數樹中。如果你嘗試註冊一個已經註冊的地址,addRoute會直接拋出一個panic錯誤。
在處理HTTP請求時,需要透過路徑找到對應節點的值。根節點有一個getValue方法負責處理查詢作業。我們在談論 Gin 處理 HTTP 請求時會提到這一點。

導入中間件處理函數

RouterGroup的Use方法可以匯入一組中間件處理函數。官網提到的功能點中的「中間件支援」是透過Use方法實現的。
在最初的範例中,在建立Engine結構體變數時,沒有使用New,而是使用了Default。讓我們看看 Default 額外做了什麼。

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
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

可以看出,這是一個非常簡單的函數。除了呼叫New建立Engine物件外,只呼叫Use導入Logger和Recovery兩個中間件函數的回傳值。 Logger的傳回值是用來記錄日誌的函數,Recovery的傳回值是用來處理panic的函數。我們暫時跳過這個,稍後再看這兩個函數。
雖然Engine內嵌了RouterGroup,也實作了Use方法,但只是呼叫了RouterGroup的Use方法以及一些輔助操作。

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            //... Initialize the fields of RouterGroup
        },
        //... Initialize the remaining fields
    }
    engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup
    engine.pool.New = func() any {
        return engine.allocateContext()
    }
    return engine
}
登入後複製
登入後複製
登入後複製
登入後複製

可見RouterGroup的使用方法也非常簡單。它只是透過append將參數的中間件處理功能添加到自己的Handler中。

開始跑步

在這個小例子中,最後一步就是不帶參數呼叫 Engine 的 Run 方法。呼叫後,整個框架開始運行,用瀏覽器存取註冊地址即可正確觸發回調。

type RouterGroup struct {
    Handlers    HandlersChain // Processing functions of the group itself
    basePath    string        // Associated base path
    engine      *Engine       // Save the associated engine object
    root        bool          // root flag, only the one created by default in Engine is true
}
登入後複製
登入後複製
登入後複製

Run方法只做兩件事:解析位址和啟動服務。這裡地址其實只要傳一個字串就可以了,但為了達到能傳能不能傳的效果,使用了一個可變參數。 resolveAddress方法處理addr不同情況的結果。
啟動服務使用標準庫的net/http套件中的ListenAndServe方法。此方法接受一個監聽位址和一個Handler介面的變數。 Handler介面的定義非常簡單,只有一個ServeHTTP方法。

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}
登入後複製
登入後複製
登入後複製

因為Engine實作了ServeHTTP,所以Engine本身將會被傳遞到這裡的ListenAndServe方法。當監聽埠有新的連接時,ListenAndServe會負責接受並建立連接,當連接上有資料時,會呼叫handler的ServeHTTP方法進行處理。

處理訊息

Engine的ServeHTTP是處理訊息的回呼函數。讓我們來看看它的內容。

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    assert1(finalSize < int(abortIndex), "too many handlers")
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}
登入後複製

回呼函數有兩個參數。第一個是w,用於接收請求回覆。將回覆資料寫入w。另一個是req,保存本次請求的資料。後續處理所需的所有資料都可以從req讀取
ServeHTTP 方法做了四件事。首先從pool池取得一個Context,然後將Context與回呼函數的參數綁定,然後以Context為參數呼叫handleHTTPRequest方法來處理這次網路請求,最後將Context放回池中。
我們首先只看handleHTTPRequest方法的核心部分。

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
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

handleHTTPRequest方法主要做了兩件事。首先根據請求的位址從基數樹中取得先前註冊的方法。這裡,handlers會被指派到Context中進行本次處理,然後呼叫Context的Next函數來執行handlers中的方法。最後將本次請求的回傳資料寫入Context的responseWriter類型物件中。

情境

處理 HTTP 請求時,所有與上下文相關的資料都在 Context 變數中。作者也在Context結構體的註釋中寫到“Context is the most important part of gin”,可見其重要性。
上面講Engine的ServeHTTP方法時可以看出,Context並不是直接建立的,而是透過Engine的pool變數的Get方法取得的。取出後,使用前重置其狀態,使用後放回池中。
Engine 的池變數的類型為sync.Pool。目前只知道它是Go官方提供的支援並發使用的物件池。您可以透過 Get 方法從池中取得對象,也可以使用 Put 方法將物件放入池中。當池為空並且使用Get方法時,它會透過自己的New方法建立物件並傳回。
這個New方法是在Engine的New方法中定義的。我們再看一下Engine的New方法。

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            //... Initialize the fields of RouterGroup
        },
        //... Initialize the remaining fields
    }
    engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup
    engine.pool.New = func() any {
        return engine.allocateContext()
    }
    return engine
}
登入後複製
登入後複製
登入後複製
登入後複製

從程式碼可以看出Context的建立方法是Engine的allocateContext方法。 allocateContext 方法並沒有什麼神秘之處。它只是對切片長度進行兩步預分配,然後創建物件並返回它。

type RouterGroup struct {
    Handlers    HandlersChain // Processing functions of the group itself
    basePath    string        // Associated base path
    engine      *Engine       // Save the associated engine object
    root        bool          // root flag, only the one created by default in Engine is true
}
登入後複製
登入後複製
登入後複製

上面提到的 Context 的 Next 方法將執行處理程序中的所有方法。讓我們來看看它的實現。

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}
登入後複製
登入後複製
登入後複製

雖然handlers是一個切片,但是Next方法並不是簡單地實現為handlers的遍歷,而是引入了一個處理進度記錄索引,該索引初始化為0,在方法開始時遞增,在方法結束後再次遞增執行完成。

Next的設計和它的用法有很大關係,主要是為了配合一些中間件功能。例如,當某個handler執行過程中觸發panic時,可以使用中間件中的recover捕獲錯誤,然後再次呼叫Next繼續執行後續的handler,而不會因為該問題影響整個handlers數組一名處理程序。

應對恐慌

在Gin中,如果某個請求的處理函數觸發了panic,整個框架並不會直接崩潰。相反,將拋出錯誤訊息,並將繼續提供服務。這有點類似Lua框架通常使用xpcall來執行訊息處理函數。這個操作就是官方文件中提到的「Crash-free」特性點。
上述提到,使用 gin.Default 建立 Engine 時,會執行 Engine 的 Use 方法來匯入兩個函數。其中之一是 Recovery 函數的傳回值,它是其他函數的包裝。最終呼叫的函數是CustomRecoveryWithWriter。我們來看看這個函數的實作。

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
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

這裡我們不關注錯誤處理的細節,而只看看它做了什麼。該函數傳回一個匿名函數。在這個匿名函數中,使用defer註冊了另一個匿名函數。在這個內部匿名函式中,使用recover來捕捉panic,然後進行錯誤處理。處理完成後,呼叫Context的Next方法,這樣Context原本依序執行的處理程序就可以繼續執行。

Leapcell:用於 Web 託管、非同步任務和 Redis 的下一代無伺服器平台

最後跟大家介紹一下部署Gin服務最好的平台:Leapcell。

A Deep Dive into Gin: Golang

1. 多語言支持

  • 使用 JavaScript、Python、Go 或 Rust 進行開發。

2.免費部署無限個項目

  • 只需支付使用費用-無請求,不收費。

3. 無與倫比的成本效益

  • 即用即付,無閒置費用。
  • 範例:25 美元支援 694 萬個請求,平均回應時間為 60 毫秒。

4.簡化的開發者體驗

  • 直覺的使用者介面,輕鬆設定。
  • 完全自動化的 CI/CD 管道和 GitOps 整合。
  • 即時指標和日誌記錄以獲取可操作的見解。

5. 輕鬆的可擴充性和高效能

  • 自動擴充以輕鬆處理高並發。
  • 零營運開銷-只需專注於建置。

在文件中探索更多內容!

Leapcell Twitter:https://x.com/LeapcellHQ

以上是深入探討 Gin:Golang 的領先框架的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

Golang和C:並發與原始速度 Golang和C:並發與原始速度 Apr 21, 2025 am 12:16 AM

Golang在並發性上優於C ,而C 在原始速度上優於Golang。 1)Golang通過goroutine和channel實現高效並發,適合處理大量並發任務。 2)C 通過編譯器優化和標準庫,提供接近硬件的高性能,適合需要極致優化的應用。

Golang vs.C:性能和速度比較 Golang vs.C:性能和速度比較 Apr 21, 2025 am 12:13 AM

Golang適合快速開發和並發場景,C 適用於需要極致性能和低級控制的場景。 1)Golang通過垃圾回收和並發機制提升性能,適合高並發Web服務開發。 2)C 通過手動內存管理和編譯器優化達到極致性能,適用於嵌入式系統開發。

Golang的影響:速度,效率和簡單性 Golang的影響:速度,效率和簡單性 Apr 14, 2025 am 12:11 AM

goimpactsdevelopmentpositationality throughspeed,效率和模擬性。 1)速度:gocompilesquicklyandrunseff,IdealforlargeProjects.2)效率:效率:ITScomprehenSevestAndardArdardArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdEcceSteral Depentencies,增強的Depleflovelmentimency.3)簡單性。

開始GO:初學者指南 開始GO:初學者指南 Apr 26, 2025 am 12:21 AM

goisidealforbeginnersandsubableforforcloudnetworkservicesduetoitssimplicity,效率和concurrencyFeatures.1)installgromtheofficialwebsitealwebsiteandverifywith'.2)

Golang vs. Python:主要差異和相似之處 Golang vs. Python:主要差異和相似之處 Apr 17, 2025 am 12:15 AM

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。Golang以其并发模型和高效性能著称,Python则以简洁语法和丰富库生态系统著称。

C和Golang:表演至關重要時 C和Golang:表演至關重要時 Apr 13, 2025 am 12:11 AM

C 更適合需要直接控制硬件資源和高性能優化的場景,而Golang更適合需要快速開發和高並發處理的場景。 1.C 的優勢在於其接近硬件的特性和高度的優化能力,適合遊戲開發等高性能需求。 2.Golang的優勢在於其簡潔的語法和天然的並發支持,適合高並發服務開發。

Golang和C:性能的權衡 Golang和C:性能的權衡 Apr 17, 2025 am 12:18 AM

Golang和C 在性能上的差異主要體現在內存管理、編譯優化和運行時效率等方面。 1)Golang的垃圾回收機制方便但可能影響性能,2)C 的手動內存管理和編譯器優化在遞歸計算中表現更為高效。

See all articles