本篇只讨论HTTP中间件

中间件定义

https://doc.traefik.io/traefik/middlewares/overview/

Attached to the routers, pieces of middleware are a means of tweaking the requests before they are sent to your service (or before the answer from the services are sent to the clients).

实际上与http的中间件一致,核心是实现对http请求的修改和控制。

中间件配置

Traefik自带了许多中间件,可以直接通过配置进行使用。所有的中间件配置都在 http.middlewares 下,比如,下述配置实现了 “addPrefix”中间件(https://doc.traefik.io/traefik/middlewares/http/addprefix/):

http:
routers:
router1:
service: myService
middlewares:
- "foo-add-prefix"
rule: "Host(`example.com`)" middlewares:
foo-add-prefix:
addPrefix:
prefix: "/foo" services:
service1:
loadBalancer:
servers:
- url: "http://127.0.0.1:80"

中间件开发要点

主要是需要编写下述几点:

  1. 中间件配置。代码中表现为一个结构体,可以映射到配置文件中。
  2. 中间件Handler。作为实现中间件逻辑功能的结构提,实现 http.Handler 接口。
  3. 中间件的初始化代码。需要定义中间件的“构造函数”,用于读取配置并实例化中间件的Handler。

接下来,我们按照Traefik的开发思路,一步一步实现上述内容,最终实现一个中间件的开发。

1、定义中间件配置

参照Traefik已有的中间件配置,我们应将我们自己中间件的配置写在:/pkg/config/dynamic/middlewares.go

比如AddPrefix中间件的配置结构体如下:

type AddPrefix struct {
Prefix string `json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
}

通过 Tags 来确定其配置项在配置文件中的名称。

除了定义中间件的配置之外,还需要将配置添加到 Middleware 这个结构体中。在配置文件结构中所有的中间件配置都在 http.middlewares 下是因为代码中的配置结构就是如此。Middleware 也在 /pkg/config/dynamic/middlewares.go 中。

AddPrefix中间件在 Middleware 中就有一行:

type Middleware struct {
AddPrefix *AddPrefix `json:"addPrefix,omitempty" toml:"addPrefix,omitempty" yaml:"addPrefix,omitempty" export:"true"`
......(其余部分在此省略)
}

同样是通过 Tags 来确定其配置项在配置文件中的名称。

上述结构就确定了中间件在配置文件中的配置:

http:
middlewares:
foo-add-prefix:
addPrefix:
prefix: "/foo"

其中 “foo-add-prefix” 是中间件的名字,可以自定义,但尽量使其有意义。

2、定义中间件的Handler

中间件的Handler是实际实现中间件功能的部分。按照Traefik已有中间件的代码,应该定义在 /pkg/middlewares/ 这个目录下,作为一个包存在。

我们先在 /pkg/middlewares/ 下创建目录,如:/pkg/middlewares/addprefix/,并在其中创建 add_prefix.go 文件来写代码。

既然是实现Handler,我们应先定义一个 Handler 结构体。结构体属性可以随便定义,看你需要什么就加什么,其中的值可以来自配置文件(初始化的时候可以获得配置项的内容)。其中有必要的选项是 一个名为 next 的 http.Handler 类型的属性,其表示接下来要执行的中间件Handler,必不可少。

我们定义的Handler也需要实现 http.Handler 接口,即添加 ServeHTTP 函数。最终的实现如下:

type addPrefix struct {
next http.Handler
prefix string
name string
}
func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
......(具体代码内容省略)
a.next.ServeHTTP(rw, req)
}

可以看到,我们在实现了中间件自身的功能后,调用了 next.ServeHTTP(rw, req) 。这就是执行下一个中间件的语句。由此可见,我们可以通过此语句的调用与否,决定是否中断中间件的执行过程,来从一定程度上控制中间件的执行逻辑。

3、中间件的初始化

中间件的初始化,分为两部分:

  1. 中间件的“构造函数”
  2. 在上游初始化代码中调用“构造函数”

“构造函数”

虽然名为“构造函数”但是由于 Golang 没有传统意义上的构造函数,所以我们只是定义一个能够返回 中间件Handler 实例的方法。

参照已有的Traefik中间件,此方法名为 New ,定义在 中间件的Handler 的同一个包里。比如 AddPrefix 中间件的 New 函数:

// New creates a new handler.
func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name string) (http.Handler, error) {
var result *addPrefix
......(中间的初始化代码省略)
return result, nil
}

New 函数的参数:

  • ctx:上下文,用于获取日志等通用信息。
  • next:下一个中间件的 Handler。这个是必要参数,中间件的执行流程必须的内容。
  • config:就是我们在配置定义中定义的配置实例,其中包含了来自于配置文件的数据。
  • name:配置文件中,中间件的名字。就是上面配置文件里的 foo-add-prefix

实际上,参数是可以自己增删的,因为调用 New 方法的部分,也是我们自己编写,故而我们可以完全控制 New 的定义和调用。不过,在没有什么特殊情况的时候,建议就按上述四个参数来定义,因为其中包含了我们中间件的充分信息。

返回值就是一个 Handler 实例,即我们定义的 addPrefix 结构体(需要注意的是,返回的 http.Handler 类型是个指针)。

调用“构造函数”

调用 New 函数的位置在:/pkg/server/middleware/middlewares.go 文件。其中的 buildConstructor 函数负责初始化所有中间件。

还是以 AddPrefix 中间件举例,它在 buildConstructor 中的代码如下:

func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) {
......(省略其它代码)
var middleware alice.Constructor
// AddPrefix
if config.AddPrefix != nil {
middleware = func(next http.Handler) (http.Handler, error) {
return addprefix.New(ctx, next, *config.AddPrefix, middlewareName)
}
}
......(省略其它代码)
return tracing.Wrap(ctx, middleware), nil
}

实际上,我们在此处,主要处理配置文件的数据,步骤如下:

  1. 确认中间件配置存在。如果不存在,则跳过初始化,后续会有容错处理。
  2. 确认中间件配置可用。如果不可用,可以直接返回 error,错误信息会显示在日志中。
  3. 调用中间件的 New 方法,并向 middleware 赋值,注意:middleware是一个函数,定义为 func(next http.Handler) (http.Handler, error) 我们New方法返回的内容作为其返回值即可。

需要注意的

  1. 中间件中如果不是处于控制流程的需要,一定要调用 next
  2. 尽量遵循Traefik已经有的开发逻辑,比如:代码结构,命名规范等。避免增加无意义的心智负担。

【Traefik二次开发】中间件 Middleware 开发的更多相关文章

  1. ASP.NET Core 开发-中间件(Middleware)

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

  2. ASP.NET Core -中间件(Middleware)使用

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

  3. 中间件(Middleware)

    中间件(Middleware) ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下 ...

  4. .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服务

    .NetCore 下开发独立的(RPL)含有界面的组件包 (一)准备工作 .NetCore 下开发独立的(RPL)含有界面的组件包 (二)扩展中间件及服 务 .NetCore 下开发独立的(RPL)含 ...

  5. LayIM.AspNetCore Middleware 开发日记(二)预备知识介绍

    前言 开发一个AspNetCore的中间件需要理解RequestDelegate.另外,还需要理解.NET Core中的依赖注入.还有一个就是内嵌资源的访问.例如:EmbeddedFileProvid ...

  6. 用Netty开发中间件:高并发性能优化

    用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...

  7. OWIN的理解和实践(三) –Middleware开发入门

    上篇我们谈了Host和Server的建立,但Host和Server无法产出任何有实际意义的内容,真正的内容来自于加载于Server的Middleware,本篇我们就着重介绍下Middleware的开发 ...

  8. Middleware开发入门

    Middleware开发入门 上篇我们谈了Host和Server的建立,但Host和Server无法产出任何有实际意义的内容,真正的内容来自于加载于Server的Middleware,本篇我们就着重介 ...

  9. LayIM.AspNetCore Middleware 开发日记(一)闲言碎语

    前言 前几天写博客的时候突然看见了历史上的今天.不禁感慨时光如梭,这系列博客后来被我标注了已经过时,但是还有很多小伙伴咨询我.既然过时就要更新,正好 .NET Core 也出来很久了,于是乎想到把La ...

随机推荐

  1. VSCode配置远程免密登陆

    生成秘钥 在本地pc的cmd窗口输入:ssh-keygen -t rsa 生成秘钥 C:\Users\NZY/.ssh/id_rsa.pub 该目录就是生成的秘钥要保存的地方(以我自己的电脑为例) 将 ...

  2. [LINUX] 像电影里的黑客一样用 terminal 作为日常开发

    目录 1.效果预览 2.具体实现 2.1 定位鼠标位置 2.2 获取屏幕位置 2.3 计算鼠标在哪个窗口 2.4 1920x1080 平铺效果设计 2.5 1280x1024 平铺效果设计 3 代码 ...

  3. 业务可视化-让你的流程图"Run"起来

    前言 最近在研究业务可视化的问题,在日常的工作中,流程图和代码往往是分开管理的. 一个被维护多次的系统,到最后流程图和代码是否匹配这个都很难说. 于是一直有一个想法,让程序直接读流程图,根据流程图的配 ...

  4. LMC7660即-5V产生电路

    LMC7660为小功率极性反转电源转换器,通过LMC7660电路产生-5V电压,其芯片管脚定义如下表所示. LMC7660负电压产生电路如下图所示. 其中6脚当供电电压大于等于5V时该脚必须悬空,当供 ...

  5. 综合案例_文件搜索和FileFilter过滤器的原理和使用

    文件搜索 需求 : 遍历D:\aaa文件夹,及 aaa 文件夹的子文件夹并且只要.java结尾的文件 分析: 1.目录搜索,无法判断多少级目录,所以使用递归,遍历所有目录 2.遍历目录时,获取的子文件 ...

  6. 递归概念&分类&注意事项和练习_使用递归计算1-n之间的和

    递归:方法自己调用自己 递归的分类: 递归分为两种,直接递归和间接递归 直接递归称为方法自身调用自己 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法 注意事项: 递归一定要有条件限定 ...

  7. Fleet 使用感受

    1. 前言 笔者主要使用的编程语言是 Java.平时使用的 IDE 是 JetBrains 公司的 IntelliJ IDEA.有时候也会打开该公司旗下的 PyCharm.DataGrip.WebSt ...

  8. C++指针和结构体基础知识

    学习C++首先要回忆起C语言当中的指针和结构体知识,本文作者将通过一段代码来总结指针和结构体基础知识:指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址.就像其他变量或常量一样,您必须在使 ...

  9. idea的使用技巧和必要的设置

    idea 如何开启多个线程 打开下面按钮,然后运行相同的代码即可 打开idea需要选择打开哪一个项目 * 设置如下,关闭下面选项即可

  10. Vue中computed用法

    computed是什么?对于任何复杂逻辑,你都应当使用计算属性.computed用来监控自己定义的变量,该变量不在data里面声明,直接在computed里面定义.然后就可以在页面上进行双向数据绑定展 ...