Gin - Middleware (中間件) 程式運作原理及用法教學

Gin - Middleware (中間件) 程式運作原理及用法教學

在 Web 應用及開發中,一個完整的請求會包含客戶端請求,伺服器端接收及處理,返回內容給結果。

在真實的場景中,在這些流程還會有更複雜的功能,例如:權限管理,安全驗證及日誌等部分,因此在開發過程我們常要思考,如何更好的管理這些通用的功能,以套件的方式來進行開發,就可以很方便地進行對接及配置。

在這裡就要介紹Gin 的 Middleware (中間件)的運作方式及原理,如何透過 Middleware 來管理這些通用的一些服務。

Middleware 負責作用的位置如下:

Client 請求 <-> Go Http server <-> Middleware <-> Handle Function

Middleware 基本結構說明

在 Gin 的 Middleware 主要的結構如下:

type HandlerFunc func(*Context)

要用 use 調用 Middleware,在 Go 語言實踐的方式如下:

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	return engine
}

其實,在 Gin 架構本身有許多部分就是用 Middleware 設計,例如,gin.Default()

CVT2HUGO: 這個方法就已經使用了 Middleware
engine.Use(Logger(), Recover())

例如, Logger() 負責日誌的部分,格式如下

func Logger() HandlerFunc{
	return LoggerWithConfig(LoggerCOnfig{})
}

對於 Recovery() 則是在我們服務發生錯誤時,會避免panic 並且返回 500 錯誤訊息。

如何自定義制定 Middleware

自定義 Middleware 主要須滿足以下兩個條件:

  • 函數 func
  • 返回 HandlerFunc

建立通用的 Middleware

舉例來說,這裡我們建立一個簡單的 middleware ,在每次請求過程都會 print 出 Helloworld

func RequestPrintHelloworld() gin.HandlerFunc {
	return func() {
	fmt.Println("Helloworld")
	}
}

接著我們就可以在代碼中使用這個 Middleware

func main() {
	engine := gin.Default()
  engine.Use(RequestPrintHelloworld())

  engine.GET('/home', func(context *gin.Context) {
    context.JSON(200, map[string]interface{}{
      "status":1,
      "msg":"success",
    }))
  })
}

特定路由套用Middleware

在前面的情況下,是所有的請求都會一律套用 Middleware,如果是權限驗證,則是有些需要,有些請求不需要套用 Middleware。

這裡可以先了解一下 Gin 的Http請求的部分,以 GET 方法來說,格式基本上如下:

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}

可以看到,第一個參數是接口參數,第二個參數是 HandlerFunc,從這裡可以了解到,如果Middleware 需要套用到某些路由,則可以在第二個參數進行套用,例如:

engines := gin.Default()

engine.GET("/home", RequestPrintHelloworld(), func(context *gin.Context) {
    context.JSON(200, map[string]interface{}{
      "status":1,
      "msg":"success",
  }))
})

透過Middleware處理請求後的行為

前面我們提到,在 Middleware 的請求是在解析請求之前完成,但如果某些情況下,想要在請求處理結束後,再透過 Middleware 來處理,要怎麼做呢?

這時可以透過 context.Next 函數來處理請求後的行為。

context.Next 可以把請求處理流程差分為兩部分:

在使用 context.Next 之前的的訊息,會按照 Middleware 正常的流程進行處理,也就是在接口解析之前就會完成:

Client 請求 <-> Go Http server <-> Middleware <-> Handle Function

在 context.Next 之後的內容,則會在接口解析處理之後,透過Middleware 來處理。

也就是說,流程大致如下:

Client 請求 -> Go Http server -> Middleware(context.Next 之前)-> Handle Function 解析接口 -> Middleware(context.Next 之後) -> 返回訊息給 Client 

底下舉例

func RequestPrintHelloworld() gin.HandlerFunc {
	return func(context *gin.Context) {
	fmt.Println("Helloworld")
	
	fmt.Println("正常 Middleware 流程", context.Writer.Status())
    
	context.Next() //開始導向解析接口
	
	
	fmt.Println("接口解析及處理完成,並且取得狀態", context.Writer.Status())
	}
}

在我們接口來直接返回 404

engines := gin.Default()

engine.GET("/home", RequestPrintHelloworld(), func(context *gin.Context) {
  fmt.Println("路由解析 Handle Function 流程")
    context.JSON(404, map[string]interface{}{
      "status":0,
      "msg":"not found",
  }))
})

接著會看到打印出的內容為:

正常 Middleware 流程 200
路由解析 Handle Function 流程
接口解析及處理完成,並且取得狀態 404

這裡可以看到,一開始進入 Middleware 時,請求狀態是正常 200,接著進入 router 解析狀態碼,我們直接返回 404,接著在Middleware content.Next() 之後,取得的狀態碼為 404。

以上就是在 Gin 的 Middleware 的一些運作原理及應用方式。