四种Golang实现middleware框架的方式小结
作者:Alex
写在前面
middleware是一般框架里面常用的形式,比如web框架、rpc框架,通过middleware在流量入口和出口做一些公共事情,包括鉴权、日志、埋点、统计、限流、参数处理、异常处理等等。
在工作中经常会用到,在阅读web框架(gin,beego)的时候也会遇到,今天总结一下middleware有哪些实现方式。
方案一:数组递归调用
package middleware import "context" // 处理函数 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
结构体中定义业务处理函数handler
和插件数组middlewares
,在执行函数Exec
里面,将业务处理函数handler
封装成一个middleware放到middlewares
后面,然后递归调用内部函数mkCallChain
。这个内部函数mkCallChain
返回的是一个函数,将所有middleware一层一层包裹起来,最终callChain := m.mkCallChain(m.middlewares)
得到的是一个调用链。
这段代码有点绕,需要细品。
测试方案一
// 方案一 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")
结果
===方案一 begin
TimeCost before
FinlterMW begin
LoggerMW before
HandlerMsg: hello chain
LoggerMW end
FinlterMW end
TimeCostMW:cost 1000428754
===方案一 end
方案二:顺序实现
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), } } // 执行下一个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开始执行 return m.middlewares[0](m) }
核心代码是这段
type MyContext struct { middlewares []MiddleWareFunc idx int maxIdx int }
自己定义一个context将所有middleware作为数组放在context中,执行Exec()
的时候就执行第一个middleware,并且将context传进去。其他middlewaer中通过调用Next()
函数来触发下一个middleware。
这种方式看起来逻辑简单,容易理解。gin框架的middleware就是这样实现的。这个方式是作者对gin框架的middleware的总结和抽象。
测试方案二
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")
结果
===方案二 begin
TimeCost before
FinlterMW begin
LoggerMW before
LoggerMW end
FinlterMW end
TimeCostMW:cost 1000588399
===方案二 end
方式三:链式调用
package middlewarechain import "context" type Handler func(ctx context.Context) error type MiddleWareFunc func(ctx context.Context, next Handler) Handler
这段代码逻辑很简单,它就是将上一个middleweare作为next参数传到当前middleware,形成链式调用。
看到这个定义你会不会觉得很奇怪,怎么这么点代码?
是的,它的代码就是这么少。有句话说的好“哪有什么岁月静好,不过是有人替你负重前行,生活从来都不容易”,定义的地方代码少了,调用的时候肯定就复杂了。
下面看看测试用例
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")
结果
===方案三 begin
LoggerMW before
FinlterMW begin
TimeCost before
PrintMsg:test
TimeCostMW:cost 6130
FinlterMW end
LoggerMW end
===方案三 end
可见,在定义middleweare的时候,要将上一个middleeware传入当前middleeware的定义。跟其他几种方案相比,其实它就是将middleware的注册去掉了,没有地方维护所有的middleware。
方案四:for循环实现
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
结构体,内部维护一个middlewares
数组,在调用Exec
的时候,循环执行middlewares
测试方案四
fmt.Println("===方案四 begin") ctx = context.Background() middleware4 := middlewarefor.NewMiddlewareManager( middlewarefor.RecoveryMW, middlewarefor.LoggerMW, middlewarefor.TimeCostMW, ) middleware4.Exec(ctx) fmt.Println("===方案四 end")
结果
===方案四 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
总结
上面四种方案,都能实现middleware,好坏不予评价,你喜欢用哪种方式就用哪种。
本文及github上的代码实现主要是用于学习和总结,如果你想用某种方式到自己的项目中,直接复制过去就行,不建议引用本代码仓库。
github代码仓库:github.com/ZBIGBEAR/middleware
到此这篇关于四种Golang实现middleware框架的方式小结的文章就介绍到这了,更多相关Go middleware框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!