当我们用 Go 语言编写较为复杂的服务时, 一个永恒的话题就是中间件. 这个话题在网上被 一遍 , 一遍 , 又一遍 地讨论着. 归根结底, 中间件应该允许我们:
拦截 ServeHTTP 调用, 并执行任意代码.
在持续的链上对请求 / 响应流做变更.
中断中间件链条, 或继续下一个中间件拦截器, 最终到真正的请求处理程序上面.
这些听起来跟 express.JS 中间件 很相似. 我们研究了 许多资料 https://lmgtfy.com/?q=golang+middleware+library , 发现了一些 已经存在的解决方案 https://github.com/urfave/negroni#handlers , 这些方案跟我们想要的非常吻合, 但他们要么有 不必要的额外功能 , 要么需求 不对我们的胃口 https://github.com/go-midway/midway#basic-design . 很明显, 我们可以基于 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 大概是这样:
- func HomeRouter(w http.ResponseWriter, r *http.Request) {
- // Handle your request
- }
- // ...
- // Some where when installing Hanlder
- chain := MiddlewareHandlerFunc(HomeRouter).
- Intercept(NewElapsedTimeInterceptor()).
- Intercept(NewRequestIdInterceptor())
- // Install it like regular HttpHandler
- mux.Path("/home").HandlerFunc(http.HandlerFunc(chain))
将 http.HandlerFunc 转换成 MiddlewareHandlerFunc , 并通过调用 Intercept 方法来安装拦截器. Intercept 的返回类型又是一个 MiddlewareHandlerFunc , 允许我们再次调用 Intercept .
如果使用 Intercept 架构, 非常值得注意的一点就是执行的先后顺序. 因为调用 chain(responseWriter, request) 实际上就是间接调用上一个拦截器, 并将其停止, 即从拦截器的尾部回到处理程序的首部. 这非常有意义, 因为正在拦截调用; 所以你应该在父程序前执行拦截.
简化
虽然逆向链式系统让抽象变得更清晰, 但大多时候都会有一个预编译拦截数组, 可以在不同的处理程序中被复用. 另外, 当我们把中间件定义为数组时, 更倾向按执行顺序去声明这些数组, 而不是终止的顺序. 我们把这个数组拦截器称作: MiddlewareChain . 我们想要的中间件链大概是这样:
注意, 这些中间件将按照链中出现的顺序调用, 即 RequestIDInterceptor 和 ElapsedTimeInterceptor . 这增加了代码的重用性和可读性.
实现
一旦设计好了抽象内容, 实现起来就会很顺利:
- /*
- Copyright (c) 2019 DoorDash
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
- */
- package middleware
- import "net/http"
- // MiddlewareInterceptor intercepts an HTTP handler invocation, it is passed both response writer and request
- // which after interception can be passed onto the handler function.
- type MiddlewareInterceptor func(http.ResponseWriter, *http.Request, http.HandlerFunc)
- // MiddlewareHandlerFunc builds on top of http.HandlerFunc, and exposes API to intercept with MiddlewareInterceptor.
- // This allows building complex long chains without complicated struct manipulation
- type MiddlewareHandlerFunc http.HandlerFunc
- // Intercept returns back a continuation that will call install middleware to intercept
- // the continuation call.
- func (cont MiddlewareHandlerFunc) Intercept(mw MiddlewareInterceptor) MiddlewareHandlerFunc {
- return func(writer http.ResponseWriter, request *http.Request) {
- mw(writer, request, http.HandlerFunc(cont))
- }
- }
- // MiddlewareChain is a collection of interceptors that will be invoked in there index order
- type MiddlewareChain []MiddlewareInterceptor
- // Handler allows hooking multiple middleware in single call.
- func (chain MiddlewareChain) Handler(handler http.HandlerFunc) http.Handler {
- curr := MiddlewareHandlerFunc(handler)
- for i := len(chain) - 1; i>= 0; i-- {
- mw := chain[i]
- curr = curr.Intercept(mw)
- }
- return http.HandlerFunc(curr)
- }
这样一来, 20 行 (不包括注释) 的代码, 就能构建一个很不错的中间件库. 在裸机上, 这几行抽象代码连贯性也是令人惊叹的. 这能让我们有条不紊地编写出流畅的 Http 中间件链. 希望这几行 Go 语言代码也能给你带来好的中间件体验.
来源: http://www.tuicool.com/articles/UnQ3iqm