AICon 上海站|90%日程已就绪,解锁Al未来! 了解详情
写点什么

如何用 Go 语言写出好用的 Http 中间件?

  • 2019-08-23
  • 本文字数:2507 字

    阅读完需:约 8 分钟

如何用 Go 语言写出好用的 Http 中间件?

当我们用 Go 语言编写较为复杂的服务时,一个永恒的话题就是中间件。这个话题在网上被一遍一遍又一遍地讨论着。归根结底,中间件应该允许我们:


  1. 拦截 ServeHTTP 调用,并执行任意代码。

  2. 在持续的链上对请求/响应流做变更。

  3. 中断中间件链条,或继续下一个中间件拦截器,最终到真正的请求处理程序上面。


这些听起来跟express.js 中间件很相似。我们研究了许多资料,发现了一些已经存在的解决方案,这些方案跟我们想要的非常吻合,但他们要么有不必要的额外功能,要么需求不对我们的胃口。很明显,我们可以基于express.js编写中间件,安装这个干净整洁的组件之后,20 行以下代码就可以实现一个轻量级的 API

抽象

设计抽象时,首先我们要考虑的就是,如何写中间件函数(从现在起,可以称它为拦截器)。


答案很明显:


它们看起来就像 http.HandlerFunc,带一个额外的参数 next,程序进行下一步的处理。这使得任何人都可以像编写简单函数一样,类似 http.HandlerFunc 这样来编写拦截器,做他们想做的,并能按照他们的意愿传递控制权。


接下来我们要考虑的就是,如何将拦截器挂到http.Handlerhttp.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 Hanlderchain := MiddlewareHandlerFunc(HomeRouter). Intercept(NewElapsedTimeInterceptor()). Intercept(NewRequestIdInterceptor())
// Install it like regular HttpHandlermux.Path("/home").HandlerFunc(http.HandlerFunc(chain))
复制代码


http.HandlerFunc转换成MiddlewareHandlerFunc,并通过调用Intercept方法来安装拦截器。Intercept的返回类型又是一个MiddlewareHandlerFunc,允许我们再次调用Intercept


如果使用Intercept架构,非常值得注意的一点就是执行的先后顺序。因为调用chain(responseWriter, request)实际上就是间接调用上一个拦截器,并将其停止,即从拦截器的尾部回到处理程序的首部。这非常有意义,因为正在拦截调用;所以你应该在父程序前执行拦截。

简化

虽然逆向链式系统让抽象变得更清晰,但大多时候都会有一个预编译拦截数组,可以在不同的处理程序中被复用。另外,当我们把中间件定义为数组时,更倾向按执行顺序去声明这些数组,而不是终止的顺序。我们把这个数组拦截器称作:MiddlewareChain。我们想要的中间件链大概是这样:


注意,这些中间件将按照链中出现的顺序调用,即RequestIDInterceptorElapsedTimeInterceptor。这增加了代码的重用性和可读性。

实现

一旦设计好了抽象内容,实现起来就会很顺利:


/*Copyright (c) 2019 DoorDashPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, 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 THESOFTWARE.*/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 manipulationtype 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 ordertype 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 语言代码也能给你带来好的中间件体验。


原文链接:


https://doordash.engineering/2019/07/22/writing-delightful-http-middlewares-in-go


2019-08-23 09:007207

评论 1 条评论

发布
用户头像
代码是不完整的,照搬都可以漏。。。
2019-08-27 10:46
回复
没有更多了
发现更多内容

tidb变更大小写敏感问题的总结

TiDB 社区干货传送门

集群管理 故障排查/诊断

# 文盘Rust -- tokio绑定cpu实践

TiDB 社区干货传送门

开发语言

海外直播源码技术文字聊天功能的配置

山东布谷科技

软件开发、 区块链二维码溯源系统开发 源码搭建 直播源码 海外直播源码

软件测试/测试开发丨Allure2报告中添加附件-html、视频

测试人

程序员 软件测试 自动化测试 测试开发 Allure

定时任务原理方案综述 | 京东云技术团队

京东科技开发者

定时任务 线程 中间件 分布式定时任务 企业号 6 月 PK 榜

科兴未来|第四届中国·盐城创新创业大赛

科兴未来News

小程序容器与PWA有什么不同?

没有用户名丶

天翼云SD-WAN解决方案直播

天翼云开发者社区

云计算 服务器 云服务

火热报名 |【崖山论“见”】第3期,如何让SQL速度飞起来

YashanDB

数据库 sql 技术沙龙 优化器 技术干货

为什么 Serverless 能提升资源利用率?

阿里巴巴云原生

阿里云 Serverless 云原生

【TiDB Future App Hackathon 2023 】TiDB 首届全球黑客马拉松,开发者的狂欢夏日盛会!快来一起 Coding 吧!

TiDB 社区干货传送门

我对混沌工程的理解

老张

混沌工程

全球LED租赁屏市场

Dylan

LED 市场 规模化 全球 技术指南

想给业务松松绑,又怕数据泄漏隐患? 动态脱敏了解一下!

极盾科技

数据安全

FreeRTOS使用 — 合理使用内存 “ 任务中创建任务 ”

矜辰所致

内存管理 FreeRTOS 任务创建 6 月 优质更文活动

软件测试/测试开发丨Allure2报告中添加附件-日志

测试人

程序员 软件测试 测试开发 Allure

文件管理开发指南全新登场,快来了解如何访问各类文件

HarmonyOS开发者

HarmonyOS

华秋干货铺 | HDMI接口需注意的PCB可制造性设计问题

华秋电子

企业级低代码平台:企业IT部门的得力助手

优秀

低代码 快速开发 企业级低代码

人工智能正在使代码普惠化!未来五年人人都能编程

FN0

深度解析Java程序员从入行到被裁全过程

Java全栈架构师

程序人生 后端 架构师 java程序员 java面试

SAP ABAP SM50 事务码的另类用途 - ABAP工作进程对数据库表读取操作的检测

汪子熙

SAP abap Netweaver 思爱普 6 月 优质更文活动

【零售电商系列】走进亚马逊(二)

小诚信驿站

6 月 优质更文活动

对线面试官-为什么要使用MQ

派大星

Java 面试题

v7.1.0 Resource Control 功能测试

TiDB 社区干货传送门

新版本/特性解读 7.x 实践

活动预告|6月15 日 Apache Paimon Meetup,深入解读 Apache Paimon 0.4.0 !

Apache Flink

大数据 flink 社区活动

【架构设计】【问题分析】记一次调用内部es服务超时问题

如果晴天

架构设计 问题定位 问题分析 日志平台 失败策略

第十一届“创业江苏”科技创业大赛正式启动

科兴未来News

v7.1 LTS Resource Control 试用

TiDB 社区干货传送门

新版本/特性解读 7.x 实践

瓴羊Quick BI新增多项可视化分析的模块,为数据驱动决策带来更多可能

流量猫猫头

图数据库在通信行业有哪些应用?

悦数图数据库

如何用 Go 语言写出好用的 Http 中间件?_编程语言_Zohaib Sibte Hassan_InfoQ精选文章