四種Golang實現(xiàn)middleware框架的方式小結(jié)
寫在前面
middleware是一般框架里面常用的形式,比如web框架、rpc框架,通過middleware在流量入口和出口做一些公共事情,包括鑒權(quán)、日志、埋點、統(tǒng)計、限流、參數(shù)處理、異常處理等等。
在工作中經(jīng)常會用到,在閱讀web框架(gin,beego)的時候也會遇到,今天總結(jié)一下middleware有哪些實現(xiàn)方式。
方案一:數(shù)組遞歸調(diào)用
package middleware import "context" // 處理函數(shù) type Handler func(ctx context.Context, msg string) error // 插件類型 type MiddleWareFunc func(ctx context.Context, msg string, next Handler) error type MiddlewareManager struct { handler Handler middlewares []MiddleWareFunc } func NewMiddlewareManager(handler Handler) *MiddlewareManager { return &MiddlewareManager{ handler: handler, } } func (m *MiddlewareManager) Register(middlewares ...MiddleWareFunc) { m.middlewares = append(m.middlewares, middlewares...) } func (m *MiddlewareManager) Exec(ctx context.Context, msg string) error { handlerFunc := func(ctx context.Context, msg string, next Handler) error { return m.handler(ctx, msg) } m.middlewares = append(m.middlewares, handlerFunc) callChain := m.mkCallChain(m.middlewares) return callChain(ctx, msg) } func (m *MiddlewareManager) mkCallChain( middlewares []MiddleWareFunc) Handler { if len(middlewares) <= 0 { return nil } return func(ctx context.Context, msg string) error { return middlewares[0](ctx, msg, m.mkCallChain(middlewares[1:])) } }
在MiddlewareManager
結(jié)構(gòu)體中定義業(yè)務(wù)處理函數(shù)handler
和插件數(shù)組middlewares
,在執(zhí)行函數(shù)Exec
里面,將業(yè)務(wù)處理函數(shù)handler
封裝成一個middleware放到middlewares
后面,然后遞歸調(diào)用內(nèi)部函數(shù)mkCallChain
。這個內(nèi)部函數(shù)mkCallChain
返回的是一個函數(shù),將所有middleware一層一層包裹起來,最終callChain := m.mkCallChain(m.middlewares)
得到的是一個調(diào)用鏈。
這段代碼有點繞,需要細(xì)品。
測試方案一
// 方案一 fmt.Println("===方案一 begin") m1 := middleware.NewMiddlewareManager(HandlerMsg) m1.Register(middleware.TimeCostMW, middleware.FilterMW, middleware.LoggerMW) if err := m1.Exec(context.Background(), "hello chain"); err != nil { panic(err) } fmt.Println("===方案一 end")
結(jié)果
===方案一 begin
TimeCost before
FinlterMW begin
LoggerMW before
HandlerMsg: hello chain
LoggerMW end
FinlterMW end
TimeCostMW:cost 1000428754
===方案一 end
方案二:順序?qū)崿F(xiàn)
package middlewarecontext type MiddleWareFunc func(ctx *MyContext) error type MyContext struct { middlewares []MiddleWareFunc idx int maxIdx int } func NewMyContext() *MyContext { return &MyContext{ middlewares: make([]MiddleWareFunc, 0), } } // 執(zhí)行下一個middleware func (m *MyContext) Next() error { if m.idx < m.maxIdx-1 { m.idx += 1 return m.middlewares[m.idx](m) } return nil } // 終止middleware func (m *MyContext) Abort() { m.idx = m.maxIdx } func (m *MyContext) Register(middlewares ...MiddleWareFunc) { m.middlewares = append(m.middlewares, middlewares...) m.maxIdx = len(m.middlewares) } func (m *MyContext) Exec() error { // 從第一個middleware開始執(zhí)行 return m.middlewares[0](m) }
核心代碼是這段
type MyContext struct { middlewares []MiddleWareFunc idx int maxIdx int }
自己定義一個context將所有middleware作為數(shù)組放在context中,執(zhí)行Exec()
的時候就執(zhí)行第一個middleware,并且將context傳進(jìn)去。其他middlewaer中通過調(diào)用Next()
函數(shù)來觸發(fā)下一個middleware。
這種方式看起來邏輯簡單,容易理解。gin框架的middleware就是這樣實現(xiàn)的。這個方式是作者對gin框架的middleware的總結(jié)和抽象。
測試方案二
fmt.Println("===方案二 begin") m2 := middlewarecontext.NewMyContext() m2.Register( middlewarecontext.TimeCostMW, middlewarecontext.FilterMW, middlewarecontext.LoggerMW) if err := m2.Exec(); err != nil { panic(err) } fmt.Println("===方案二 end")
結(jié)果
===方案二 begin
TimeCost before
FinlterMW begin
LoggerMW before
LoggerMW end
FinlterMW end
TimeCostMW:cost 1000588399
===方案二 end
方式三:鏈?zhǔn)秸{(diào)用
package middlewarechain import "context" type Handler func(ctx context.Context) error type MiddleWareFunc func(ctx context.Context, next Handler) Handler
這段代碼邏輯很簡單,它就是將上一個middleweare作為next參數(shù)傳到當(dāng)前middleware,形成鏈?zhǔn)秸{(diào)用。
看到這個定義你會不會覺得很奇怪,怎么這么點代碼?
是的,它的代碼就是這么少。有句話說的好“哪有什么歲月靜好,不過是有人替你負(fù)重前行,生活從來都不容易”,定義的地方代碼少了,調(diào)用的時候肯定就復(fù)雜了。
下面看看測試用例
fmt.Println("===方案三 begin") ctx := context.Background() m3 := middlewarechain.TimeCostMW(ctx, func(ctx context.Context) error { PrintMsg("test") return nil }) m4 := middlewarechain.FilterMW(ctx, m3) m5 := middlewarechain.LoggerMW(ctx, m4) if err := m5(ctx); err != nil { fmt.Println(err) } fmt.Println("===方案三 end")
結(jié)果
===方案三 begin
LoggerMW before
FinlterMW begin
TimeCost before
PrintMsg:test
TimeCostMW:cost 6130
FinlterMW end
LoggerMW end
===方案三 end
可見,在定義middleweare的時候,要將上一個middleeware傳入當(dāng)前middleeware的定義。跟其他幾種方案相比,其實它就是將middleware的注冊去掉了,沒有地方維護(hù)所有的middleware。
方案四:for循環(huán)實現(xiàn)
package middlewarefor import "context" type Handler func(ctx context.Context) error type Middleware func(next Handler) Handler type MiddlewareManager struct { middlewares []Middleware } func NewMiddlewareManager(middlewares ...Middleware) *MiddlewareManager { return &MiddlewareManager{ middlewares: middlewares, } } func (m *MiddlewareManager) Register(middlewares ...Middleware) { m.middlewares = append(m.middlewares, middlewares...) } func (m *MiddlewareManager) Exec(ctx context.Context) error { handler := defaultHandler for i := range m.middlewares { handler = m.middlewares[len(m.middlewares)-i-1](handler) } return handler(ctx) } func defaultHandler(ctx context.Context) error { return nil }
它跟方案一很像,都是定義一個MiddlewareManager
結(jié)構(gòu)體,內(nèi)部維護(hù)一個middlewares
數(shù)組,在調(diào)用Exec
的時候,循環(huán)執(zhí)行middlewares
測試方案四
fmt.Println("===方案四 begin") ctx = context.Background() middleware4 := middlewarefor.NewMiddlewareManager( middlewarefor.RecoveryMW, middlewarefor.LoggerMW, middlewarefor.TimeCostMW, ) middleware4.Exec(ctx) fmt.Println("===方案四 end")
結(jié)果
===方案四 begin
2023/01/15 15:27:09 [RecoveryMW] befor
2023/01/15 15:27:09 [LoggerMW] befor
2023/01/15 15:27:09 [TimeCostMW] cost:0.000000s
2023/01/15 15:27:09 [LoggerMW] end
2023/01/15 15:27:09 [RecoveryMW] end
===方案四 end
總結(jié)
上面四種方案,都能實現(xiàn)middleware,好壞不予評價,你喜歡用哪種方式就用哪種。
本文及github上的代碼實現(xiàn)主要是用于學(xué)習(xí)和總結(jié),如果你想用某種方式到自己的項目中,直接復(fù)制過去就行,不建議引用本代碼倉庫。
github代碼倉庫:github.com/ZBIGBEAR/middleware
到此這篇關(guān)于四種Golang實現(xiàn)middleware框架的方式小結(jié)的文章就介紹到這了,更多相關(guān)Go middleware框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang使用sync.Once實現(xiàn)懶加載的用法和坑點詳解
這篇文章主要為大家詳細(xì)介紹了golang使用sync.Once實現(xiàn)懶加載的用法和坑點,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11詳解Go語言如何使用標(biāo)準(zhǔn)庫sort對切片進(jìn)行排序
Sort?標(biāo)準(zhǔn)庫提供了對基本數(shù)據(jù)類型的切片和自定義類型的切片進(jìn)行排序的函數(shù)。今天主要分享的內(nèi)容是使用?Go?標(biāo)準(zhǔn)庫?sort?對切片進(jìn)行排序,感興趣的可以了解一下2022-12-12go build 通過文件名后綴實現(xiàn)不同平臺的條件編譯操作
這篇文章主要介紹了go build 通過文件名后綴實現(xiàn)不同平臺的條件編譯操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12一文搞懂Golang文件操作增刪改查功能(基礎(chǔ)篇)
這篇文章主要介紹了一文搞懂Golang文件操作增刪改查功能(基礎(chǔ)篇),Golang 可以認(rèn)為是服務(wù)器開發(fā)語言發(fā)展的趨勢之一,特別是在流媒體服務(wù)器開發(fā)中,已經(jīng)占有一席之地,今天我們不聊特別深奧的機(jī)制和內(nèi)容,就來聊一聊 Golang 對于文件的基本操作2021-04-04Go語言中的基礎(chǔ)數(shù)據(jù)類型使用實例
這篇文章主要為大家介紹了Go中的基礎(chǔ)數(shù)據(jù)類型使用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04