中间件:能不能设计一个机制,将这些非业务逻辑代码抽象出来,封装好,提供接口给控制器使用。这个机制的实现,就是我们今天要讲的中间件。

代码的组织顺序很清晰,先预处理请求,再处理业务逻辑,最后处理返回值,你发现没有这种顺序,其实很符合设计模式中的装饰器模式。装饰器模式,顾名思义,就是在核心处理模块的外层增加一个又一个的装饰,类似洋葱。

现在,抽象出中间件的思路是不是就很清晰了,把核心业务逻辑先封装起来,然后一层一层添加装饰,最终让所有请求正序一层层通过装饰器,进入核心处理模块,再反序退出装饰器。原理就是这么简单,不难理解,我们接着看该如何实现。

使用函数嵌套方式实现中间件

对于一个超时控制器,我们可以定义一个中间件为 TimeoutHandler

中间件机制有两个问题:
1.中间件是循环嵌套的,当有多个中间件的时候,整个嵌套长度就会非常长,非常不优雅的,
比如:TimeoutHandler(LogHandler(recoveryHandler(UserLoginController)))
2.刚才的实现,只能为单个业务控制器设置中间件,不能批量设置。
开发的路由是具有同前缀分组功能的(IGroup),需要批量为某个分组设置一个超时时长。

使用 pipeline 思想改造中间件一层层嵌套不好用,如果我们将每个核心控制器所需要的中间件,使用一个数组链接(Chain)起来,形成一条流水线(Pipeline),就能完美解决这两个问题了。

中间件机制的本质就是装饰器模型,对核心的逻辑函数进行装饰、封装,所以一开始我们就使用函数嵌套的方式实现了中间件机制。但是实现之后,
我们发现函数嵌套的弊端:一是不优雅,二是无法批量设置中间件。
所以我们引入了 pipeline 的思想,将所有中间件做成一个链条,通过这个链条的调用,来实现中间件机制。最后,我们选了最基础的 Recovery 中间件演示如何具体实现,一方面作为中间件机制的示例,另一方面,也在功能上为我们的框架增强了健壮性。中间件机制是我们必须要掌握的机制,很多 Web 框架中都有这个逻辑。在架构层面,中间件机制就相当于,在每个请求的横切面统一注入了一个逻辑。这种统一处理的逻辑是非常有用的,比如统一打印日志、统一打点到统计系统、统一做权限登录验证等。

中间件的注册是有顺序的。比如最后才注册Recovery的话,pipeline中在Recovery前面的中间件如果panic了还是没法recover的
中间件需要显式调用ctx.Next(),如果写中间件时忘记了的话pipeline就断了。或许可以把中间件进一步拆成preRequest()和postRequest()两部分
* 中间件本质是装饰器模式,如果能像Java/Python里那样写装饰器标注的话可能意图更明显

1 是的,中间件注册有顺序,所以recovery是需要放在第一个。
2 这个是可以考虑,但是分成两个函数和现在用一个函数区别就是有的变量是没有办法写到两个函数中的,参数传递比较麻烦,比如我要打印请求时长,有一个变量,start_time, 需要在preRequest中写,postRequest中读,现在的方式就比较简单。分成两个函数就需要在postRequest中用参数传递之类的。 preRequest() ctxParam postRequest(ctxParam)
3 注释来表示装饰器,然后运行的时候读取注释来按需加载。这种方式也是行的,就是具体实现的时候需要读取“注释”来映射中间件,这里存在一个反射的逻辑,可能会降低效率。所以在golang中这种实现不多见。