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 的一些運作原理及應用方式。