用 20 行代码写出清晰易用的 Go 中间件 API

新闻 前端
在使用 Go 编写复杂的服务时,您将遇到一个典型的主题是中间件。这个话题在网上被讨论了一次又一次。

 [[331358]]

在使用 Go 编写复杂的服务时,您将遇到一个典型的主题是中间件。这个话题在网上被讨论了一次又一次。本质上,中间件允许我们做了如下事情:

  • 拦截 ServeHTTP 调用,执行任意代码
  • 对调用链(Continuation Chain) 上的请求/响应流进行更改
  • 打断中间件链,或继续下一个中间件拦截器并最终到达真正的请求处理器

这些与 express.js 中间件所做的工作非常类似。我们探索了各种库,找到了接近我们想要的现有解决方案,但是他们要么有不要的额外内容,要么不符合我们的品位。显然,我们可以在 express.js 中间件的启发下,写出 20 行代码以下的更清晰的易用的 API(Installation API)

抽象

在设计抽象时,我们首先设想如何编写中间件函数(下文开始称为拦截器),答案非常明显:

  1. func NewElapsedTimeInterceptor() MiddlewareInterceptor { 
  2. return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 
  3. startTime := time.Now() 
  4. defer func() { 
  5. endTime := time.Now() 
  6. elapsed := endTime.Sub(startTime) 
  7. // 记录时间消耗 
  8. }() 
  9.  
  10. next(w, r) 
  11.  
  12. func NewRequestIdInterceptor() MiddlewareInterceptor { 
  13. return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 
  14. if r.Headers.Get("X-Request-Id") == "" { 
  15. r.Headers.Set("X-Request-Id", generateRequestId()) 
  16.  
  17. next(w, r) 

它们看起来就像 http.HandlerFunc,但有一个额外的参数 next,该函数(参数)会继续处理请求链。这将允许任何人像编写类似 http.HandlerFunc 的简单函数一样写拦截器,它可以拦截调用,执行所需操作,并在需要时传递控制权。

接下来,我们设想如何将这些拦截器连接到 http.Handler 或 http.HandlerFunc 中。为此,首先要定义 MiddlewareHandlerFunc,它只是 http.HandlerFunc 的一种类型。(type MiddlewareHandlerFunc http.HandlerFunc)。这将允许我们在 http.HandlerFunc 栈上之上构建一个更好的 API。现在给定一个 http.HandlerFunc 我们希望我们的链式 API 看起来像这样:

  1. func HomeRouter(w http.ResponseWriter, r *http.Request) { 
  2. // 处理请求 
  3.  
  4. // ... 
  5. // 在程序某处注册 Hanlder 
  6. chain := MiddlewareHandlerFunc(HomeRouter). 
  7. Intercept(NewElapsedTimeInterceptor()). 
  8. Intercept(NewRequestIdInterceptor()) 
  9.  
  10. // 像普通般注册 HttpHandler 
  11. mux.Path("/home").HandlerFunc(http.HandlerFunc(chain)) 

将 http.HandlerFunc 传递到 MiddlewareHandlerFunc,然后调用 Intercept 方法注册我们的 Interceptor。Interceptor 的返回类型还是 MiddlewareHandlerFunc,它允许我们再次调用 Intercept。

使用 Intercept 组合需要注意的一件重要事情是执行的顺序。由于 chain(responseWriter, request)是间接调用最后一个拦截器,拦截器的执行是反向的,即它从尾部的拦截器一直返回到头部的处理程序。这很有道理,因为你在拦截调用时,拦截器应该要在真正的请求处理器之前执行。

简化

虽然这种反向链系统使抽象更加流畅,但事实证明,大多数情况下 s 我们有一个预编译的拦截器数组,能够在不同的 handlers 之间重用。同样,当我们将中间件链定义为数组时,我们自然更愿意以它们执行顺序声明它们(而不是相反的顺序)。让我们将这个数组拦截器称为中间件链。我们希望我们的中间件链看起来有点像:

  1. // 调用链或中间件可以按下标的顺序执行 
  2. middlewareChain := MiddlewareChain{ 
  3. NewRequestIdInterceptor(), 
  4. NewElapsedTimeInterceptor(), 
  5.  
  6. // 调用所有以 HomeRouter 结尾的中间件 
  7. mux.Path("/home").Handler(middlewareChain.Handler(HomeRouter)) 

实现

一旦我们设计好抽象的概念,实现就显得简单多了

  1. package middleware 
  2.  
  3. import "net/http" 
  4.  
  5. // MiddlewareInterceptor intercepts an HTTP handler invocation, it is passed both response writer and request 
  6. // which after interception can be passed onto the handler function. 
  7. type MiddlewareInterceptor func(http.ResponseWriter, *http.Request, http.HandlerFunc) 
  8.  
  9. // MiddlewareHandlerFunc builds on top of http.HandlerFunc, and exposes API to intercept with MiddlewareInterceptor. 
  10. // This allows building complex long chains without complicated struct manipulation 
  11. type MiddlewareHandlerFunc http.HandlerFunc 
  12.  
  13.  
  14. // Intercept returns back a continuation that will call install middleware to intercept 
  15. // the continuation call. 
  16. func (cont MiddlewareHandlerFunc) Intercept(mw MiddlewareInterceptor) MiddlewareHandlerFunc { 
  17. return func(writer http.ResponseWriter, request *http.Request) { 
  18. mw(writer, request, http.HandlerFunc(cont)) 
  19.  
  20. // MiddlewareChain is a collection of interceptors that will be invoked in there index order 
  21. type MiddlewareChain []MiddlewareInterceptor 
  22.  
  23. // Handler allows hooking multiple middleware in single call. 
  24. func (chain MiddlewareChain) Handler(handler http.HandlerFunc) http.Handler { 
  25. curr := MiddlewareHandlerFunc(handler) 
  26. for i := len(chain) - 1; i >= 0; i-- { 
  27. mw := chain[i] 
  28. curr = curr.Intercept(mw) 
  29.  
  30. return http.HandlerFunc(curr) 

因此,在不到 20 行代码(不包括注释)的情况下,我们就能够构建一个很好的中间件库。它几乎是简简单单的,但是这几行连贯的抽象实在是太棒了。它使我们能够毫不费力地编写一些漂亮的中间件链。希望这几行代码也能激发您的中间件体验。

责任编辑:张燕妮 来源: Go语言中文网
相关推荐

2013-12-12 10:55:21

2023-11-27 07:10:06

日志中间件

2015-12-21 14:56:12

Go语言Http网络协议

2021-10-06 19:03:35

Go中间件Middleware

2022-11-18 07:54:02

Go中间件项目

2021-04-29 21:54:44

Python代码语言

2016-11-11 21:00:46

中间件

2023-12-06 07:14:28

前端API中间件

2011-05-24 15:10:48

2021-02-11 08:21:02

中间件开发CRUD

2024-02-06 14:05:00

Go中间件框架

2017-12-11 13:30:49

Go语言数据库中间件

2018-07-29 12:27:30

云中间件云计算API

2018-02-01 10:19:22

中间件服务器系统

2015-02-07 21:52:45

PaaS中间件

2018-05-02 16:23:24

中间件RPC容器

2013-03-13 10:37:22

中间件Windows

2021-06-15 10:01:02

应用系统软件

2012-11-30 10:21:46

移动中间件

2023-06-29 10:10:06

Rocket MQ消息中间件
点赞
收藏

51CTO技术栈公众号