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

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 



回呼函數有兩個參數。第一個是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
學習GO二進制編碼/解碼:使用'編碼/二進制”軟件包學習GO二進制編碼/解碼:使用'編碼/二進制”軟件包May 08, 2025 am 12:13 AM

Go語言使用"encoding/binary"包進行二進制編碼與解碼。 1)該包提供binary.Write和binary.Read函數,用於數據的寫入和讀取。 2)需要注意選擇正確的字節序(如BigEndian或LittleEndian)。 3)數據對齊和錯誤處理也是關鍵,確保數據的正確性和性能。

GO:帶有標準'字節”軟件包的字節切​​片操作GO:帶有標準'字節”軟件包的字節切​​片操作May 08, 2025 am 12:09 AM

1)usebybytes.joinforconcatenatinges,2)bytes.bufferforincrementalwriting,3)bytes.indexorbytes.indexorbytes.indexbyteforsearching bytes.bytes.readereforrednorederencretingnchunknunknchunknunk.sss.inc.softes.4)

進行編碼/二進制包:優化二進制操作的性能進行編碼/二進制包:優化二進制操作的性能May 08, 2025 am 12:06 AM

theencoding/binarypackageingoiseforporptimizingBinaryBinaryOperationsDuetoitssupportforendiannessessandefficityDatahandling.toenhancePerformance:1)usebinary.nativeendiandiandiandiandiandiandiandian nessideendian toavoid avoidByteByteswapping.2)

Go Bytes軟件包:簡短的參考和提示Go Bytes軟件包:簡短的參考和提示May 08, 2025 am 12:05 AM

Go的bytes包主要用於高效處理字節切片。 1)使用bytes.Buffer可以高效進行字符串拼接,避免不必要的內存分配。 2)bytes.Equal函數用於快速比較字節切片。 3)bytes.Index、bytes.Split和bytes.ReplaceAll函數可用於搜索和操作字節切片,但需注意性能問題。

Go Bytes軟件包:字節切片操縱的實例Go Bytes軟件包:字節切片操縱的實例May 08, 2025 am 12:01 AM

字節包提供了多種功能來高效處理字節切片。 1)使用bytes.Contains檢查字節序列。 2)用bytes.Split分割字節切片。 3)通過bytes.Replace替換字節序列。 4)用bytes.Join連接多個字節切片。 5)利用bytes.Buffer構建數據。 6)結合bytes.Map進行錯誤處理和數據驗證。

進行二進制編碼/解碼:實踐指南進行二進制編碼/解碼:實踐指南May 07, 2025 pm 05:37 PM

Go的encoding/binary包是處理二進制數據的工具。 1)它支持小端和大端字節序,可用於網絡協議和文件格式。 2)可以通過Read和Write函數處理複雜結構的編碼和解碼。 3)使用時需注意字節序和數據類型的一致性,尤其在不同系統間傳輸數據時。該包適合高效處理二進制數據,但需謹慎管理字節切片和長度。

Go'字節”軟件包:比較,加入,分裂及更多Go'字節”軟件包:比較,加入,分裂及更多May 07, 2025 pm 05:29 PM

“字節”包裝封裝becapeitoffersefficerSoperationsOnbyteslices,cocialforbinarydatahandling,textPrococessing,andnetworkCommunications.byteslesalemutable,允許forforforforforformance-enhangingin-enhangingin-placemodifications,makaythisspackage

GO弦套件:您需要知道的基本功能GO弦套件:您需要知道的基本功能May 07, 2025 pm 04:57 PM

go'sstringspackageIncludeSessentialFunctionsLikeContains,trimspace,split,andreplaceAll.1)contunsefefitedsseffitedsfificeCheckSforSubStrings.2)trimspaceRemovesWhitespaceToeensuredity.3)splitparsentertparsentertparsentertparsentertparstructedtextlikecsv.4)report textlikecsv.4)

See all articles

熱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

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

熱工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。