Write in front
middleware is a commonly used form in general frameworks, such as web frameworks and rpc frameworks. Through middleware, it does some public things at traffic entrances and exits, including authentication, logging, burying points, statistics, current limiting, parameter processing, exception processing, etc.
It is often used in work, and it will also be encountered when reading web frameworks (gin, beego). Today I will summarize what implementation methods are available in middleware.
Solution 1: Array recursive call
package middleware import "context" // Processing functionstype Handler func(ctx , msg string) error // Plug-in typetype MiddleWareFunc func(ctx , 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) { = append(, middlewares...) } func (m *MiddlewareManager) Exec(ctx , msg string) error { handlerFunc := func(ctx , msg string, next Handler) error { return (ctx, msg) } = append(, handlerFunc) callChain := () return callChain(ctx, msg) } func (m *MiddlewareManager) mkCallChain( middlewares []MiddleWareFunc) Handler { if len(middlewares) <= 0 { return nil } return func(ctx , msg string) error { return middlewares[0](ctx, msg, (middlewares[1:])) } }
existMiddlewareManager
Define business processing functions in structurehandler
and plugin arraysmiddlewares
, executing the functionExec
Inside, the business processing function ishandler
Encapsulate it into a middleware and put it inmiddlewares
Afterwards, then recursively call the internal functionmkCallChain
. This internal functionmkCallChain
Returns a function that wraps all middleware layer by layer, and finallycallChain := ()
What you get is a call chain.
This code is a bit tangible and needs to be carefully savored.
Test plan 1
// Plan 1 ("===Plan 1 begin") m1 := (HandlerMsg) (, , ) if err := ((), "hello chain"); err != nil { panic(err) } ("===Plan 1 end")
result
===Scheme 1 begin
TimeCost before
FinlterMW begin
LoggerMW before
HandlerMsg: hello chain
LoggerMW end
FinlterMW end
TimeCostMW:cost 1000428754
===Scheme one end
Solution 2: Sequential implementation
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), } } // Execute the next middlewarefunc (m *MyContext) Next() error { if < -1 { += 1 return [](m) } return nil } // Terminate middlewarefunc (m *MyContext) Abort() { = } func (m *MyContext) Register(middlewares ...MiddleWareFunc) { = append(, middlewares...) = len() } func (m *MyContext) Exec() error { // Start execution from the first middleware return [0](m) }
This is the core code
type MyContext struct { middlewares []MiddleWareFunc idx int maxIdx int }
Define a context by yourself and place all middleware as an array in the context, executeExec()
When the first middleware is executed, the context is passed in. Other middlewaer callsNext()
Function to trigger the next middleware.
This method looks simple and easy to understand. This is how the middleware of the gin framework is implemented. This method is the author's summary and abstraction of the middleware of the gin framework.
Test plan 2
("===Plan 2 begin") m2 := () ( , , ) if err := (); err != nil { panic(err) } ("===Plan 2 end")
result
===Scheme 2 begin
TimeCost before
FinlterMW begin
LoggerMW before
LoggerMW end
FinlterMW end
TimeCostMW:cost 1000588399
===Scheme 2 end
Method 3: Chain call
package middlewarechain import "context" type Handler func(ctx ) error type MiddleWareFunc func(ctx , next Handler) Handler
This code logic is very simple. It is to pass the previous middlewear as next parameter to the current middleware to form a chain call.
Do you find it strange to see this definition? Why are there such a little code?
Yes, it has so little code. There is a saying that goes, "There is no such thing as peace of mind, but someone is carrying the burden on you, and life is never easy." If there are fewer codes in the definition, it will definitely be complicated when calling.
Let's take a look at the test cases below
("===Plan 3 begin") ctx := () m3 := (ctx, func(ctx ) error { PrintMsg("test") return nil }) m4 := (ctx, m3) m5 := (ctx, m4) if err := m5(ctx); err != nil { (err) } ("===Plan 3 end")
result
===Scheme 3 begin
LoggerMW before
FinlterMW begin
TimeCost before
PrintMsg:test
TimeCostMW:cost 6130
FinlterMW end
LoggerMW end
===Scheme 3 end
It can be seen that when defining middlewear, the previous middleeeware must be passed into the current middleeeware definition. Compared with other solutions, it actually removes the middleware registration, and there is no place to maintain all middleware.
Solution 4: For loop implementation
package middlewarefor import "context" type Handler func(ctx ) 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) { = append(, middlewares...) } func (m *MiddlewareManager) Exec(ctx ) error { handler := defaultHandler for i := range { handler = [len()-i-1](handler) } return handler(ctx) } func defaultHandler(ctx ) error { return nil }
It is very similar to the solution, both of which define oneMiddlewareManager
Structure, internal maintenancemiddlewares
Array, invokedExec
When the loop is executedmiddlewares
Test plan 4
("===Plan 4 begin") ctx = () middleware4 := ( , , , ) (ctx) ("===Plan 4 end")
result
===Scheme 4 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
===Scheme 4 end
Summarize
The above four solutions can all implement middleware. No evaluation is given, and you can use the same method you like.
The code implementation on this article and github is mainly used for learning and summary. If you want to use some way to your project, just copy it directly. It is not recommended to reference this code repository.
Github code repository:/ZBIGBEAR/middleware
This is the end of this article about the four ways to implement the middleware framework in Golang. For more related Go middleware framework content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!