当我们用 Go 语言编写较为复杂的服务时,一个永恒的话题就是中间件。这个话题在网上被一遍、一遍、又一遍地讨论着。归根结底,中间件应该允许我们:
拦截 ServeHTTP 调用,并执行任意代码。
在持续的链上对请求/响应流做变更。
中断中间件链条,或继续下一个中间件拦截器,最终到真正的请求处理程序上面。
这些听起来跟express.js 中间件很相似。我们研究了许多资料,发现了一些已经存在的解决方案,这些方案跟我们想要的非常吻合,但他们要么有不必要的额外功能,要么需求不对我们的胃口。很明显,我们可以基于express.js编写中间件,安装这个干净整洁的组件之后,20 行以下代码就可以实现一个轻量级的 API
抽象
设计抽象时,首先我们要考虑的就是,如何写中间件函数(从现在起,可以称它为拦截器)。
答案很明显:
它们看起来就像 http.HandlerFunc
,带一个额外的参数 next,程序进行下一步的处理。这使得任何人都可以像编写简单函数一样,类似 http.HandlerFunc 这样来编写拦截器,做他们想做的,并能按照他们的意愿传递控制权。
接下来我们要考虑的就是,如何将拦截器挂到http.Handler
或http.HandlerFunc
上。想要达成这个目标,首先要做的就是定义MiddlewareHandlerFunc
,简单来说就是http.HandlerFunc
的一种类型(例如,type MiddlewareHandlerFunc http.HandlerFunc
)。这将让我们在http.HandlerFunc
的基础之上,构建一个更好的 API。现在给出一个http.HandlerFunc
,我们想要的链式 API 大概是这样:
将http.HandlerFunc
转换成MiddlewareHandlerFunc
,并通过调用Intercept
方法来安装拦截器。Intercept
的返回类型又是一个MiddlewareHandlerFunc
,允许我们再次调用Intercept
。
如果使用Intercept
架构,非常值得注意的一点就是执行的先后顺序。因为调用chain(responseWriter, request)
实际上就是间接调用上一个拦截器,并将其停止,即从拦截器的尾部回到处理程序的首部。这非常有意义,因为正在拦截调用;所以你应该在父程序前执行拦截。
简化
虽然逆向链式系统让抽象变得更清晰,但大多时候都会有一个预编译拦截数组,可以在不同的处理程序中被复用。另外,当我们把中间件定义为数组时,更倾向按执行顺序去声明这些数组,而不是终止的顺序。我们把这个数组拦截器称作:MiddlewareChain
。我们想要的中间件链大概是这样:
注意,这些中间件将按照链中出现的顺序调用,即RequestIDInterceptor
和ElapsedTimeInterceptor
。这增加了代码的重用性和可读性。
实现
一旦设计好了抽象内容,实现起来就会很顺利:
这样一来,20 行(不包括注释)的代码,就能构建一个很不错的中间件库。在裸机上,这几行抽象代码连贯性也是令人惊叹的。这能让我们有条不紊地编写出流畅的 Http 中间件链。希望这几行 Go 语言代码也能给你带来好的中间件体验。
原文链接:
https://doordash.engineering/2019/07/22/writing-delightful-http-middlewares-in-go
评论 1 条评论