四種Golang實(shí)現(xiàn)middleware框架的方式小結(jié)
寫(xiě)在前面
middleware是一般框架里面常用的形式,比如web框架、rpc框架,通過(guò)middleware在流量入口和出口做一些公共事情,包括鑒權(quán)、日志、埋點(diǎn)、統(tǒng)計(jì)、限流、參數(shù)處理、異常處理等等。
在工作中經(jīng)常會(huì)用到,在閱讀web框架(gin,beego)的時(shí)候也會(huì)遇到,今天總結(jié)一下middleware有哪些實(shí)現(xiàn)方式。
方案一:數(shù)組遞歸調(diào)用
package middleware
import "context"
// 處理函數(shù)
type Handler func(ctx context.Context,
msg string) error
// 插件類(lèi)型
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封裝成一個(gè)middleware放到middlewares后面,然后遞歸調(diào)用內(nèi)部函數(shù)mkCallChain。這個(gè)內(nèi)部函數(shù)mkCallChain返回的是一個(gè)函數(shù),將所有middleware一層一層包裹起來(lái),最終callChain := m.mkCallChain(m.middlewares)得到的是一個(gè)調(diào)用鏈。
這段代碼有點(diǎn)繞,需要細(xì)品。
測(cè)試方案一
// 方案一
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í)行下一個(gè)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 {
// 從第一個(gè)middleware開(kāi)始執(zhí)行
return m.middlewares[0](m)
}
核心代碼是這段
type MyContext struct {
middlewares []MiddleWareFunc
idx int
maxIdx int
}
自己定義一個(gè)context將所有middleware作為數(shù)組放在context中,執(zhí)行Exec()的時(shí)候就執(zhí)行第一個(gè)middleware,并且將context傳進(jìn)去。其他middlewaer中通過(guò)調(diào)用Next()函數(shù)來(lái)觸發(fā)下一個(gè)middleware。
這種方式看起來(lái)邏輯簡(jiǎn)單,容易理解。gin框架的middleware就是這樣實(shí)現(xiàn)的。這個(gè)方式是作者對(duì)gin框架的middleware的總結(jié)和抽象。
測(cè)試方案二
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
這段代碼邏輯很簡(jiǎn)單,它就是將上一個(gè)middleweare作為next參數(shù)傳到當(dāng)前middleware,形成鏈?zhǔn)秸{(diào)用。
看到這個(gè)定義你會(huì)不會(huì)覺(jué)得很奇怪,怎么這么點(diǎn)代碼?
是的,它的代碼就是這么少。有句話說(shuō)的好“哪有什么歲月靜好,不過(guò)是有人替你負(fù)重前行,生活從來(lái)都不容易”,定義的地方代碼少了,調(diào)用的時(shí)候肯定就復(fù)雜了。
下面看看測(cè)試用例
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
可見(jiàn),在定義middleweare的時(shí)候,要將上一個(gè)middleeware傳入當(dāng)前middleeware的定義。跟其他幾種方案相比,其實(shí)它就是將middleware的注冊(cè)去掉了,沒(méi)有地方維護(hù)所有的middleware。
方案四:for循環(huán)實(shí)現(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
}它跟方案一很像,都是定義一個(gè)MiddlewareManager結(jié)構(gòu)體,內(nèi)部維護(hù)一個(gè)middlewares數(shù)組,在調(diào)用Exec的時(shí)候,循環(huán)執(zhí)行middlewares
測(cè)試方案四
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é)
上面四種方案,都能實(shí)現(xiàn)middleware,好壞不予評(píng)價(jià),你喜歡用哪種方式就用哪種。
本文及github上的代碼實(shí)現(xiàn)主要是用于學(xué)習(xí)和總結(jié),如果你想用某種方式到自己的項(xiàng)目中,直接復(fù)制過(guò)去就行,不建議引用本代碼倉(cāng)庫(kù)。
github代碼倉(cāng)庫(kù):github.com/ZBIGBEAR/middleware
到此這篇關(guān)于四種Golang實(shí)現(xiàn)middleware框架的方式小結(jié)的文章就介紹到這了,更多相關(guān)Go middleware框架內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言O(shè)RM包中使用worm構(gòu)造查詢條件的實(shí)例詳解
worm是一款方便易用的Go語(yǔ)言O(shè)RM庫(kù)。worm支Model方式(持結(jié)構(gòu)體字段映射)、原生SQL以及SQLBuilder三種模式來(lái)操作數(shù)據(jù)庫(kù),并且Model方式、原生SQL以及SQLBuilder可混合使用,本文通過(guò)一些例子來(lái)說(shuō)明如何使用worm來(lái)構(gòu)造查詢條件,感興趣的朋友一起看看吧2022-07-07
使用Go語(yǔ)言實(shí)現(xiàn)敏感詞過(guò)濾功能
敏感詞過(guò)濾,算是一個(gè)比較常見(jiàn)的功能,尤其是在內(nèi)容、社交類(lèi)應(yīng)用中更是如此,本文介紹如何使用Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的敏感詞過(guò)濾功能,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
Golang使用http協(xié)議實(shí)現(xiàn)心跳檢測(cè)程序過(guò)程詳解
這篇文章主要介紹了Golang使用http協(xié)議實(shí)現(xiàn)心跳檢測(cè)程序過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-03-03
Go操作Kafka的實(shí)現(xiàn)示例(kafka-go)
本文介紹了使用kafka-go庫(kù)在Go語(yǔ)言中與Kafka進(jìn)行交互,涵蓋了kafka-go的安裝、API使用、消息發(fā)送與消費(fèi)方法,以及如何通過(guò)DockerCompose快速搭建Kafka環(huán)境,文章還比較了其他兩個(gè)常用的Kafka客戶端庫(kù),感興趣的可以了解一下2024-10-10
Go?channel實(shí)現(xiàn)批量讀取數(shù)據(jù)
Go中的?channel?其實(shí)并沒(méi)有提供批量讀取數(shù)據(jù)的方法,需要我們自己實(shí)現(xiàn)一個(gè),使用本文就來(lái)為大家大家介紹一下如何通過(guò)Go?channel實(shí)現(xiàn)批量讀取數(shù)據(jù)吧2023-12-12
golang websocket 服務(wù)端的實(shí)現(xiàn)
這篇文章主要介紹了golang websocket 服務(wù)端的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Go語(yǔ)言中的os.Truncate函數(shù)用法解讀
這篇文章主要介紹了Go語(yǔ)言中的os.Truncate函數(shù)用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-07-07

