Go Gin的Middleware(中間件)簡介。
Gin使用router設定路由路徑時,可以透過RouterGroup.Use(middleware ...HandlerFunc)
設定middleware。
Middleware的用途為若多個請求路徑需要執行某些相同的邏輯時,可以透過設定middleware來達成。概念類似Java Spring Boot框架的Intercepter是對請求的橫切。
Middleware是一個type為gin.HandlerFunc
的函式,簽章為func(*Context)
,Context
為gin的環境變數用來在middleware間傳遞資料。所以middleware翻成「路由中介函式」比較好理解。
範例
範例環境:
- Go 1.16
- Gin 1.7.2
目錄結構:
/
├─ main.go
└─ middleware/
└─ demo_middleware.go
例如下面在middleware/demo_middleware.go
中定義一個函式PrintHello()
回傳自訂的middleware gin.HandlerFunc()
函式實作。收到請求後會先印出"hello-"後接傳入的字串變數s
,接著調用Context.Next()
呼叫下一個middleware函式,回應時(response)會依相反的順序執行middleware函式中Context.Next()
之後的邏輯。
/middleware/demo_middleware.go
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
)
func PrintHello(s string) gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("hello-" + s)
// before request
c.Next() // execute next middleware handler
// after request
fmt.Println("bye-" + s)
}
}
在main()
中使用RouterGroup.Use()
設定middleware PrintHello()
。
main.go
package main
import (
"fmt"
"abc.com/demo/middleware"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.Use(
middleware.PrintHello("1"),
middleware.PrintHello("2"),
middleware.PrintHello("3"),
)
router.GET("/demo", func(c *gin.Context) {
msg := "demo"
fmt.Println(msg)
c.JSON(200, msg)
})
router.Run()
}
測試
使用cURL發送請求給/demo
在console則顯示以下。
hello-1
hello-2
hello-3
demo
bye-3
bye-2
bye-1
以圖是來看像是這樣。
┌───────────────────────────────┐
│ PrintHello("1") │
│ │
│ hello-1 │
│ ┌─────────────────────────┐ │
│ │ PrintHello("2") │ │
│ │ │ │
│ │ hello-2 │ │
│ │ ┌───────────────────┐ │ │
│ │ │ PrintHello("3") │ │ │
│ │ │ │ │ │
GET|/demo │ │ │ hello-3 │ │ │
────────────┼──┼──┼───────────────────┼──┼──┼───────────┐
│ │ │ │ │ │ │ demo
◄───────────┼──┼──┼───────────────────┼──┼──┼───────────┘
200 │ │ │ bye-3 │ │ │
"demo" │ │ └───────────────────┘ │ │
│ │ bye-2 │ │
│ └─────────────────────────┘ │
│ bye-1 │
└───────────────────────────────┘
原碼分析
Gin middleware handler原始碼運作方式如下。
呼叫RouterGroup.Use()
設定middleware handler。
gin.go#L261
// gin.go line 261-266
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
...
}
routergroup.go#L51
// routergroup.go line:51-54
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...) // append to engind.RouterGroup.Handlers
...
}
在呼叫RouterGroup.GET()
設定API path時才會把middleware handler放入Gin的engine.trees
中的node
。
routergroup.go#L102
// routergroup.go line:102-104
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
group.combineHandler()
把特定http method的route path的handler參數與既有的route group的middleware handler結合。group.engine.addRoute()
把handlers
放入engine.trees
的node
routergroup.go#L72
// routergroup.go line:72-77
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
...
handlers = group.combineHandlers(handlers) // combine parameter handlers with engine.RouterGroup.Handlers
group.engine.addRoute(httpMethod, absolutePath, handlers) // set handler in methodTrees's node
...
}
以http method從engine.trees
取得node
並放入handlers
。
gin.go#L276
// gin.go line:276-295
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
...
root := engine.trees.get(method) // root is a node type with path and handlers
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers) // set handlers to tree node
...
}
到此Gin的engine
中就紀錄了每個route path被呼叫時要執行的handlers(及middleware handlers)。
當Gin接收route的請求時是透過Go的http.ServeHTTP()
把收到的Request
及ResponseWriter
轉交給Gin Engine.ServeHTTP()
。
server.go#L2859
// server.go line:2859-2868
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
...
handler.ServeHTTP(rw, req) // handler is Gin's Engine
}
然後取得engine
建立時的Context
把Request
及ResponseWriter
放入後轉交給engine.handleHTTPRequest()
。
gin.go#L439
// gin.go line:439-448
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
...
}
在handleHTTPRequest()
中loop engine.trees
的每一個node
並把其handlers存入Context.handlers
然後呼叫Context.Next()
執行。
gin.go#L461
// gin.go line:461-519
func (engine *Engine) handleHTTPRequest(c *Context) {
...
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
...
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
...
if value.handlers != nil {
c.handlers = value.handlers // set middleware handlers to Context.handlers
c.fullPath = value.fullPath
c.Next() // run handlers
c.writermem.WriteHeaderNow()
return
}
...
}
...
}
Context.Next()
中便會開始loop執行RouterGroup.Use()
設定的每一個middleware handlers,並將自身作為參數傳入。
context.go#L62
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c) // invoke handler
c.index++
}
}
而middleware handler中調用Context.Next()
又會執行上面函示,context的index遞增,從c.handlers
取出下一個handler執行。
以上簡化如下。
package main
import "fmt"
func main() {
handlers := [4]HandlerFunc{
PrintHello("1"),
PrintHello("2"),
PrintHello("3"),
Demo(),
}
c := NewContext()
c.handlers = handlers[:]
c.Next()
}
func Demo() HandlerFunc {
return func(c *Context) {
fmt.Println("demo")
}
}
func PrintHello(s string) HandlerFunc {
return func(c *Context) {
fmt.Println("hello-" + s)
c.Next()
fmt.Println("bye-" + s)
}
}
func NewContext() Context {
return Context{
index: -1,
}
}
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
type Context struct {
handlers HandlersChain
index int8
}
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
Gin官網文件上只列出基本的範例但都沒有詳細說明,如果得自己去看原始碼學習在使用上的效率會很差。雖然網路上不少人寫相關技術文章但畢竟都不是官方來源。不詳盡的官方文件對於技術的普及是個障礙。豐富的官方文件說明及範例絕對是一個框架是否能被市場採用的關鍵,例如Java Spring Boot或React就是個很好的例子。
啊?還是說自己可以去gin-gonic/website
contribute?
沒有留言:
張貼留言