在 Go 中编写令人愉快的 HTTP 中间件基础入门_go新手入门

在使用 Go 编写复杂的服务时,您将遇到一个典型的主题是中间件。这个话题在网上被讨论了一次又一次。本质上,中间件允许我们做了如下事情:ServeHTTP
这些与 express.js 中间件所做的工作非常类似。我们探索了各种库,找到了接近我们想要的现有解决方案,但是他们要么有不要的额外内容,要么不符合我们的品位。显然,我们可以在 express.js 中间件

在 Go 中编写令人愉快的 HTTP 中间件基础入门

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

在 Go 中编写令人愉快的 HTTP 中间件基础入门_go新手入门

ServeHTTP

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

抽象

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

func NewElapsedTimeInterceptor() MiddlewareInterceptor {
    return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        startTime := time.Now()
        defer func() {
            endTime := time.Now()
            elapsed := endTime.Sub(startTime)
            // 记录时间消耗
        }()

        next(w, r)
    }
}

func NewRequestIdInterceptor() MiddlewareInterceptor {
    return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        if r.Headers.Get("X-Request-Id") == "" {
            r.Headers.Set("X-Request-Id", generateRequestId())
        }

        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 看起来像这样:

func HomeRouter(w http.ResponseWriter, r *http.Request) {
	// 处理请求
}

// ...
// 在程序某处注册 Hanlder
chain := MiddlewareHandlerFunc(HomeRouter).
  Intercept(NewElapsedTimeInterceptor()).
  Intercept(NewRequestIdInterceptor())

// 像普通般注册 HttpHandler
mux.Path("/home").HandlerFunc(http.HandlerFunc(chain))

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

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

简化

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

// 调用链或中间件可以按下标的顺序执行
middlewareChain := MiddlewareChain{
  NewRequestIdInterceptor(),
  NewElapsedTimeInterceptor(),
}

// 调用所有以 HomeRouter 结尾的中间件
mux.Path("/home").Handler(middlewareChain.Handler(HomeRouter))

实现

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

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

原文 https://studygolang.com/articles/25913

海计划公众号
(0)
上一篇 2020/03/22 01:26
下一篇 2020/03/22 01:26

您可能感兴趣的内容

  • Lebab入门指南_用于将你的ES5代码转成ES6/ES7

    Lebab入门指南 官方网址:https://uniibu.github.io/lebab-ce/ GitHub:https://github.com/lebab/lebab 简介…

    2020/03/07
  • 用 CSS background 实现刻度线的呈现小白入门_css使用指南

    有的时候,我们需要在网页中的进度条或某种度量计上呈现一条条的刻度线。例如这种:简单的实现方式,大致有两种:一是用图片做背景,横向平铺线条图片;二是给每一块刻度区域平铺一个元素,然后用边线实现。身为一个“环保主义者”,这两种方式都不能让我满意。在看了 Lea Verou 的 CSS SECRETS 后,我受到了启发——可以用渐变背景的方式去实现。原理很简单。最

    2020/04/03
  • dash使用攻略_一个API文档浏览器

    dash使用攻略 官方网址:https://kapeli.com/dash 简介描述:一个API文档浏览器 Dash是Mac OS平台上的软件,基本免费。是一个API浏览器和代码片…

    2020/03/06
  • 使用base64编码在页面嵌入图片基础教程_base64指南教程

    因为页面中插入一个图片都要写明图片的路径——相对路径或者绝对路径。而除了具体的网站图片的图片地址,如果是在自己电脑文件夹里的图片,当我们的HTML文件在别人电脑上打开的时候图片则由于地址不对或者没有将图片一起发过去而导致图片无法显示。为了便于显示,我今天试了一下将图片转换为base64编码的方法。注:在上面的Data URI中,data表示取得数据的协定名称

    2020/03/24
  • vant使用帮助_轻量、可靠的移动端 Vue 组件库

    vant使用帮助 官方网址:https://youzan.github.io/vant GitHub:https://github.com/youzan/vant 简介描述:轻量、…

    2020/03/07
  • javascript如何判断元素是否存在?零基础入门_元素菜鸟攻略

    JavaScript中可以通过theForm、theForm.periodPerMonth、getElementById等方法判断元素是否存在。也可以使用Jquery的length属性来判断。javascript判断元素是否存在的方法示例:判断表单元素是否存在(一)if(“periodPerMonth” in document.theForm)
    {
    re

    2020/03/24
  • Vue刷新当前页面的3种实现基础指南_刷新教程视频

    前些日子项目中突然接到了一个需求,要求点击当前路由刷新页面,进过实验有如下几种方案可实现需求,并简述不同。1. this.$router.go(0)此方式是利用了 history 中前进和后退的功能,传入 0 刷新当前页面。 缺点:页面整个刷新,会白屏。2. location.reload()直接使用刷新当前页面的方法。 缺点:同 this.$router.

    2020/03/29
  • jquery中attr()、prop()、css() 的区别菜鸟指南_区别入门知识

    区别.attr( ) 可以设置元素的属性(也就是给元素新增加一个原来并不存在的属性)也可以获取元素的本来就有的属性以及额外设置的属性。如果要获取的属性没有设置,那么获取到的结果是 undefined; .prop( )可以设置元素的属性(HTML固有的属性,可以给元素添加属性)也可以获取元素的固有的属性值,如果是额外设置的其他属性,则无法通过prop( )获

    2020/03/24
  • jquery.validate验证教程视频_插件基础知识教程

    jquery validate插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求。该插件捆绑了一套有用的验证方法,包括 URL 和电子邮件验证,同时提供了一个用来编写用户自定义方法的 API。所有的捆绑方法默认使用英语作为错误信息,且已翻译成其他 37 种语言。规则名称类型描述requiredBoole

    2020/03/20
  • 前端工程化:构建、部署、灰度小白帮助_工程化小白指南

    优秀的技术方案很多,大部分时候我们感觉只是在现有技术方案里面做排列组合、求笛卡尔积、选择最优解,做出一个最适合当前项目的方案。未来,人类应该编写最核心的业务代码、设置规则,由云端和AI来根据当前项目情况自动选择和调整到最优的架构和方案。前言前端项目的工程化,不只对开发层面的组件化、模块化、规范化等,更涉及到构建、部署的工程化和自动化。工程化的一些概念,编译、

    2020/03/26
  • ehr、HRIS、HRMS,你真的知道它们含义吗?使用教程_系统菜鸟教程网

    ehr不是C-HR(Computerized HR),C-HR只是将人力资源管理电脑化;也不是D-HR(Digitalized HR),D-HR只是将人力资源资料数位化;也有别于HRIS(Human Resource Information System),HRIS是人力资源决策支援系统,可以随时提供人力资源决策所需的各项分析、统计资料。那么eHR、HRIS

    2020/03/20
  • 访问http网站Safari提示网站不安全怎么办?基础入门_安全入门基础

    macOS和iOS上的Safari在所有通过HTTP连接的地址栏中会显示“不安全”的警告。去年,谷歌Chrome和火狐MozillaFirefox是最先显示这种警告的主流浏览器。当通过HTTP连接时,Safari中的所有页面都会显示“不安全”警告。如果页面包含表格字段,当用户在输入时,这种“不安全”警告还会变成红色。HTTP的风险如果您访问有这种警告的网站,

    2020/03/29
  • js中async与defer入门攻略_js知识小白攻略

    元素的几种常见属性:async 异步加载,立即下载,不应妨碍页面其他操作,标记为 async 的异步脚本并不保证按照指定的先后顺序执行,因此异步脚本不应该在加载期间修改DOM,异步脚本一定会在页面的 load 事件前执行,不一定在 DOMContentLoaded 事件前后触发,js有依赖性时,用async很容易出错,async 是无序执行

    2020/03/31
  • Insomnia小白帮助_一款调试API工具

    Insomnia小白帮助 官方网址:https://insomnia.rest/ GitHub:https://github.com/getinsomnia/insomnia 简介…

    2020/03/06
  • open-color菜鸟指南_UI设计的配色方案

    open-color菜鸟指南 官方网址:https://yeun.github.io/open-color/ GitHub:https://github.com/yeun/open…

    2020/03/06
  • GitHub发布史上最大更新,年度报告出炉!菜鸟教程_github基础知识入门

    在昨天的 GitHub Universe 开发者大会上,GitHub 发布了史上最大更新:可直接运行部分代码的 GitHub Actions,以及宣布了 2018 年的 GitHub 年度报告,包括最热门的开源项目和编程语言,让我们一起来先睹为快!自 2008 年推出,GitHub 目前已经是互联网上最大的项目管理和开源协作平台,目前拥有 9600 万个项目

    2020/04/03