gin 框架上手實踐

FunTester發表於2024-07-26

Gin框架是一個用 Go 語言編寫的高效能 Web 框架,以其速度和簡潔性著稱。它由一個輕量級的 HTTP 路由器和一箇中介軟體架構組成,能夠處理大型流量並簡化開發者的工作。Gin的主要特點包括內建的路由組、簡潔的 API 設計、強大的錯誤處理機制、支援多種格式的請求繫結和驗證,以及內建的日誌記錄功能。由於其效能優越和易於使用,Gin廣泛應用於構建RESTful API和 Web 服務。其設計理念是儘可能減少繁瑣的配置和程式碼,讓開發者專注於業務邏輯,實現快速開發和部署。

PS:據部分資料顯示,HTTP路由的最佳化,速度提升了 10 倍以上。但我是查到的主流資料,效能提升沒這麼誇張。對比我知道的一些框架,QPS 提升還可以,大多數都是小於 3 倍的,但是記憶體消耗非常小,記憶體分配和分配時間非常小。主要原因也是因為 gin 採用了大量的 sync.Pool 最佳化,這一點跟 fasthttp 一樣的邏輯。所以池化技術是一個效能最佳化的大殺器。

下面我們來開始 gin 框架的實踐之旅。

依賴和安裝

  1. Go 語言環境
  2. 安裝 gin :go get -u github.com/gin-gonic/gin
  3. 開始擼程式碼

這是一個例子

首先我們先看一個最簡單的例子:

package main  

import (  
    "github.com/gin-gonic/gin"  
    "net/http")  

func main() {  
    //建立一個預設的路由引擎,預設使用了Logger和Recovery中介軟體  
    r := gin.Default()  
    //註冊一個路由和處理函式  
    r.GET("/", func(c *gin.Context) {  
       //返回一個字串,狀態碼是200  
       c.String(http.StatusOK, "Hello FunTester")  
    })  
    //監聽埠,預設是8080,這裡一般不設定IP,原因是在伺服器上可能有多個IP,如果設定了IP,就只能監聽這個IP  
    r.Run(":8000")  
}

請求與響應

上面的每行程式碼的功能都已經寫好了註釋,是不是非常簡單,其中 GET 方法的引數型別是: handlers ...HandlerFunc ,而 HandlerFunc 的定義是 type HandlerFunc func(*Context) ,其中 gin.ContextGin 框架中最核心的結構之一,它提供了上下文環境,供處理 HTTP 請求和響應。gin.Context包含許多有用的方法和屬性,使開發者能夠輕鬆訪問請求資料、設定響應資料、處理錯誤以及在中介軟體和處理器之間傳遞資訊。

下面是 gin.Context 構造方法:

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8
    fullPath string

    engine       *Engine
    params       *Params
    skippedNodes *[]skippedNode

    // This mutex protects Keys map.
    mu sync.RWMutex

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]any

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string

    // queryCache caches the query result from c.Request.URL.Query().
    queryCache url.Values

    // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
    // or PUT body parameters.
    formCache url.Values

    // SameSite allows a server to define a cookie attribute making it impossible for
    // the browser to send this cookie along with cross-site requests.
    sameSite http.SameSite
}

相信看到這裡,你一定若有所悟了吧。gin 框架用來處理請求響應都是靠 gin.Context 而請求和響應都包含在 gin.Context 當中。

gin 框架的請求方法是透過方法名直接設定的,例如上面例子中的 r.GET 就表示這個介面註冊了 GET 方法的呼叫。下面是 gin 支援的呼叫方法:

  1. GET:用於從伺服器獲取資源。
  2. POST:用於向伺服器提交資料,通常用於建立資源。
  3. PUT:用於更新伺服器上的資源。
  4. DELETE:用於刪除伺服器上的資源。
  5. PATCH:用於部分更新伺服器上的資源。
  6. HEAD:類似於 GET 請求,但只返回響應頭,用於獲取資源的後設資料。
  7. OPTIONS:用於獲取伺服器支援的 HTTP 方法。
  8. TRACE:用於回顯伺服器收到的請求,主要用於診斷。

這些方法覆蓋了基本的 HTTP 操作,允許客戶端與伺服器進行各種型別的互動。

下面我們看看處理響應的幾種方法:

JSON 響應

router.GET("/json", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "hello",
        "status":  "success",
    })
})

String 響應

router.GET("/string", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello, world")
})

HTML 響應

router.LoadHTMLGlob("templates/*")
router.GET("/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
        "title": "Main website",
    })
})

當然還有其他格式的響應,這裡就不演示了。

引數解析

路徑引數

URL 路徑當中引數是最常見的一種型別,在 gin 框架當中,針對這種情況設定了兩種型別。第一種是普通的路徑引數,另一種是正則匹配的 URL 地址。一開始我也有點懵,下面來看程式碼演示。

router.GET("/user/:id/*path", func(c *gin.Context) {
    id := c.Param("id")
    path := c.Param("path")
    // 根據id獲取使用者資訊或處理其他邏輯
    c.String(http.StatusOK, "User ID: %s Path: %s", id, path)
})

這裡 id 就是普通的路徑引數, path 就是匹配引數。假如請求路徑是 /user/1/age 匹配結果就是 id=1path=/age。假如路徑是 /user/2/account/balance ,那麼 id=2 ,而 path=/account/balance 這下就清楚這兩者區別,通常不咋會用到匹配的引數。

query 引數

獲取 Get 請求引數的常用函式:

  • func (c Context) Query*(key string) string
  • func (c Context) DefaultQuery*(key, defaultValue string) string
  • func (c Context) GetQuery*(key string) (string, bool)

演示程式碼:

func Handler(c *gin.Context) {
    //獲取name引數, 透過Query獲取的引數值是String型別。
    name := c.Query("name")
    //獲取name引數, 跟Query函式的區別是,可以透過第二個引數設定預設值。
    name := c.DefaultQuery("name", "tizi365")
    //獲取id引數, 透過GetQuery獲取的引數值也是String型別, 
    // 區別是GetQuery返回兩個引數,第一個是引數值,第二個引數是引數是否存在的bool值,可以用來判斷引數是否存在。
    id, ok := c.GetQuery("id")
        if !ok {
       // 引數不存在
    }
}

POST 引數

獲取 Post 請求引數的常用函式:

  • func (c Context) PostForm*(key string) string
  • func (c Context) DefaultPostForm*(key, defaultValue string) string
  • func (c Context) GetPostForm*(key string) (string, bool)

演示程式碼如下:

func Handler(c *gin.Context) {
    //獲取name引數, 透過PostForm獲取的引數值是String型別。
    name := c.PostForm("name")

    // 跟PostForm的區別是可以透過第二個引數設定引數預設值
    name := c.DefaultPostForm("name", "tizi365")

    //獲取id引數, 透過GetPostForm獲取的引數值也是String型別,
    // 區別是GetPostForm返回兩個引數,第一個是引數值,第二個引數是引數是否存在的bool值,可以用來判斷引數是否存在。
    id, ok := c.GetPostForm("id")
    if !ok {
        // 引數不存在
    }
}

物件引數

前面獲取引數的方式都是一個個引數的讀取,比較麻煩,Gin框架支援將請求引數自動繫結到一個struct物件,這種方式支援 Get/Post 請求,也支援HTTP請求body內容為json/xml格式的引數。

type User struct {  
    Name string `json:"name" form:"name"`  
    Age  int    `json:"age"  form:"age"`  
}

演示程式碼如下:

user.GET("/login", func(c *gin.Context) {  
    u := &User{}  
    c.ShouldBind(u)  
    c.JSON(http.StatusOK, u)  
})

分組路由

在 Gin 框架中,分組路由(Route Groups)是用於組織路由和中介軟體的一種有效方式。分組路由可以幫助你將相關的路由和中介軟體組織在一起,使程式碼更加清晰和易於維護。以下是如何在 Gin 框架中使用分組路由的示例:

user := r.Group("/user")  
{  
    user.GET("/login", func(c *gin.Context) {  
       u := &User{}  
       c.ShouldBind(u)  
       c.JSON(http.StatusOK, u)  
    })  
}

非常簡單的語法,只需要註冊一個 Group 即可,然後組內註冊的 HandlerFunc 的路由字首會加上組的 relativePath 也就是 /user

中介軟體

在 Gin 框架中,中介軟體(Middleware)是一個函式,它可以在處理請求之前或之後執行特定的操作。中介軟體通常用於執行一些通用的任務,比如日誌記錄、身份驗證、跨域資源共享(CORS)處理等。Gin 框架支援全域性中介軟體、路由組中介軟體和單個路由中介軟體。

基於 gin.Context 強大的功能,以及 gin 框架的優秀設計,中介軟體的實現方法依舊是返回 HandlerFunc 即可。下面展示一個列印日誌的中介軟體。

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 開始時間
        t := time.Now()
        // 處理請求
        c.Next()
        // 計算執行時間
        latency := time.Since(t)
        log.Printf("Latency: %v", latency)
        // 獲取響應狀態碼
        status := c.Writer.Status()
        log.Printf("Status: %d", status)
    }
}

中介軟體的應用的話,有 3 種型別:全域性、一組路由、單個路由。下面一次展示使用方法:

全域性:

func main() {
    router := gin.Default()
    // 應用全域性中介軟體
    router.Use(Logger())
    router.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })
    router.Run(":8080")
}

針對一組路由:

func main() {
    router := gin.Default()
    // 定義一個路由組
    apiGroup := router.Group("/api")
    {
        // 應用中介軟體到路由組
        apiGroup.Use(Logger())
        apiGroup.GET("/users", func(c *gin.Context) {
            c.String(http.StatusOK, "List of users")
        })
        apiGroup.GET("/user/:id", func(c *gin.Context) {
            id := c.Param("id")
            c.String(http.StatusOK, "User ID: %s", id)
        })
    }
    router.Run(":8080")
}

針對單個路由:

func main() {
    router := gin.Default()
    // 應用中介軟體到單個路由
    router.GET("/admin", Logger(), func(c *gin.Context) {
        c.String(http.StatusOK, "Admin page")
    })
    router.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })
    router.Run(":8080")
}

下面是我列印響應結果的中介軟體實踐:


func Check() gin.HandlerFunc {  
    return func(c *gin.Context) {  
       c.Set("example", "12345")  
       //path := c.Request.URL.Path  
       w := &responseWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}  
       c.Writer = w  
       c.Next()  
       responseBody := w.body.String()  
       logger.Info("response", zap.String("response", responseBody))  
    }  
}

type responseWriter struct {  
    gin.ResponseWriter  
    body *bytes.Buffer  
}  

func (w responseWriter) Write(b []byte) (int, error) {  
    w.body.Write(b)  
    return w.ResponseWriter.Write(b)  
}

其他

還有一些其他的常用 API,下面我撿著自己學到的展示一下。

獲取客戶端 IP:c.ClientIP()

設定模式:

// 設定 release模式  
//gin.SetMode(gin.ReleaseMode)  
// 或者 設定debug模式  
gin.SetMode(gin.DebugMode)

Gin 框架提供了兩種執行模式:Release 模式Debug 模式。這兩種模式主要區別在於日誌輸出和錯誤處理方面。透過設定不同的模式,開發者可以更好地適應開發和生產環境的需求。這個看需求選用吧。

  • 2021 年原創合集
  • 2022 年原創合集
  • 2023 年原創合集
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI 自動化
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章