四种Golang实现middleware框架的方式小结

 更新时间:2024年03月24日 08:26:58   作者:Alex  
middleware是一般框架里面常用的形式,比如web框架、rpc框架等,本文为大家详细介绍了四种实现middleawre的方式,感兴趣的可以了解一下

写在前面

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框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go内置零值标识符zero

    Go内置零值标识符zero

    大家学习和使用 Go 语言时,有一个神奇的概念:零值(zero-values),所以本文想给大家分享一个关于零值的新提案,目测已经八九不离十了
    2023-08-08
  • Go语言ORM包中使用worm构造查询条件的实例详解

    Go语言ORM包中使用worm构造查询条件的实例详解

    worm是一款方便易用的Go语言ORM库。worm支Model方式(持结构体字段映射)、原生SQL以及SQLBuilder三种模式来操作数据库,并且Model方式、原生SQL以及SQLBuilder可混合使用,本文通过一些例子来说明如何使用worm来构造查询条件,感兴趣的朋友一起看看吧
    2022-07-07
  • GO语言短变量声明的实现示例

    GO语言短变量声明的实现示例

    在Go语言中,短变量声明是一种简洁的变量声明方式,使用 := 运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下
    2025-08-08
  • 使用Go语言实现敏感词过滤功能

    使用Go语言实现敏感词过滤功能

    敏感词过滤,算是一个比较常见的功能,尤其是在内容、社交类应用中更是如此,本文介绍如何使用Go语言实现简单的敏感词过滤功能,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • golang防止内存逃逸的方法小结

    golang防止内存逃逸的方法小结

    在Go语言中,内存逃逸是指在函数中分配的变量在函数结束后仍然被引用,防止内存逃逸有助于提高程序的性能,因为栈上分配的内存可以更快地被回收,本文给大家总结了一些防止内存逃逸的方法,需要的朋友可以参考下
    2024-02-02
  • Golang使用http协议实现心跳检测程序过程详解

    Golang使用http协议实现心跳检测程序过程详解

    这篇文章主要介绍了Golang使用http协议实现心跳检测程序过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-03-03
  • Go操作Kafka的实现示例(kafka-go)

    Go操作Kafka的实现示例(kafka-go)

    本文介绍了使用kafka-go库在Go语言中与Kafka进行交互,涵盖了kafka-go的安装、API使用、消息发送与消费方法,以及如何通过DockerCompose快速搭建Kafka环境,文章还比较了其他两个常用的Kafka客户端库,感兴趣的可以了解一下
    2024-10-10
  • Go channel实现批量读取数据

    Go channel实现批量读取数据

    Go中的 channel 其实并没有提供批量读取数据的方法,需要我们自己实现一个,使用本文就来为大家大家介绍一下如何通过Go channel实现批量读取数据吧
    2023-12-12
  • golang websocket 服务端的实现

    golang websocket 服务端的实现

    这篇文章主要介绍了golang websocket 服务端的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Go语言中的os.Truncate函数用法解读

    Go语言中的os.Truncate函数用法解读

    这篇文章主要介绍了Go语言中的os.Truncate函数用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-07-07

最新评论