Gin 框架 -八-: 中間件

原創 劉慶輝 猿碼記

中間件(英語:Middleware),又譯中間件、中介層,是一類提供系統軟件和應用軟件之間連接、便於軟件各部件之間的溝通的軟件,應用軟件可以藉助中間件在不同的技術架構之間共享信息與資源。

1. 介紹

Gin框架中, 中間件本質上是gin.HandlerFunc 函數,如果我們要自定義中間件,只需要返回類型是gin.HandlerFunc 即可。

Gin框架中, 使用中間件可分爲以下幾種場景:

2. 使用

2.1 全局使用

gin.Default()函數中,默認註冊全局中間件Logger、Recovery,具體代碼如下:

func Default() *Engine {
  // 打印debug信息
 debugPrintWARNINGDefault()
  // 創建引擎
 engine := New()
  // 註冊全局中間件
 engine.Use(Logger(), Recovery())
 return engine
}

2.2 單個路由使用

func main()  {
 engine := gin.New()
 // 添加一箇中間件: Logger()
 engine.GET("/route",gin.Logger(), func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"針對單個路由"})
 })
 // 添加多箇中間件: Logger(),Recovery()
 engine.GET("/route2",gin.Logger(),gin.Recovery(), func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"針對單個路由添加多箇中間件"})
 })
 _ = engine.Run()
}

2.3 路由組使用

func main()  {
 engine := gin.New()
  // 聲明路由後,通過Use註冊中間件
 v1Group := engine.Group("/v1").Use(gin.Logger())
 {
  v1Group.GET("/middleGroup", func(context *gin.Context) {
    context.JSON(200,gin.H{"msg":"succss"})
  })
 }
 _ = engine.Run()
}

3. 自定義中間件

我們可以創建自己的中間件,在中間件中需要繼續執行時, 使用c.Next(), 而要終止執行時則需調用c.Abort()終止請求。

3.1 語法結構

func MiddleName() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 請求前業務邏輯...
    // 繼續往下執行
  context.Next()
    // context.Abort() 停止往下執行
  // 請求後業務邏輯...
  end := time.Now().Unix()
  fmt.Printf("接口耗時:%v 秒 \n", end - t)
 }
}

代碼說明:

3.2 示例

// 聲明自定義中間件
func MyMiddleware() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 請求前
  fmt.Println("中間件-- 請求前")
  t := time.Now().Unix()
    // 繼續往下執行
  context.Next()
    // context.Abort() 停止往下執行
  // 請求後
  end := time.Now().Unix()
  fmt.Printf("接口耗時:%v 秒 \n", end - t)
 }
}
// 自定義中間件使用
func RunWithMyMiddle()  {
 engine := gin.New()
 // 註冊自定義中間件
 engine.GET("/route",MyMiddleware(), func(context *gin.Context) {
  time.Sleep(time.Second * 3)
  context.JSON(200,gin.H{"msg":"自定義路由"})
 })
 _ = engine.Run()
}

4. 多箇中間件

如果同時註冊多箇中間件,那麼他們之間的執行順序是什麼樣呢?

4.1 執行流程

4.2 代碼示例

// 自定義中間件A
func MyMiddleware() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 請求前
  fmt.Println("中間件1-- 請求前")
  context.Next()
  // 請求後
  fmt.Println("中間件1-- 請求後")
 }
}
// 自定義中間件B
func MyMiddleware2() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 請求前
  fmt.Println("中間件2-- 請求前")
  context.Next()
  // 請求後
  fmt.Println("中間件2-- 請求後")
 }
}
// 啓動服務
func main()  {
 engine := gin.New()
 // 使用多箇中間件
 engine.GET("/route",MyMiddleware(), MyMiddleware2(),func(context *gin.Context) {
  time.Sleep(time.Second * 3)
  context.JSON(200,gin.H{"msg":"自定義路由"})
 })
 _ = engine.Run()
}

請求控制檯輸出:

中間件1-- 請求前
中間件2-- 請求前
中間件2-- 請求後
中間件1-- 請求後

5. 實踐

創建自定義中間件用來判斷Token是否有效 (是否是登錄狀態)。

5.1 源碼

./mian.go代碼:

package main
import "go-use/practise"
func main() {
 practise.RunServeWithCheckToken()
}

./practise/check_token.go 代碼:

package practise
import (
 "github.com/gin-gonic/gin"
)
// 檢測token
func CheckTokenMiddle() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 獲取token
  token := context.DefaultQuery("token","")
  // 檢測token
  if token != "abcd" {
   context.JSON(500,gin.H{"msg":"請先登錄!"})
   // 終止請求
   context.Abort()
  }
  // token合法則繼續往下執行
  context.Next()
 }
}
// 啓動服務
func RunServeWithCheckToken()  {
 engine := gin.Default()
 // 登錄接口不需要添加檢測Token中間件
 engine.GET("/user/login", func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"登錄成功!","token":"abcd"})
 })
 // 需要驗證token的路由
 user := engine.Group("/user").Use(CheckTokenMiddle())
 {
  // 用戶基本信息
  user.GET("/info", func(context *gin.Context) {
   data := map[string]interface{} {
    "name""張三",
    "age": 18,
    "likes"[]string{"打遊戲","旅遊"},
   }
   context.JSON(200,gin.H{"msg":"請求成功","data":data})
  })
  // 更新用戶
  user.GET("/update", func(context *gin.Context) {
   context.JSON(200,gin.H{"msg":"請求成功"})
  })
 }
 // 啓動服務
 _ = engine.Run()
}

5.2 請求

# 不需要驗證token的接口
➜ curl -X GET http://127.0.0.1:8080/user/login
{"msg":"登錄成功!","token":"abcd"}
# 需要驗證Token,但是沒傳時
➜ curl -X GET http://127.0.0.1:8080/user/info
{"msg":"請先登錄!"}
# 需要驗證Token,並且已傳時
➜  curl -X GET http://127.0.0.1:8080/user/info?token=abcd
{"data":{"age":18,"likes":["打遊戲","旅遊"],"name":"張三"},"msg":"請求成功"}

6. 使用 Goroutine

當在中間件或 handler 中啓動新的 Goroutine 時,不能使用原始的上下文,必須使用只讀副本。

6.1 代碼示例

// 在中間件中使用Goroutine
func main()  {
 engine := gin.Default()
 engine.Use(MyMiddleWithGoRoutine())
 engine.GET("/useGo", func(context *gin.Context) {
  context.JSON(200,gin.H{"msg":"success"})
 })
 _ = engine.Run()
}
// 在中間件中使用Goroutine
func MyMiddleWithGoRoutine() gin.HandlerFunc {
 return func(context *gin.Context) {
  // 複製上下文
  cpContext := context.Copy()
  go func() {
   time.Sleep(3 * time.Second)
   fmt.Println(cpContext.Request.URL.Path)
  }()
  context.Next()
 }
}

7. 流行的中間件

在 gin-gonic/contrib(https://github.com/gin-gonic/contrib) 庫中整理很多常用的中間件,可以直接使用,具體中間件有以下:

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