0.Express Gateway入门 v1.0
0.Express Gateway入门 v1.0
阅读(590) | 喜爱(0) | 评论(0) | 清爽页面
1.简述
所谓”网关“,就是网络通道里的一个“关卡”;任何想要访问服务器的用户都好像一辆辆汽车要通过这个“关卡”。既然都要从这里过,那么我们就可以引导交通、告知他们应该怎么走(代理),可以设卡盘问、拦截嫌疑犯(权限认证及拦截),可以为通过的车辆“改头换面”(重写),也可以视交通状况进行“疏导”(负载均衡)。
Express Gateway(下称 EG)是一个基于 Node.js 和 Express.js 的网关服务,它是一个简易的、健壮的、易拓展的网关,通过简单配置即可快速启用。
本文将对 Express Gateway(下称 EG) 进行简单的介绍,包括如何安装、配置 EG,同时会给出插件开发的简单说明。
关于如何管理 EG 的用户 Consumers、如何将开发的插件发布为 NPM 包,不会在本文进行详细介绍,后续进一步深入研究、给出相关文档资料。
Express Gateway 的官方文档链接为:
https://www.express-gateway.io/
2.搭建一个最简网关
下面将使用 EG 搭建一个最简单的网关(什么都不做,只做转发),其中有几个准备条件:
- 准备一个微服务。这里只在本地跑一个服务:
127.0.0.1:3000
,并提供一个GET /local
的路由(可参看本小节末提供的一个简单示例) - 准备一个浏览器。作为客户端请求微服务
- 杀掉端口
8080
的服务(如有)。因为我们的网关要用这个端口,或者配置 EG 监听其他端口
这个网关只要做一件事:把所有客户端来的请求中,前缀为 local
的请求转发到微服务中。作为测试,我们在后面例子中会发出一个请求,格式如下:
GET http://127.0.0.1:8080/local
快速创建一个微服务的方法:
1.安装 Express 安装器:
sudo npm install -g express-generator
2.创建一个 Express 项目:
express test-local
3.安装依赖并运行
yarn
4.新增一个 GET /local
路由(仅适用本例)。在 routes/index.js
中增加:
router.get('/local', function(req, res, next) {
res.render('index', { title: 'Express via EG' });
});
5.启动服务:
yarn start
2.1 安装EG
官方资料:https://www.express-gateway.io/getting-started/#installation
全局安装 express-gateway
命令(可能要加 sudo
):
npm install -g express-gateway
找一个干净的目录创建一个 EG 网关项目:
eg gateway create
使用基本配置:
➜ eg gateway create
? What is the name of your Express Gateway? my-gateway
? Where would you like to install your Express Gateway? my-gateway
? What type of Express Gateway do you want to create? (Use arrow keys)
Getting Started with Express Gateway
❯ Basic (default pipeline with proxy)
进入目录、运行网关:
cd my-gateway && npm start
如果不出意外,你会在控制台看到:
如果要跟着官方介绍走,则选择 “Getting Started with Express Gateway”
2.2 基本目录结构
EG 的基本目录结构如下:
.
├── config // 配置
│ ├── models // 模型
│ │ ├── applications.json
│ │ ├── credentials.json
│ │ └── users.json
│ ├── gateway.config.yml // 网关配置文件
│ └── system.config.yml // 系统配置文件
├── server.js // 服务启动文件
└── package.json
我们需要处理的文件是 gateway.config.yml
,初始内容为:
http:
port: 8080
admin:
port: 9876
host: localhost
apiEndpoints:
# see: http://www.express-gateway.io/docs/configuration/gateway.config.yml/apiEndpoints
serviceEndpoints:
# see: http://www.express-gateway.io/docs/configuration/gateway.config.yml/serviceEndpoints
policies:
- basic-auth
- cors
- expression
- key-auth
- log
- oauth2
- proxy
- rate-limit
pipelines:
# see: https://www.express-gateway.io/docs/configuration/gateway.config.yml/pipelines
其中,http
标识了 EG 服务以 http
协议启动后,将监听的端口号为 8080
。如果要监听其他端口,可以修改这个字段。
2.3 添加一个微服务endpoint声明
这一步主要是为了声明网关可以管理的微服务 endpoints,并为每一个 endpoint 命名。在 gateway.config.yml
文件中找到 serviceEndpoints
字段,并在里面添加一个成员:
serviceEndpoints:
local-service:
url: 'http://127.0.0.1:3000'
注意,serviceEndpoints
字段是 Object
类型,直接将 endpoint 添加进去即可。我们将已启动的本地服务(127.0.0.1:3000
)命名为 local-service
,作为 serviceEndpoints
的一个 key。
2.4 添加一个客户端endpoint声明
这一步并不是指定具体的客户端(因为可以是任何设备、任何软件)。这里指定的是客户端发出 http/https 请求后,EG要处理它们的依据,本例中,我们定义所有前缀为 local
的请求(/local
或 /local/*
) 都被划分为 client-1
。
在 gateway.config.yml
文件中找到 apiEndpoints
字段,并在里面添加一个成员:
apiEndpoints:
client-demo:
paths: ["/local", "/local/*"]
注:
paths
字段可以是String
或Array<String>
,详见后面说明或官方文档
2.5 创建一个pipeline串联客户端与微服务
前面已经定义好了微服务 local-service
及 客户端 client-demo
,还需要通过一个管道把它们串联起来。而这个管道就是我们可以”做文章“的地方。不过在本例中,我们先不做任何处理,仅仅是把微服务和客户端串起来。
在 gateway.config.yml
文件中找到 pipelines
字段,我们命名这个管道名称为 local-pipeline
:
pipelines:
local-pipeline:
apiEndpoints:
- client-demo
policies:
- proxy:
- action:
serviceEndpoint: local-service
changeOrigin: true
上面的几个字段说明如下:
local-pipeline
:类型为String
,标识管道名称,可以创建多个管道apiEndpoints
:类型为Array
,标识了这个管道的客户端 endpoint,使用前面apiEndpoints
字段定义的名称;如果有多个,可以依次添加(注意这里是数组)policies
:类型为Array
,声明了这个管道要使用的策略。上述例子中声明了这个管道只使用了一个策略 Policy:proxy
;它的作用是代理/转发请求,这里指定了将 apiEndpoints 来的请求转发到前面定义了的微服务local-service
去,同时使用原始的 Headers。
这里的
changeOrigin
是以 EG 的角度来看的。从 EG 角度来看,EG 向微服务发出请求,应当使用 EG 自己的 Headers,因此,不论 api endpoint 的 Headers 是什么,如果 EG 向微服务转发请求使用的 Headers 是 EG 自己的,那么就没有改变(changeOrigin: false
);反之,如果要 EG 在转发请求的时候使用 api endpoint 的 Headers,那么对于 EG 而言是改变了 Headers的,因此,上述例子中必须定义changeOrigin: true
。
2.6 运行和测试
启动 EG:
yarn start
不出意外的话,可以在控制台看到这样的打印:
在浏览器中,输入 http://localhost:8080/local
,可以看到以下内容:
3.概念
3.1 基本概念
通过上面的例子,我们已经把大部分 EG 的概念都说了一遍,现在我们再重新看一次:
字段 | 名称 | 说明 |
---|---|---|
endpoints | 端点 | 在 EG 中,端点即 url;EG 提供了两类端点:api 端点和服务端点。EG 通过 api 端点暴露出 api,同时会将来自 api 端点的请求代理到服务端点 |
policies | 策略 | EG 的策略是一组条件conditions、操作action和参数parameters,它(们)在 EG 中承担着评估、响应指定的请求的责任。可以把策略看成 Express 的中间件 |
pipelines | 管道 | 管道是一组连接 API 端点的策略。一个 API 请求会被 API 端点接收并转发给管道,依次检查管道里的策略会、符合条件的会被执行。一般情况下,最后一个策略是代理策略(Proxy Policy),它会把这个请求转发到一个微服务 |
consumers | 消费者 | 一个消费者发起 API 请求并消费这个 API 请求的响应。在 EG 中,消费者代表用户及其应用;EG 提供了一个高可伸缩的消费者管理模块,每一个 app 都必须属于一个用户 |
credentials | 凭证 | credentials 是身份验证和授权的类型。在 EG 中,一个消费者(一个用户或一个应用)可以被授予一个或多个凭证,应用则可以拥有独立于它们关联用户的凭证。每一个凭证指明了一组用于授权的范围 |
scopes | 范围 | 范围是用于分配授权的标签。 范围是全局定义的,然后分配给API端点和凭据。 如果API使用者尝试访问带有范围标记的API端点,则保护端点的授权策略将查找凭据以确保使用者具有相应的范围 |
官方文档地址: https://www.express-gateway.io/docs/core-concepts/
3.2 对概念的理解
首先是 API 端点,可以看作客户端或其他外部服务过来的请求,这些会按照一定的规则分类(比如上文例子中前缀为 /local
的请求被划分成一类请求,命名为 client-demo
,这是一个 API 端点。
EG 作为一个网关,为 API 端点提供了多个管道(pipeline)贯通到微服务端点(service-endpoint)。
每一个管道都有一组(一个或多个)策略来管理每一个请求,可以对请求进行修改、转发、拦截等操作。策略在 EG 启动的时候进行配置。
请求进入 EG 后,会根据设定进入一个管道(pipeline);每一个管道内都会有一个或多个策略,请求将逐个通过它们;如果一个请求符合某个策略的条件(conditions),则会执行该策略指定的行为(actions),否则会进入下一个策略;执行完后,也一样会进入下一个策略中。
参考资料:
https://www.express-gateway.io/about/
3.3 插件Plugins与策略Policies的关系
仔细阅读官方文档,在插件Plugins的页面中有这么一段:
Note: Existing policies within the Express Gateway core will be eventually refactored out of the core into Express Gateway plugins using this framework.
摘自:https://www.express-gateway.io/docs/plugins/
这意味着,在 EG v1.2.0 之后,由于使用了插件框架,所有的策略都被从 EG 核心移出、并以插件的形式进行了重构。因此,我们如果要自定义一个策略,就必须以插件的形式定义和引入 EG。
我们可以将插件看做一个卡车,卡车上载着一个或多个货箱(策略Policy);在 EG 启动的时候,EG 内核会把所有的卡车召集起来卸货,堆放在不同的管道(pipeline)中。
如果想更了解具体的步骤,可以阅读本文的“运行时Runtime及启动顺序”的“初始化阶段”一节内容。
3.4 运行时Runtime及启动顺序
参考文档:
https://www.express-gateway.io/docs/runtime/boot-sequence/
3.4.1 启动顺序图
3.4.2 初始化阶段
-
初始化配置
EG 会加载所有的配置文件(gateway.config.yml
,system.config.yml
或对应的 json 文件) -
注册插件
EG 会依次加载system.config.yml
文件里plugins
字段声明的插件。通过加载全部插件,EG 可以获取这些插件所提供的所有的策略(Policies)和条件(Conditions)等实体(entities)。
其中,插件通过下面语句加载:
require('plugin-name')(pluginContext)
通过 pluginContext
,插件可以获取所需的上下文。需要注意的是,在这个阶段里,任何策略、条件或中间件都不会执行;同时,任何 http 服务器都不会接受请求。因为在这个阶段,EG 仅仅是在汇总全部的拓展以支持真正的运行阶段
3.4.3 初始化网关
-
初始化网关服务
EG 在这个阶段会首先创建一个线程用于处理所有进入 EG 的请求。 -
加载网关的拓展
EG 会在这个阶段初始化全部已注册的条件、策略、路由和中间件;在此之后,这些实体就能够运用在请求的处理了。 -
初始化管道引擎
所有管道都会被转换为 ExpressJS 的路由,同时注册到 EG 的 app 中。 -
启动网关服务
最终,EG 的 app 启动,并暴露了 HTTP 和 HTTPS 服务,EG 开始监听配置文件里指定的端口号。 -
触发ready事件
在完成了上述步骤后,EG 会主动广播两个服务就绪的消息:http-ready
和https-ready
其中,加载网关的拓展 中:
- 路由和中间件会挂载到 EG 的 app 中
- 条件和策略会被注册到管道的引擎
3.4.4 初始化admin
-
启动admin服务
创建一个用于处理 admin api 请求的线程。 -
加载admin的拓展
初始化已注册的路由和中间件。 -
启动 admin 服务
admin 的 app 会以 node.js 服务的方式暴露,同时监听配置的端口。此后,admin 就可以开始处理相关请求了。 -
触发ready事件
admin 开始监听端口的同时,会广播admin-ready
事件。
3.4.5 热更新
EG 支持热更新即服务会监听变化、不需要手动重启服务
以下操作会触发热更新、不需要手动重启:
- API Endpoints 更改
- Service Endpoints 更改
- Pipeline 更改
以下操作的更改必须手动重启:
- http 字段更改
- https 字段更改
- system.config.yml 文件更改
- models 目录下的 model 更改
4.项目及配置结构
参考资料:
https://www.express-gateway.io/docs/runtime/project-directory/
4.1 基本结构
my-gateway
├── config // EG的配置文件
│ ├── gateway.config.yml
│ ├── models
│ │ ├── applications.js
│ │ ├── credentials.js
│ │ └── users.js
│ └── system.config.yml
├── server.js // EG 的启动文件
└── package.json
4.2 基本语法
yml 格式的配置数值基本语法如下:
${ENV_VARIABLE_NAME:-DEFAULT_VALUE}
其中:
ENV_VARIABLE_NAME
表示如果定义了这个环境变量,则使用它DEFAULT_VALUE
:如果没有定义指定的环境变量,则使用这个默认值
举个例子:
http:
port: ${HTTP_PORT:-8080}
apiEndpoints:
customers:
host: customers.company.com
orders:
host: orders.company.com
本例中,如果环境变量定义了 HTTP_PORT
则 http 监听的端口号使用环境变量的值,否则监听 8080
端口。
4.3 gateway.config.yml
4.3.1 http
该字段用于配置 EG 的 http 服务。其基本格式如下:
http:
port: 9080 # EG will listen for http requests on port 9080
hostname: localhost
参数说明:
参数名 | 说明 |
---|---|
port |
要监听的 http 端口号 |
hostname |
要监听的 http 服务 |
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/http/
4.3.2 https
该字段用于配置 EG 的 https 服务。其基本格式如下:
https:
port: 9443
hostname: localhost
tls:
"*.demo.io":
key: example/keys/demo.io.key.pem
cert: example/keys/demo.io.cert.pem
"api.acme.com":
key: example/keys/acme.com.key.pem
cert: example/keys/acme.com.cert.pem
"default":
key: example/keys/eg.io.key.pem
cert: example/keys/eg.io.cert.pem
参数说明:
参数名 | 说明 |
---|---|
port |
要监听的 http 端口号 |
hostname |
要监听的 http 服务 |
tls |
密钥和证书 |
其中,EG 支持 TLS,包括 SNI。tls
字段可以指定一个或多个域名使用的密钥和证书;每个 tls
通过 key
字段来指定密钥文件的路径、通过 cert
来指定证书文件路径。
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/https/
4.3.3 admin
该字段用于配置 EG 的 admin 服务。其基本格式如下:
http:
host: localhost
port: 9080 # EG will listen for http requests on port 9080
参数说明:
参数名 | 说明 |
---|---|
host |
监听的hostname |
port |
监听端口号 |
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/admin/
4.3.4 apiEndpoints
EG 通过 URLs 将微服务作为 API 暴露给外部,这些 URLs 就是 apiEndpoints。API 使用者可以 apiEndpoints 发出请求,当 apiEndpoints 被绑定在管道中、与微服务(serviceEndpoints) 连通后,外部才可以访问微服务(仍然要经过一些策略和动作)。
下面例子中定义了几个 apiEndpoints:help
,api
,example
及 example2
:
apiEndpoints:
help: # name, used as reference in pipeline
host: '*' # required, '*' will not check for host name
paths: /help # optional, by default will serve all requests - same as *
api: # name, used as reference in pipeline
host: '*.com' # wildcard pattern support
paths:
- '/v1/*' # string or array of strings
- '/v2/*'
example: # name, used as reference in pipeline
host: 'example.com'
paths: /v2/* # string or array of strings
example2: # It is possible to provide an array of matching conditions in the same apiEndpoint
- host: 'example2.com'
paths: /v2/*
methods: ["GET", "OPTIONS"]
scopes: ["example2:read"]
- host: 'example2.com'
paths: /v2/*
methods: ["PUT", "POST", "PATCH", "DELETE"]
scopes: ["example2:write"]
参数说明:
参数名 | 说明 |
---|---|
host |
监听的hostname,类型为 String 。即请求头 Headers 的 HOST 参数,如果不设置则默认为 * ,即接收所有host |
paths |
指定接收的路径,类型为 Array<String> 或 直接使用 String 。它会对来自 host 的请求 URLs 进行过滤 |
methods |
指定接收的请求类型,类型为 Array<String> 。数组内元素可以是 GET ,POST ,PUT ,DELETE 等的一个或多个,也可以不指定(即全部接收、不进行过滤) |
scopes |
操作范围,类型为 Array<String> 。标志请求允许操作的范围 |
4.3.4.1 例1:任意host和指定path
下面例子中,指定了host
为 *
、path
为 /help
:
apiEndpoints:
help:
host: '*'
paths: /help
此例中,没有在请求头 Headers 中设置 HOST
的请求会报 404
,路径不是 /help
的请求也会报 404
:
// Passed
linxiaozhou.com/help
easypm.linxiaozhou.com/help
linxiaozhou.cn/help
// 404
linxiaozhou.com
easypm.linxiaozhou.com/contact
easypm.linxiaozhou.com/help/docs
linxiaozhou.com/help # Headers Not set HOST
4.3.4.2 例2:指定host和指定path
下面例子中,指定了host
为 linxiaozhou.com
、path
为 /help
:
apiEndpoints:
help:
host: 'linxiaozhou.com'
paths: /help
则所有 host
不是 linxiaozhou.com
或路径不是 /help
的请求会报 404
(没有在请求头 Headers 中设置 HOST
的请求也会报 404
后续不再说明):
// Passed
linxiaozhou.com/help
// 404
easypm.linxiaozhou.com
easypm.linxiaozhou.com/help
easypm.linxiaozhou.com/contact
easypm.linxiaozhou.com/help/docs
4.3.4.3 例3:指定host子域名及路径path
有下面的配置:
apiEndpoints:
help:
host: '*.linxiaozhou.com'
paths: /help
如果一个请求的 host 不含有子域名,会报 404
;如果路径没有 help
,也会报 404
:
// Passed
easypm.linxiaozhou.com/help
// 404
linxiaozhou.com/help
easypm.linxiaozhou.com
easypm.linxiaozhou.com/contact
easypm.linxiaozhou.com/help/docs
4.3.4.4 关于path
明确指定路径:
paths: /admin
- Passed:
/admin
- 404:
/admin/bob
,/admin/charlie/1
,/staff
一定要区分
/admin/*
的区别
使用通配符:
paths: /admin/*
- Passed:
/admin/bob
,/admin/charlie/1
- 404:
/admin
合并上面两种情况:
paths: ['/admin','/admin/*']
- Passed:
/admin
,/admin/bob
,/admin/charlie/1
- 404:
/staff
大部分的场景下,应该合并这两种情况
包含变量:
paths: /admin/:id
- Passed:
/admin/bob
,/admin/charlie
- 404: /admin; /staff
4.3.4.5 关于顺序
下面例子中,会按顺序匹配:先检查是否匹配 tabby
,如果不符合则检查是否匹配 cat
;如果不匹配 cat
,则检查是否匹配 com
:
apiEndpoints:
tabby:
host: '*.tabby.cat.com'
paths: '*' # optional, if not specified will default to *
cat:
host: '*.cat.com'
com:
host: '*.com'
但如果我们改为下面的方式,则永远不可能进入 tabby
和 cat
,因为所有符合它们的请求都符合 com
的要求、全部进入了 com
里面:
# 大误
apiEndpoints:
com:
host: '*.com'
tabby:
host: '*.tabby.cat.com'
paths: '*' # optional, if not specified will default to *
cat:
host: '*.cat.com'
因此,我们建议把更具体的 host 放在前面,否则会被前面模糊匹配的拦截。
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/apiEndpoints/
4.3.5 serviceEndpoints
通过 apiEndpoints 接收到的请求,会经过管道并最终代理到指定的微服务中。serviceEndpoints 指定了网关代理的微服务节点的信息:
serviceEndpoints: # downstream microservices
cats: # name, used as a reference in pipeline
url: "http://cats1.example.com"
dogs: # name, used as a reference in pipeline
urls: # multiple urls can be used with load balancing
- "http://dogs1.example.com"
- "http://dogs2.example.com"
参数说明:
参数名 | 说明 |
---|---|
url |
微服务的协议及名称,类型为 String 。例如 https://www.linxiaozhou.com |
urls |
一组微服务的协议及名称,类型为 Array<String> 。例如 ["https://cdn1.linxiaozhou.com", "https://cdn2.linxiaozhou.com"] ,可用于负载均衡 |
proxyOptions |
使用代理策略的选项 |
4.3.6 policies
策略白名单,任何需要在 EG 服务中使用的策略都必须在此声明。例如:
policies:
- cors
- rate-limiter
- log
- proxy
- oauth2
- key-auth
- basic-auth
policies
是一个数组,每个元素为策略的名称。这个名称对应了node_modules/express-gateway/lib/policies/
目录下的文件夹名称:
当我们自定义了一个插件、里面会有对应的策略,则首先要将这个插件在 system.config.yml
文件的 plugins
字段引入,然后将策略的名称写入 gateway.config.yml
文件的 policies
字段。
关于自定义插件策略的名称
我们在编写策略的时候,就指定了这个策略的名称:
module.exports = { name: 'logger', schema: { $id: 'N/A', }, policy: (actionParams) => { // ... } };
4.3.7 pipelines
管道指定了 EG 的核心操作,它像一条管道一样连通了上面定义的 apiEndpoints
和 serviceEndpoints
。所有来自 apiEndpoints
的请求都将依次执行管道内定义的策略,然后进入代理的 serviceEndpoints
。
下面例子中,定义了一个名为 default
的管道,它会把 api
(在 apiEndpoints
定义的 API 端点) 的请求流入一个策略 simple-logger
后代理给 example
(在 serviceEndpoints
定义的微服务):
http:
port: 3000
serviceEndpoints:
example: # will be referenced in proxy policy
url: 'http://example.com'
apiEndpoints:
api:
host: '*'
paths: /
pipelines:
default:
apiEndpoints:
- api
policies:
-
simple-logger: # policy name
- # array of objects with condition\action properties
condition: #optional,; defaults to always execute
name: pathExact
path: /v1
action:
message: "${req.method} ${req.originalUrl}"
-
proxy: # policy name
- # array of objects with condition\action properties
action:
serviceEndpoint: example # see declaration above
在管道中,会依次列出每一个策略 Policy,每个策略的结构包含两个字段:
condition
:条件,可不填。只有满足条件的请求才会触发下面的动作action
;如果不填,则直接执行动作。action
:动作,这个策略具体要执行的动作。具体动作需要传入什么参数和对应数值,与具体的策略有关,需要阅读策略的说明来决定
如何设置
condition
,可参看这份说明
https://www.express-gateway.io/docs/policies/customization/conditions/
关于 action
,以策略 Proxy
为例,它可以接收的参数如:
更详细的说明,可参看这个链接:https://www.express-gateway.io/docs/policies/proxy/
4.4 system.config.yml
system.config.yml
定义了网关的系统级配置和全局参数,它描述了网关的基础设置内容。
4.4.1 db
该字段定义了 EG 存储数据的方式。目前 EG 支持两种方式:
- 内存
- Redis
4.4.1.1 内存数据库
使用内存数据库需要注意以下特性:
- 每次重新启动时都会重置数据
- 热启动不会销毁数据
由于它不需要使用 Redis 来管理,使用简单方便,可以在开发和演示的时候来使用;但由于上述特性,它并不适合生产环境中使用,Redis 才是最佳选择。
使用内存数据库时,将 emulate
置为 true
即可:
db:
redis:
emulate: true
namespace: EG
4.4.1.2 Redis数据库
使用 Redis 数据库当然要先启动 Redis 服务,这也是应用在生产环境的选择。
db:
redis:
host: localhost
port: 6379
namespace: EG
更多关于 redis 的配置参数,可以阅读以下资料:
https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options
4.4.1.3 哨兵和集群
EG 使用 ioredis 连接到 Redis 实例,该实例支持连接到Cluster和Sentinel,但 EG 目前还不支持连接集群的功能。
如果想连接到 Sentinel 实例,只需修改配置文件即可:
db:
redis:
sentinels:
- host: 'localhost'
port: 26379
- host: 'localhost'
port: 26380
name: mymaster
namespace: EG
更多关于哨兵的配置资料,可阅读下面资料:
https://github.com/luin/ioredis#sentinel
更多 ioredis 的资料,可阅读下面资料:
https://github.com/luin/ioredis
4.4.2 plugins
plugins
字段声明了所需要用到的全部插件,里面包含了可用的策略、条件、自定义路由及中间件。
声明一个插件基本格式为:
plugins:
example:
description: some helpful information for yml file maintainer
param1: 'this is plugin initialization parameter'
声明一个自定义插件的基本格式为:
plugins:
express-gateway-plugin-logger:
param1: 'logger-plugin'
package: "./plugins/logger/manifest.js"
更多关于插件的安装和开发资料,可阅读下面的“插件”章节。
4.4.3 crypto
EG 不会保存明文密码、会将它们转换为hash散列。crypto
字段即声明了转换的规则:
crypto:
cipherKey: sensitiveKey
algorithm: aes256
saltRounds: 10
关于加密的方式,可以阅读:
https://www.npmjs.com/package/bcrypt
4.4.4 session
EG 本身不需要 session 即可工作,只有在使用内置的 OAuth 2.0 认证时候会使用到 session。EG 对 session的支持基于 express-session。
默认使用内存管理 session,例如:
session:
secret: keyboard cat # replace with secure key that will be used to sign session cookie
resave: false
saveUninitialized: false
基于 Redis 的 session 管理(需要运行 redis 并安装 npm i connect-redis
):
session:
storeProvider: connect-redis
storeOptions:
host: localhost
port: 6379
secret: keyboard cat # replace with secure key that will be used to sign session cookie
resave: false
saveUninitialized: false
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/session/
4.4.5 accessTokens
OAuth 2.0 相关配置,可以配置访问令牌(Access Token) 的参数,例如:
accessTokens:
timeToExpiry: 7200000
tokenType: 'jwt'
issuer: 'express-gateway'
audience: 'something'
subject: 'somebody'
secretOrPrivateKey: 'ssssst'
timeToExpiry
:令牌的到期时间tokenType
:要发行的令牌类型。 它可以是不透明的或jwt
issuer
:当tokenType
不是jwt
时被忽略。 定义要在令牌中发送的发行者audience
:当tokenType
不是jwt
时被忽略。 定义要发送给令牌的受众subject
:当tokenType
不是jwt
时被忽略。 定义要在令牌中发送的主题secretOrPrivateKey
:当tokenType
为jwt
时被忽略。 定义用于对令牌进行签名的私钥或私钥secretOrPrivateKeyFile
:当tokenType
为jwt
时被忽略。 定义用于存储令牌签名的私钥或私钥的文件
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/accessTokens/
4.4.6 refreshTokens
OAuth 2.0 相关配置,可以配置刷新 token 的时间间隔。例如:
refreshTokens:
timeToExpiry: 7200000
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/refreshTokens/
4.4.7 authorizationCodes
OAuth 2.0 相关配置,
authorizationCodes:
timeToExpiry: 300000
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/authorizationCodes/
4.5 models
本文不对 admin 进行介绍,更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/session/
5.插件
5.1 插件基础
前面提过,策略必须以插件的形式引入 EG 框架中。但需要注意的是,一个插件里,可以引入多个策略;不仅是策略,它还可以放置条件、自定义的路由及中间件。
简而言之,一个插件就是一个容器,它可以存放实体,同时可以接收事件。这里的“实体”包括三类:
- 策略Policies
- 条件Conditions
- 自定义路由及中间件
参考文档:
https://www.express-gateway.io/docs/plugins/
5.1.1 通过CLI安装插件
通过eg plugin install __name__
命令安装。安装成功后,会在 system.config.yml
文件中的 plugins
字段中增加插件的描述,例如:
plugins:
example: # "express-gateway-plugin-example"
param1: 'global per plugin param1'
5.1.2 通过NPM安装插件
通过 yarn add __name__
安装,然后在 config/system.config.yml
文件的 plugins
字段下添加包名称来引入
5.1.3 引入自己编写插件
按照规范在项目代码中添加文件夹、文件,编写好插件后,在 config/system.config.yml
文件的 plugins
字段下添加插件的相对路径来引入,例如:
plugins:
express-gateway-plugin-logger:
param1: 'logger-plugin'
package: "./plugins/logger/manifest.js"
5.2 插件开发
5.2.1 编写一个插件
前文提到,插件是一个“容器”,它用于装载策略、条件、路由及中间件。因此开发一个插件的关键在于开发它的装载的实体。
接下来,我将以装载一个策略为例,说明如何编写一个插件,并引入我们的 EG 服务中。
官方资料:
https://www.express-gateway.io/docs/plugins/plugin-development/
5.2.1.1 目录结构
下面给出了我们自定义插件的位置及目录:
.
├── config
│ ├── models
│ │ ├── applications.json
│ │ ├── credentials.json
│ │ └── users.json
│ └── sources
│ ├── gateway.config.yml
│ └── system.config.yml
├── plugins // 插件
│ └── logger
│ ├── conditions
│ ├── routes
│ ├── policies
│ │ └── logger.policy.js
│ └── manifest.js
└── server.js
在 plugins
目录中,我们定义了一个名为 logger
的目录(即插件名称),里面的几个文件(夹)分别是:
logger
├── conditions // 存放条件文件
├── routes // 存放路由及中间件文件
├── policies // 存放策略文件
│ └── logger.policy.js // 其中一个策略
└── manifest.js // 本插件的入口文件
5.2.1.2 定义一个策略
logger.policy.js
文件内容为:
module.exports = {
name: 'logger',
schema: {
$id: 'N/A',
type: 'object',
properties: {
param: {
type: 'string',
default: 'log request'
}
}
},
policy: (actionParams) => {
return (req, res, next) => {
console.log('executing policy-from-logger-plugin with params', actionParams);
// Write log here
// ...
next();
};
}
};
5.2.1.3 插件入口文件
manifest.js
文件内容如下:
module.exports = {
version: '1.2.0',
schema: {
$id: 'N/A',
},
init: function (pluginContext) {
// 注册自定义策略 Logger
const policy = require('./policies/logger.policy')
pluginContext.registerPolicy(policy)
// 热更新后触发该事件
pluginContext.eventBus.on('hot-reload', function ({ type, newConfig }) {
console.log('[PLUGINS.LOGGER] hot-reload', type, newConfig);
});
// http就绪时触发该事件
pluginContext.eventBus.on('http-ready', function ({ httpServer }) {
console.log('[PLUGINS.LOGGER] http server is ready', httpServer.address());
});
// https就绪时触发该事件
pluginContext.eventBus.on('https-ready', function ({ httpsServer }) {
console.log('[PLUGINS.LOGGER] https server is ready', httpsServer.address());
});
// admin就绪时触发该事件
pluginContext.eventBus.on('admin-ready', function ({ adminServer }) {
console.log('[PLUGINS.LOGGER] admin server is ready', adminServer.address());
});
}
}
5.2.1.4 引入插件
在 system.config.yml
文件中,声明刚刚编写的插件:
plugins:
express-gateway-plugin-logger:
param1: 'logger'
package: "./plugins/logger/manifest.js"
5.2.1.5 将策略加入白名单
引入插件后,仅仅是将策略、条件、路由及中间件引入了 EG,如果要使用策略,还需要在 gateway.config.yml
文件中加入策略白名单:
policies:
- cors
- rate-limiter
- log
- proxy
- oauth2
- key-auth
- basic-auth
- logger # 加入我们自定义的logger策略
注意要添加的文件 不是 系统配置文件
system.config.yml
5.2.1.6 在管道中使用策略
在完成上述步骤后,即可使用定义的 logger 策略,例如:
pipelines:
default:
apiEndpoints:
- api
policies:
-
logger: # 我们定义的logger策略
action:
param: "${req.method} ${req.originalUrl}"
-
proxy: # Proxy策略
action:
serviceEndpoint: example
5.2.1.7 编写插件包
上面介绍了编写一个本地的插件的步骤,但距离发布一个可用的插件包还有一定距离。如果想了解更多,可以查看官方提供的一个插件示例:
https://github.com/ExpressGateway/express-gateway-plugin-example
5.2.2 策略开发
5.2.2.1 基本结构
策略是 Express.js 中间件的包装,其基本结构为:
module.exports = {
name: 'logger',
schema: {
$id: 'N/A',
type: 'object',
properties: {
param: {
type: 'string',
default: 'log request'
}
}
},
policy: (actionParams) => {
return (req, res, next) => {
console.log('executing policy-from-logger-plugin with params', actionParams);
// do something here
// ...
next();
};
}
};
其中:
name
:管道通过这个名称找到这个策略schema
:描述了管道在执行这个策略时要提供进来的参数列表;需要注意的是,$id
可以是这个 schema 的 url(如http://express-gateway.io/schemas/policies/example-policy.json ),也可以不提供 url,但必须显式地声明为N/A
,否则会报错。policy
:返回 Express.js 中间件的一个函数。它的输入参数actionParams
,是引用该策略的管道传入的配置信息,其数据格式要遵循schema
定义的数据结构。
5.2.2.2 举例
我们将本文最开始的例子中,policy
函数做一点修改,改为重新请求的url(原来什么都没做):
module.exports = {
name: 'logger',
schema: {
$id: 'N/A',
type: 'object',
properties: {
param: {
type: 'string',
default: 'log request'
}
}
},
policy: (actionParams) => {
return (req, res, next) => {
console.log('executing policy-from-logger-plugin with params', actionParams);
// Rewrite url
req.url = req.url.replace('/local', '/');
next();
};
}
};
则在成功加载后,我们访问 http://localhost:8080/local
将不再看到最初的内容:
而会变成下面的内容。因为我们把请求 url 中的 local
截断了,实际到达我们的 local 微服务的请求变成了 http://localhost:8080/
5.2.2.3 注册到插件
策略开发完后,需要在插件中注册,这样在插件声明和引入后,策略才能被正常加载进去:
module.exports = {
version: '1.2.0',
init: function (pluginContext) {
const policy = require('./policies/logger.policy')
pluginContext.registerPolicy(policy)
}
}
5.2.2.4 添加白名单
此外,还需要在 gateway.config.yml
文件中将策略添加进白名单:
policies:
# 其他策略
- logger # 加入我们自定义的logger策略
https://www.express-gateway.io/docs/plugins/policy-development/
5.2.2.5 在管道中使用
在管道的配置中,添加对应的策略:
pipelines:
default:
apiEndpoints:
- api
policies:
-
logger: # 我们定义的logger策略
action:
param: "${req.method} ${req.originalUrl}"
# 其他策略
5.2.2.6 Trouble Shooting
如果插件的初始化方法出错,则可能会报下面的错误:
如果没有在白名单中声明策略,会报以下错误:
5.2.3 条件开发
简单来说,条件 condition 就是管道判断要不要执行某个策略的依据。因此,我们一般会把一个条件放在策略里面、放在具体的 action
之前,例如:
pipelines:
apiPipeline:
apiEndpoints:
- api
policies:
- example:
-
condition: # 符合条件的请求才会执行action
name: 'url-match'
expected: '/test'
action:
baseUrl: 'https://example.com'
而条件 condition 的基本结构如下:
module.exports = {
name: 'url-match',
schema: {
$id: 'http://express-gateway.io/schemas/conditions/url-match.json',
type: 'object',
properties: {
expected: {
type: 'string'
}
},
required: ['expected']
},
handler: conditionConfig => req => conditionConfig.expected === req.url
};
其中:
name
:管道通过它找到这个条件schema
:描述了管道在执行这个条件时要提供进来的参数列表;需要注意的是,$id
可以是这个 schema 的 url(如http://express-gateway.io/schemas/conditions/url-match.json ),也可以不提供 url,但必须显式地声明为N/A
,否则会报错handler
:它是一个函数,用于接受Express.js 请求的对象和条件参数,必须返回true
或false
,表示是否满足条件;返回true
则会继续执行下一个条件或执行 action,返回false
表示不满足条件,将不会执行。输入参数是管道中引用这个条件的所有配置选项
条件需要在插件中注册,方法是:
module.exports = {
version: '1.2.0',
init: function (pluginContext) {
const condition = require('./conditions/url-match.js')
pluginContext.registerCondition(condition)
}
}
官方资料:
https://www.express-gateway.io/docs/plugins/condition-development/
5.2.4 路由开发
EG 运行了两个 Express.js 应用,分别是网关和admin;因此我们开针对两个应用的路由进行管理。路由的开发细节不会在本文详细介绍,如需了解更多,可阅读官方文档:
https://www.express-gateway.io/docs/plugins/condition-development/
6.CLI
EG 提供了命令来管理,例如我们在最开始创建 EG 的命令:
eg gateway create
比如,列出全部用户:
# 列出全部用户
eg users list
EG 支持的模块有:
命令模块 | 模式 | 别名 |
---|---|---|
users |
Manage users | user |
apps |
Manage apps | app |
scopes |
Manage scopes | scope |
credentials |
Manage credentials | credential |
credential:scopes |
Manage scopes for credentials | credential:scope |
tokens |
Manage tokens | token |
gateway |
Manage a gateway | 无 |
plugins |
管理插件 | 无 |
7.官方插件资源 Resources
EG 提供了一些基本的资源(策略),我们可以直接使用以减少开发工作。
7.1 用法介绍
以最简单的 logger
为例,我们要在一个管道中增加这个策略。首先,我们要在 gateway.config.yml
的 policies
字段中将 logger
加入:
policies:
# 加入该策略
- logger
然后阅读文档,了解该策略需要的输入参数:
https://www.express-gateway.io/docs/policies/log
将策略加入需要使用的管道中:
pipelines:
local-pipeline:
apiEndpoints:
- client-demo
policies:
- logger:
action:
param: "Logger"
- log: # 使用该策略
- action:
message: ${req.method} ${req.originalUrl} # parameter for log action
- proxy:
- action:
serviceEndpoint: local-service
changeOrigin: true
最后进行测试:
7.2 认证相关Authentication
Basic Auth
Username and Password
https://www.express-gateway.io/docs/policies/basic-authorization/
Key Auth
API keys for users and applications
https://www.express-gateway.io/docs/policies/key-authorization/
OAuth2
Authentications and Authorization Flows
- https://www.express-gateway.io/docs/policies/oauth2/
- https://www.express-gateway.io/docs/policies/oauth2-introspection/
JWT
JSON Web Token issuing and validation
https://www.express-gateway.io/docs/policies/jwt/
7.3 自定义Customization
Expression
Execute JavaScript within the egContext object
https://www.express-gateway.io/docs/policies/expression/
Request Transformer
Modify header or body in the request message
https://www.express-gateway.io/docs/policies/request-transformer/
Response Transformer
Modify header or body in the response message
https://www.express-gateway.io/docs/policies/response-transformer/
7.4 安全 Security
CORS
Cross Origin Resource Sharing
https://www.express-gateway.io/docs/policies/cors/
7.5 Serverless
Lambda
Proxy to AWS Lambda Functions
https://www.express-gateway.io/docs/policies/lambda/
7.6 日志管理 Quality of Service
Simple Logger
Logging Format and Output
https://www.express-gateway.io/docs/policies/log/
Load Balancer Proxy
Load Balance and Proxy to Services
https://www.express-gateway.io/docs/policies/proxy/
Rate Limiter
Throttle API Requests
https://www.express-gateway.io/docs/policies/rate-limiter/
7.7 工具 Utilities
Rewrite
Rewrite URL or Redirect Requests
https://www.express-gateway.io/docs/policies/rewrite/
Terminate
Terminate a request with a status code and message
https://www.express-gateway.io/docs/policies/terminate/
8.更多
8.1 yml即json
例如,本文最开始示例的两个文件分别可以用 json 格式书写。你可以在 config
目录下分别创建 system.config.yml
和 gateway.config.yml
同名文件,同时将原来的 .yml
文件删除。
如果同时存在
.yml
和.json
是同名文件,EG 会优先使用.json
文件!
system.config.yml
-> system.config.json
,内容为:
{
"db": {
"redis": {
"emulate": true,
"namespace": "EG"
}
},
"plugins": {
"express-gateway-plugin-logger": {
"param": "Logger",
"package": "./plugins/logger/manifest.js"
}
},
"crypto": {
"cipherKey": "sensitiveKey",
"algorithm": "aes256",
"saltRounds": 10
},
"session": {
"secret": "keyboard cat",
"resave": false,
"saveUninitialized": false
},
"accessTokens": {
"timeToExpiry": 7200000
},
"refreshTokens": {
"timeToExpiry": 7200000
},
"authorizationCodes": {
"timeToExpiry": 300000
}
}
gateway.config.yml
-> gateway.config.json
,内容为:
{
"http": {
"port": "${GATEWAY_PORT:-8080}"
},
"admin": {
"port": 9876,
"host": "localhost"
},
"apiEndpoints": {
"demo-service": {
"host": "*"
}
},
"serviceEndpoints": {
"demo-service": {
"url": "${DEMO_SERVICE_ENDPOINT:-http://localhost:3000}"
}
},
"policies": [
"basic-auth",
"cors",
"expression",
"key-auth",
"log",
"oauth2",
"proxy",
"logger",
"rate-limit"
],
"pipelines": {
"demo-service": {
"apiEndpoints": [
"demo-service"
],
"policies": [
{
"logger": {
"action": {
"param": "log request"
}
}
},
{
"proxy": [
{
"action": {
"serviceEndpoint": "demo-service",
"changeOrigin": true
}
}
]
}
]
}
}
}
8.2 yml的锚点Anchor
yml的锚点 Anchor 是一个很高效的语法,它可以减少很多重复工作,减少代码的冗余。这一点是 json 文件无法比拟的。
下面代码定义了两个管道 customers
和 orders
:
http:
port: 8080
apiEndpoints:
customers:
host: customers.company.com
orders:
host: orders.company.com
serviceEndpoints:
customers:
url: 'http://customers'
orders:
url: 'http://orders'
policies:
- jwt
- proxy
- rate-limit
pipelines:
customers:
apiEndpoints:
- customers
policies:
- rate-limit:
- action:
max: 1
windowMs: 1000
- jwt:
- action:
secretOrPublicKeyFile: ./key/pubKey.pem
checkCredentialExistence: false
- proxy:
- action:
serviceEndpoint: customers
changeOrigin: true
orders:
apiEndpoints:
- orders
policies:
- rate-limit:
- action:
max: 1
windowMs: 1000
- jwt:
- action:
secretOrPublicKeyFile: ./key/pubKey.pem
checkCredentialExistence: false
- proxy:
- action:
serviceEndpoint: orders
changeOrigin: true
可以看到,两个管道的策略 policies 基本上是一致的,显得非常冗余。为了减少冗余,我们可以在第一个管道的配置信息中设置了两个锚点 &jwt
和 &proxy
:
- rate-limit: &rate-limit
- action:
max: 1
windowMs: 1000
- jwt: &jwt
- action:
secretOrPublicKeyFile: ./key/pubKey.pem
checkCredentialExistence: false
- proxy:
- action: &proxy
serviceEndpoint: customers
changeOrigin: true
在接下来的另一个管道 orders
,我们不需要再写相同的内容,可以直接引用上面的锚点:
policies:
- rate-limit: *rate-limit
- jwt: *jwt
- proxy:
- action:
<<: *proxy
serviceEndpoint: orders
相关规范可阅读该文档:
https://camel.readthedocs.io/en/latest/yamlref.html
9.后记
《Express Gateway 入门》这篇文档是继 《Babel 入门》之后字数最多、查阅资料最频繁的一份资料,经过了近一周的实践、测试、用例编写和修改,花费了五个晚上时间才告一段落。尽管如此,仍然还有一些理解不到位的地方,我也会积极听取大家的意见、不断完善本文。
开篇时已经说明,有一些部分功能不会写入本文,因此到目前为止并不算全面了解了EG;此外 EG 的部署、应用场景还没有做进一步的实践,这距离真正的应用在生产环境也还有一段很长一段路要走。
因此,如果要真正运用好 Express Gateway,我建议大家还应该往以下方面去学习和实践:
- https 的使用
- admin 模块的管理
- 编写和发布插件包
- 编写路由及中间件
- 使用 oAuth 2.0
- EG 应用在个人网站集群
10.参考资料
1.简述
所谓”网关“,就是网络通道里的一个“关卡”;任何想要访问服务器的用户都好像一辆辆汽车要通过这个“关卡”。既然都要从这里过,那么我们就可以引导交通、告知他们应该怎么走(代理),可以设卡盘问、拦截嫌疑犯(权限认证及拦截),可以为通过的车辆“改头换面”(重写),也可以视交通状况进行“疏导”(负载均衡)。
Express Gateway(下称 EG)是一个基于 Node.js 和 Express.js 的网关服务,它是一个简易的、健壮的、易拓展的网关,通过简单配置即可快速启用。
本文将对 Express Gateway(下称 EG) 进行简单的介绍,包括如何安装、配置 EG,同时会给出插件开发的简单说明。
关于如何管理 EG 的用户 Consumers、如何将开发的插件发布为 NPM 包,不会在本文进行详细介绍,后续进一步深入研究、给出相关文档资料。
Express Gateway 的官方文档链接为:
https://www.express-gateway.io/
2.搭建一个最简网关
下面将使用 EG 搭建一个最简单的网关(什么都不做,只做转发),其中有几个准备条件:
- 准备一个微服务。这里只在本地跑一个服务:
127.0.0.1:3000
,并提供一个GET /local
的路由(可参看本小节末提供的一个简单示例) - 准备一个浏览器。作为客户端请求微服务
- 杀掉端口
8080
的服务(如有)。因为我们的网关要用这个端口,或者配置 EG 监听其他端口
这个网关只要做一件事:把所有客户端来的请求中,前缀为 local
的请求转发到微服务中。作为测试,我们在后面例子中会发出一个请求,格式如下:
GET http://127.0.0.1:8080/local
快速创建一个微服务的方法:
1.安装 Express 安装器:
sudo npm install -g express-generator
2.创建一个 Express 项目:
express test-local
3.安装依赖并运行
yarn
4.新增一个 GET /local
路由(仅适用本例)。在 routes/index.js
中增加:
router.get('/local', function(req, res, next) {
res.render('index', { title: 'Express via EG' });
});
5.启动服务:
yarn start
2.1 安装EG
官方资料:https://www.express-gateway.io/getting-started/#installation
全局安装 express-gateway
命令(可能要加 sudo
):
npm install -g express-gateway
找一个干净的目录创建一个 EG 网关项目:
eg gateway create
使用基本配置:
➜ eg gateway create
? What is the name of your Express Gateway? my-gateway
? Where would you like to install your Express Gateway? my-gateway
? What type of Express Gateway do you want to create? (Use arrow keys)
Getting Started with Express Gateway
❯ Basic (default pipeline with proxy)
进入目录、运行网关:
cd my-gateway && npm start
如果不出意外,你会在控制台看到:
如果要跟着官方介绍走,则选择 “Getting Started with Express Gateway”
2.2 基本目录结构
EG 的基本目录结构如下:
.
├── config // 配置
│ ├── models // 模型
│ │ ├── applications.json
│ │ ├── credentials.json
│ │ └── users.json
│ ├── gateway.config.yml // 网关配置文件
│ └── system.config.yml // 系统配置文件
├── server.js // 服务启动文件
└── package.json
我们需要处理的文件是 gateway.config.yml
,初始内容为:
http:
port: 8080
admin:
port: 9876
host: localhost
apiEndpoints:
# see: http://www.express-gateway.io/docs/configuration/gateway.config.yml/apiEndpoints
serviceEndpoints:
# see: http://www.express-gateway.io/docs/configuration/gateway.config.yml/serviceEndpoints
policies:
- basic-auth
- cors
- expression
- key-auth
- log
- oauth2
- proxy
- rate-limit
pipelines:
# see: https://www.express-gateway.io/docs/configuration/gateway.config.yml/pipelines
其中,http
标识了 EG 服务以 http
协议启动后,将监听的端口号为 8080
。如果要监听其他端口,可以修改这个字段。
2.3 添加一个微服务endpoint声明
这一步主要是为了声明网关可以管理的微服务 endpoints,并为每一个 endpoint 命名。在 gateway.config.yml
文件中找到 serviceEndpoints
字段,并在里面添加一个成员:
serviceEndpoints:
local-service:
url: 'http://127.0.0.1:3000'
注意,serviceEndpoints
字段是 Object
类型,直接将 endpoint 添加进去即可。我们将已启动的本地服务(127.0.0.1:3000
)命名为 local-service
,作为 serviceEndpoints
的一个 key。
2.4 添加一个客户端endpoint声明
这一步并不是指定具体的客户端(因为可以是任何设备、任何软件)。这里指定的是客户端发出 http/https 请求后,EG要处理它们的依据,本例中,我们定义所有前缀为 local
的请求(/local
或 /local/*
) 都被划分为 client-1
。
在 gateway.config.yml
文件中找到 apiEndpoints
字段,并在里面添加一个成员:
apiEndpoints:
client-demo:
paths: ["/local", "/local/*"]
注:
paths
字段可以是String
或Array<String>
,详见后面说明或官方文档
2.5 创建一个pipeline串联客户端与微服务
前面已经定义好了微服务 local-service
及 客户端 client-demo
,还需要通过一个管道把它们串联起来。而这个管道就是我们可以”做文章“的地方。不过在本例中,我们先不做任何处理,仅仅是把微服务和客户端串起来。
在 gateway.config.yml
文件中找到 pipelines
字段,我们命名这个管道名称为 local-pipeline
:
pipelines:
local-pipeline:
apiEndpoints:
- client-demo
policies:
- proxy:
- action:
serviceEndpoint: local-service
changeOrigin: true
上面的几个字段说明如下:
local-pipeline
:类型为String
,标识管道名称,可以创建多个管道apiEndpoints
:类型为Array
,标识了这个管道的客户端 endpoint,使用前面apiEndpoints
字段定义的名称;如果有多个,可以依次添加(注意这里是数组)policies
:类型为Array
,声明了这个管道要使用的策略。上述例子中声明了这个管道只使用了一个策略 Policy:proxy
;它的作用是代理/转发请求,这里指定了将 apiEndpoints 来的请求转发到前面定义了的微服务local-service
去,同时使用原始的 Headers。
这里的
changeOrigin
是以 EG 的角度来看的。从 EG 角度来看,EG 向微服务发出请求,应当使用 EG 自己的 Headers,因此,不论 api endpoint 的 Headers 是什么,如果 EG 向微服务转发请求使用的 Headers 是 EG 自己的,那么就没有改变(changeOrigin: false
);反之,如果要 EG 在转发请求的时候使用 api endpoint 的 Headers,那么对于 EG 而言是改变了 Headers的,因此,上述例子中必须定义changeOrigin: true
。
2.6 运行和测试
启动 EG:
yarn start
不出意外的话,可以在控制台看到这样的打印:
在浏览器中,输入 http://localhost:8080/local
,可以看到以下内容:
3.概念
3.1 基本概念
通过上面的例子,我们已经把大部分 EG 的概念都说了一遍,现在我们再重新看一次:
字段 | 名称 | 说明 |
---|---|---|
endpoints | 端点 | 在 EG 中,端点即 url;EG 提供了两类端点:api 端点和服务端点。EG 通过 api 端点暴露出 api,同时会将来自 api 端点的请求代理到服务端点 |
policies | 策略 | EG 的策略是一组条件conditions、操作action和参数parameters,它(们)在 EG 中承担着评估、响应指定的请求的责任。可以把策略看成 Express 的中间件 |
pipelines | 管道 | 管道是一组连接 API 端点的策略。一个 API 请求会被 API 端点接收并转发给管道,依次检查管道里的策略会、符合条件的会被执行。一般情况下,最后一个策略是代理策略(Proxy Policy),它会把这个请求转发到一个微服务 |
consumers | 消费者 | 一个消费者发起 API 请求并消费这个 API 请求的响应。在 EG 中,消费者代表用户及其应用;EG 提供了一个高可伸缩的消费者管理模块,每一个 app 都必须属于一个用户 |
credentials | 凭证 | credentials 是身份验证和授权的类型。在 EG 中,一个消费者(一个用户或一个应用)可以被授予一个或多个凭证,应用则可以拥有独立于它们关联用户的凭证。每一个凭证指明了一组用于授权的范围 |
scopes | 范围 | 范围是用于分配授权的标签。 范围是全局定义的,然后分配给API端点和凭据。 如果API使用者尝试访问带有范围标记的API端点,则保护端点的授权策略将查找凭据以确保使用者具有相应的范围 |
官方文档地址: https://www.express-gateway.io/docs/core-concepts/
3.2 对概念的理解
首先是 API 端点,可以看作客户端或其他外部服务过来的请求,这些会按照一定的规则分类(比如上文例子中前缀为 /local
的请求被划分成一类请求,命名为 client-demo
,这是一个 API 端点。
EG 作为一个网关,为 API 端点提供了多个管道(pipeline)贯通到微服务端点(service-endpoint)。
每一个管道都有一组(一个或多个)策略来管理每一个请求,可以对请求进行修改、转发、拦截等操作。策略在 EG 启动的时候进行配置。
请求进入 EG 后,会根据设定进入一个管道(pipeline);每一个管道内都会有一个或多个策略,请求将逐个通过它们;如果一个请求符合某个策略的条件(conditions),则会执行该策略指定的行为(actions),否则会进入下一个策略;执行完后,也一样会进入下一个策略中。
参考资料:
https://www.express-gateway.io/about/
3.3 插件Plugins与策略Policies的关系
仔细阅读官方文档,在插件Plugins的页面中有这么一段:
Note: Existing policies within the Express Gateway core will be eventually refactored out of the core into Express Gateway plugins using this framework.
摘自:https://www.express-gateway.io/docs/plugins/
这意味着,在 EG v1.2.0 之后,由于使用了插件框架,所有的策略都被从 EG 核心移出、并以插件的形式进行了重构。因此,我们如果要自定义一个策略,就必须以插件的形式定义和引入 EG。
我们可以将插件看做一个卡车,卡车上载着一个或多个货箱(策略Policy);在 EG 启动的时候,EG 内核会把所有的卡车召集起来卸货,堆放在不同的管道(pipeline)中。
如果想更了解具体的步骤,可以阅读本文的“运行时Runtime及启动顺序”的“初始化阶段”一节内容。
3.4 运行时Runtime及启动顺序
参考文档:
https://www.express-gateway.io/docs/runtime/boot-sequence/
3.4.1 启动顺序图
3.4.2 初始化阶段
-
初始化配置
EG 会加载所有的配置文件(gateway.config.yml
,system.config.yml
或对应的 json 文件) -
注册插件
EG 会依次加载system.config.yml
文件里plugins
字段声明的插件。通过加载全部插件,EG 可以获取这些插件所提供的所有的策略(Policies)和条件(Conditions)等实体(entities)。
其中,插件通过下面语句加载:
require('plugin-name')(pluginContext)
通过 pluginContext
,插件可以获取所需的上下文。需要注意的是,在这个阶段里,任何策略、条件或中间件都不会执行;同时,任何 http 服务器都不会接受请求。因为在这个阶段,EG 仅仅是在汇总全部的拓展以支持真正的运行阶段
3.4.3 初始化网关
-
初始化网关服务
EG 在这个阶段会首先创建一个线程用于处理所有进入 EG 的请求。 -
加载网关的拓展
EG 会在这个阶段初始化全部已注册的条件、策略、路由和中间件;在此之后,这些实体就能够运用在请求的处理了。 -
初始化管道引擎
所有管道都会被转换为 ExpressJS 的路由,同时注册到 EG 的 app 中。 -
启动网关服务
最终,EG 的 app 启动,并暴露了 HTTP 和 HTTPS 服务,EG 开始监听配置文件里指定的端口号。 -
触发ready事件
在完成了上述步骤后,EG 会主动广播两个服务就绪的消息:http-ready
和https-ready
其中,加载网关的拓展 中:
- 路由和中间件会挂载到 EG 的 app 中
- 条件和策略会被注册到管道的引擎
3.4.4 初始化admin
-
启动admin服务
创建一个用于处理 admin api 请求的线程。 -
加载admin的拓展
初始化已注册的路由和中间件。 -
启动 admin 服务
admin 的 app 会以 node.js 服务的方式暴露,同时监听配置的端口。此后,admin 就可以开始处理相关请求了。 -
触发ready事件
admin 开始监听端口的同时,会广播admin-ready
事件。
3.4.5 热更新
EG 支持热更新即服务会监听变化、不需要手动重启服务
以下操作会触发热更新、不需要手动重启:
- API Endpoints 更改
- Service Endpoints 更改
- Pipeline 更改
以下操作的更改必须手动重启:
- http 字段更改
- https 字段更改
- system.config.yml 文件更改
- models 目录下的 model 更改
4.项目及配置结构
参考资料:
https://www.express-gateway.io/docs/runtime/project-directory/
4.1 基本结构
my-gateway
├── config // EG的配置文件
│ ├── gateway.config.yml
│ ├── models
│ │ ├── applications.js
│ │ ├── credentials.js
│ │ └── users.js
│ └── system.config.yml
├── server.js // EG 的启动文件
└── package.json
4.2 基本语法
yml 格式的配置数值基本语法如下:
${ENV_VARIABLE_NAME:-DEFAULT_VALUE}
其中:
ENV_VARIABLE_NAME
表示如果定义了这个环境变量,则使用它DEFAULT_VALUE
:如果没有定义指定的环境变量,则使用这个默认值
举个例子:
http:
port: ${HTTP_PORT:-8080}
apiEndpoints:
customers:
host: customers.company.com
orders:
host: orders.company.com
本例中,如果环境变量定义了 HTTP_PORT
则 http 监听的端口号使用环境变量的值,否则监听 8080
端口。
4.3 gateway.config.yml
4.3.1 http
该字段用于配置 EG 的 http 服务。其基本格式如下:
http:
port: 9080 # EG will listen for http requests on port 9080
hostname: localhost
参数说明:
参数名 | 说明 |
---|---|
port |
要监听的 http 端口号 |
hostname |
要监听的 http 服务 |
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/http/
4.3.2 https
该字段用于配置 EG 的 https 服务。其基本格式如下:
https:
port: 9443
hostname: localhost
tls:
"*.demo.io":
key: example/keys/demo.io.key.pem
cert: example/keys/demo.io.cert.pem
"api.acme.com":
key: example/keys/acme.com.key.pem
cert: example/keys/acme.com.cert.pem
"default":
key: example/keys/eg.io.key.pem
cert: example/keys/eg.io.cert.pem
参数说明:
参数名 | 说明 |
---|---|
port |
要监听的 http 端口号 |
hostname |
要监听的 http 服务 |
tls |
密钥和证书 |
其中,EG 支持 TLS,包括 SNI。tls
字段可以指定一个或多个域名使用的密钥和证书;每个 tls
通过 key
字段来指定密钥文件的路径、通过 cert
来指定证书文件路径。
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/https/
4.3.3 admin
该字段用于配置 EG 的 admin 服务。其基本格式如下:
http:
host: localhost
port: 9080 # EG will listen for http requests on port 9080
参数说明:
参数名 | 说明 |
---|---|
host |
监听的hostname |
port |
监听端口号 |
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/admin/
4.3.4 apiEndpoints
EG 通过 URLs 将微服务作为 API 暴露给外部,这些 URLs 就是 apiEndpoints。API 使用者可以 apiEndpoints 发出请求,当 apiEndpoints 被绑定在管道中、与微服务(serviceEndpoints) 连通后,外部才可以访问微服务(仍然要经过一些策略和动作)。
下面例子中定义了几个 apiEndpoints:help
,api
,example
及 example2
:
apiEndpoints:
help: # name, used as reference in pipeline
host: '*' # required, '*' will not check for host name
paths: /help # optional, by default will serve all requests - same as *
api: # name, used as reference in pipeline
host: '*.com' # wildcard pattern support
paths:
- '/v1/*' # string or array of strings
- '/v2/*'
example: # name, used as reference in pipeline
host: 'example.com'
paths: /v2/* # string or array of strings
example2: # It is possible to provide an array of matching conditions in the same apiEndpoint
- host: 'example2.com'
paths: /v2/*
methods: ["GET", "OPTIONS"]
scopes: ["example2:read"]
- host: 'example2.com'
paths: /v2/*
methods: ["PUT", "POST", "PATCH", "DELETE"]
scopes: ["example2:write"]
参数说明:
参数名 | 说明 |
---|---|
host |
监听的hostname,类型为 String 。即请求头 Headers 的 HOST 参数,如果不设置则默认为 * ,即接收所有host |
paths |
指定接收的路径,类型为 Array<String> 或 直接使用 String 。它会对来自 host 的请求 URLs 进行过滤 |
methods |
指定接收的请求类型,类型为 Array<String> 。数组内元素可以是 GET ,POST ,PUT ,DELETE 等的一个或多个,也可以不指定(即全部接收、不进行过滤) |
scopes |
操作范围,类型为 Array<String> 。标志请求允许操作的范围 |
4.3.4.1 例1:任意host和指定path
下面例子中,指定了host
为 *
、path
为 /help
:
apiEndpoints:
help:
host: '*'
paths: /help
此例中,没有在请求头 Headers 中设置 HOST
的请求会报 404
,路径不是 /help
的请求也会报 404
:
// Passed
linxiaozhou.com/help
easypm.linxiaozhou.com/help
linxiaozhou.cn/help
// 404
linxiaozhou.com
easypm.linxiaozhou.com/contact
easypm.linxiaozhou.com/help/docs
linxiaozhou.com/help # Headers Not set HOST
4.3.4.2 例2:指定host和指定path
下面例子中,指定了host
为 linxiaozhou.com
、path
为 /help
:
apiEndpoints:
help:
host: 'linxiaozhou.com'
paths: /help
则所有 host
不是 linxiaozhou.com
或路径不是 /help
的请求会报 404
(没有在请求头 Headers 中设置 HOST
的请求也会报 404
后续不再说明):
// Passed
linxiaozhou.com/help
// 404
easypm.linxiaozhou.com
easypm.linxiaozhou.com/help
easypm.linxiaozhou.com/contact
easypm.linxiaozhou.com/help/docs
4.3.4.3 例3:指定host子域名及路径path
有下面的配置:
apiEndpoints:
help:
host: '*.linxiaozhou.com'
paths: /help
如果一个请求的 host 不含有子域名,会报 404
;如果路径没有 help
,也会报 404
:
// Passed
easypm.linxiaozhou.com/help
// 404
linxiaozhou.com/help
easypm.linxiaozhou.com
easypm.linxiaozhou.com/contact
easypm.linxiaozhou.com/help/docs
4.3.4.4 关于path
明确指定路径:
paths: /admin
- Passed:
/admin
- 404:
/admin/bob
,/admin/charlie/1
,/staff
一定要区分
/admin/*
的区别
使用通配符:
paths: /admin/*
- Passed:
/admin/bob
,/admin/charlie/1
- 404:
/admin
合并上面两种情况:
paths: ['/admin','/admin/*']
- Passed:
/admin
,/admin/bob
,/admin/charlie/1
- 404:
/staff
大部分的场景下,应该合并这两种情况
包含变量:
paths: /admin/:id
- Passed:
/admin/bob
,/admin/charlie
- 404: /admin; /staff
4.3.4.5 关于顺序
下面例子中,会按顺序匹配:先检查是否匹配 tabby
,如果不符合则检查是否匹配 cat
;如果不匹配 cat
,则检查是否匹配 com
:
apiEndpoints:
tabby:
host: '*.tabby.cat.com'
paths: '*' # optional, if not specified will default to *
cat:
host: '*.cat.com'
com:
host: '*.com'
但如果我们改为下面的方式,则永远不可能进入 tabby
和 cat
,因为所有符合它们的请求都符合 com
的要求、全部进入了 com
里面:
# 大误
apiEndpoints:
com:
host: '*.com'
tabby:
host: '*.tabby.cat.com'
paths: '*' # optional, if not specified will default to *
cat:
host: '*.cat.com'
因此,我们建议把更具体的 host 放在前面,否则会被前面模糊匹配的拦截。
原始资料
https://www.express-gateway.io/docs/configuration/gateway.config.yml/apiEndpoints/
4.3.5 serviceEndpoints
通过 apiEndpoints 接收到的请求,会经过管道并最终代理到指定的微服务中。serviceEndpoints 指定了网关代理的微服务节点的信息:
serviceEndpoints: # downstream microservices
cats: # name, used as a reference in pipeline
url: "http://cats1.example.com"
dogs: # name, used as a reference in pipeline
urls: # multiple urls can be used with load balancing
- "http://dogs1.example.com"
- "http://dogs2.example.com"
参数说明:
参数名 | 说明 |
---|---|
url |
微服务的协议及名称,类型为 String 。例如 https://www.linxiaozhou.com |
urls |
一组微服务的协议及名称,类型为 Array<String> 。例如 ["https://cdn1.linxiaozhou.com", "https://cdn2.linxiaozhou.com"] ,可用于负载均衡 |
proxyOptions |
使用代理策略的选项 |
4.3.6 policies
策略白名单,任何需要在 EG 服务中使用的策略都必须在此声明。例如:
policies:
- cors
- rate-limiter
- log
- proxy
- oauth2
- key-auth
- basic-auth
policies
是一个数组,每个元素为策略的名称。这个名称对应了node_modules/express-gateway/lib/policies/
目录下的文件夹名称:
当我们自定义了一个插件、里面会有对应的策略,则首先要将这个插件在 system.config.yml
文件的 plugins
字段引入,然后将策略的名称写入 gateway.config.yml
文件的 policies
字段。
关于自定义插件策略的名称
我们在编写策略的时候,就指定了这个策略的名称:
module.exports = { name: 'logger', schema: { $id: 'N/A', }, policy: (actionParams) => { // ... } };
4.3.7 pipelines
管道指定了 EG 的核心操作,它像一条管道一样连通了上面定义的 apiEndpoints
和 serviceEndpoints
。所有来自 apiEndpoints
的请求都将依次执行管道内定义的策略,然后进入代理的 serviceEndpoints
。
下面例子中,定义了一个名为 default
的管道,它会把 api
(在 apiEndpoints
定义的 API 端点) 的请求流入一个策略 simple-logger
后代理给 example
(在 serviceEndpoints
定义的微服务):
http:
port: 3000
serviceEndpoints:
example: # will be referenced in proxy policy
url: 'http://example.com'
apiEndpoints:
api:
host: '*'
paths: /
pipelines:
default:
apiEndpoints:
- api
policies:
-
simple-logger: # policy name
- # array of objects with condition\action properties
condition: #optional,; defaults to always execute
name: pathExact
path: /v1
action:
message: "${req.method} ${req.originalUrl}"
-
proxy: # policy name
- # array of objects with condition\action properties
action:
serviceEndpoint: example # see declaration above
在管道中,会依次列出每一个策略 Policy,每个策略的结构包含两个字段:
condition
:条件,可不填。只有满足条件的请求才会触发下面的动作action
;如果不填,则直接执行动作。action
:动作,这个策略具体要执行的动作。具体动作需要传入什么参数和对应数值,与具体的策略有关,需要阅读策略的说明来决定
如何设置
condition
,可参看这份说明
https://www.express-gateway.io/docs/policies/customization/conditions/
关于 action
,以策略 Proxy
为例,它可以接收的参数如:
更详细的说明,可参看这个链接:https://www.express-gateway.io/docs/policies/proxy/
4.4 system.config.yml
system.config.yml
定义了网关的系统级配置和全局参数,它描述了网关的基础设置内容。
4.4.1 db
该字段定义了 EG 存储数据的方式。目前 EG 支持两种方式:
- 内存
- Redis
4.4.1.1 内存数据库
使用内存数据库需要注意以下特性:
- 每次重新启动时都会重置数据
- 热启动不会销毁数据
由于它不需要使用 Redis 来管理,使用简单方便,可以在开发和演示的时候来使用;但由于上述特性,它并不适合生产环境中使用,Redis 才是最佳选择。
使用内存数据库时,将 emulate
置为 true
即可:
db:
redis:
emulate: true
namespace: EG
4.4.1.2 Redis数据库
使用 Redis 数据库当然要先启动 Redis 服务,这也是应用在生产环境的选择。
db:
redis:
host: localhost
port: 6379
namespace: EG
更多关于 redis 的配置参数,可以阅读以下资料:
https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options
4.4.1.3 哨兵和集群
EG 使用 ioredis 连接到 Redis 实例,该实例支持连接到Cluster和Sentinel,但 EG 目前还不支持连接集群的功能。
如果想连接到 Sentinel 实例,只需修改配置文件即可:
db:
redis:
sentinels:
- host: 'localhost'
port: 26379
- host: 'localhost'
port: 26380
name: mymaster
namespace: EG
更多关于哨兵的配置资料,可阅读下面资料:
https://github.com/luin/ioredis#sentinel
更多 ioredis 的资料,可阅读下面资料:
https://github.com/luin/ioredis
4.4.2 plugins
plugins
字段声明了所需要用到的全部插件,里面包含了可用的策略、条件、自定义路由及中间件。
声明一个插件基本格式为:
plugins:
example:
description: some helpful information for yml file maintainer
param1: 'this is plugin initialization parameter'
声明一个自定义插件的基本格式为:
plugins:
express-gateway-plugin-logger:
param1: 'logger-plugin'
package: "./plugins/logger/manifest.js"
更多关于插件的安装和开发资料,可阅读下面的“插件”章节。
4.4.3 crypto
EG 不会保存明文密码、会将它们转换为hash散列。crypto
字段即声明了转换的规则:
crypto:
cipherKey: sensitiveKey
algorithm: aes256
saltRounds: 10
关于加密的方式,可以阅读:
https://www.npmjs.com/package/bcrypt
4.4.4 session
EG 本身不需要 session 即可工作,只有在使用内置的 OAuth 2.0 认证时候会使用到 session。EG 对 session的支持基于 express-session。
默认使用内存管理 session,例如:
session:
secret: keyboard cat # replace with secure key that will be used to sign session cookie
resave: false
saveUninitialized: false
基于 Redis 的 session 管理(需要运行 redis 并安装 npm i connect-redis
):
session:
storeProvider: connect-redis
storeOptions:
host: localhost
port: 6379
secret: keyboard cat # replace with secure key that will be used to sign session cookie
resave: false
saveUninitialized: false
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/session/
4.4.5 accessTokens
OAuth 2.0 相关配置,可以配置访问令牌(Access Token) 的参数,例如:
accessTokens:
timeToExpiry: 7200000
tokenType: 'jwt'
issuer: 'express-gateway'
audience: 'something'
subject: 'somebody'
secretOrPrivateKey: 'ssssst'
timeToExpiry
:令牌的到期时间tokenType
:要发行的令牌类型。 它可以是不透明的或jwt
issuer
:当tokenType
不是jwt
时被忽略。 定义要在令牌中发送的发行者audience
:当tokenType
不是jwt
时被忽略。 定义要发送给令牌的受众subject
:当tokenType
不是jwt
时被忽略。 定义要在令牌中发送的主题secretOrPrivateKey
:当tokenType
为jwt
时被忽略。 定义用于对令牌进行签名的私钥或私钥secretOrPrivateKeyFile
:当tokenType
为jwt
时被忽略。 定义用于存储令牌签名的私钥或私钥的文件
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/accessTokens/
4.4.6 refreshTokens
OAuth 2.0 相关配置,可以配置刷新 token 的时间间隔。例如:
refreshTokens:
timeToExpiry: 7200000
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/refreshTokens/
4.4.7 authorizationCodes
OAuth 2.0 相关配置,
authorizationCodes:
timeToExpiry: 300000
更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/authorizationCodes/
4.5 models
本文不对 admin 进行介绍,更多资料可阅读:
https://www.express-gateway.io/docs/configuration/system.config.yml/session/
5.插件
5.1 插件基础
前面提过,策略必须以插件的形式引入 EG 框架中。但需要注意的是,一个插件里,可以引入多个策略;不仅是策略,它还可以放置条件、自定义的路由及中间件。
简而言之,一个插件就是一个容器,它可以存放实体,同时可以接收事件。这里的“实体”包括三类:
- 策略Policies
- 条件Conditions
- 自定义路由及中间件
参考文档:
https://www.express-gateway.io/docs/plugins/
5.1.1 通过CLI安装插件
通过eg plugin install __name__
命令安装。安装成功后,会在 system.config.yml
文件中的 plugins
字段中增加插件的描述,例如:
plugins:
example: # "express-gateway-plugin-example"
param1: 'global per plugin param1'
5.1.2 通过NPM安装插件
通过 yarn add __name__
安装,然后在 config/system.config.yml
文件的 plugins
字段下添加包名称来引入
5.1.3 引入自己编写插件
按照规范在项目代码中添加文件夹、文件,编写好插件后,在 config/system.config.yml
文件的 plugins
字段下添加插件的相对路径来引入,例如:
plugins:
express-gateway-plugin-logger:
param1: 'logger-plugin'
package: "./plugins/logger/manifest.js"
5.2 插件开发
5.2.1 编写一个插件
前文提到,插件是一个“容器”,它用于装载策略、条件、路由及中间件。因此开发一个插件的关键在于开发它的装载的实体。
接下来,我将以装载一个策略为例,说明如何编写一个插件,并引入我们的 EG 服务中。
官方资料:
https://www.express-gateway.io/docs/plugins/plugin-development/
5.2.1.1 目录结构
下面给出了我们自定义插件的位置及目录:
.
├── config
│ ├── models
│ │ ├── applications.json
│ │ ├── credentials.json
│ │ └── users.json
│ └── sources
│ ├── gateway.config.yml
│ └── system.config.yml
├── plugins // 插件
│ └── logger
│ ├── conditions
│ ├── routes
│ ├── policies
│ │ └── logger.policy.js
│ └── manifest.js
└── server.js
在 plugins
目录中,我们定义了一个名为 logger
的目录(即插件名称),里面的几个文件(夹)分别是:
logger
├── conditions // 存放条件文件
├── routes // 存放路由及中间件文件
├── policies // 存放策略文件
│ └── logger.policy.js // 其中一个策略
└── manifest.js // 本插件的入口文件
5.2.1.2 定义一个策略
logger.policy.js
文件内容为:
module.exports = {
name: 'logger',
schema: {
$id: 'N/A',
type: 'object',
properties: {
param: {
type: 'string',
default: 'log request'
}
}
},
policy: (actionParams) => {
return (req, res, next) => {
console.log('executing policy-from-logger-plugin with params', actionParams);
// Write log here
// ...
next();
};
}
};
5.2.1.3 插件入口文件
manifest.js
文件内容如下:
module.exports = {
version: '1.2.0',
schema: {
$id: 'N/A',
},
init: function (pluginContext) {
// 注册自定义策略 Logger
const policy = require('./policies/logger.policy')
pluginContext.registerPolicy(policy)
// 热更新后触发该事件
pluginContext.eventBus.on('hot-reload', function ({ type, newConfig }) {
console.log('[PLUGINS.LOGGER] hot-reload', type, newConfig);
});
// http就绪时触发该事件
pluginContext.eventBus.on('http-ready', function ({ httpServer }) {
console.log('[PLUGINS.LOGGER] http server is ready', httpServer.address());
});
// https就绪时触发该事件
pluginContext.eventBus.on('https-ready', function ({ httpsServer }) {
console.log('[PLUGINS.LOGGER] https server is ready', httpsServer.address());
});
// admin就绪时触发该事件
pluginContext.eventBus.on('admin-ready', function ({ adminServer }) {
console.log('[PLUGINS.LOGGER] admin server is ready', adminServer.address());
});
}
}
5.2.1.4 引入插件
在 system.config.yml
文件中,声明刚刚编写的插件:
plugins:
express-gateway-plugin-logger:
param1: 'logger'
package: "./plugins/logger/manifest.js"
5.2.1.5 将策略加入白名单
引入插件后,仅仅是将策略、条件、路由及中间件引入了 EG,如果要使用策略,还需要在 gateway.config.yml
文件中加入策略白名单:
policies:
- cors
- rate-limiter
- log
- proxy
- oauth2
- key-auth
- basic-auth
- logger # 加入我们自定义的logger策略
注意要添加的文件 不是 系统配置文件
system.config.yml
5.2.1.6 在管道中使用策略
在完成上述步骤后,即可使用定义的 logger 策略,例如:
pipelines:
default:
apiEndpoints:
- api
policies:
-
logger: # 我们定义的logger策略
action:
param: "${req.method} ${req.originalUrl}"
-
proxy: # Proxy策略
action:
serviceEndpoint: example
5.2.1.7 编写插件包
上面介绍了编写一个本地的插件的步骤,但距离发布一个可用的插件包还有一定距离。如果想了解更多,可以查看官方提供的一个插件示例:
https://github.com/ExpressGateway/express-gateway-plugin-example
5.2.2 策略开发
5.2.2.1 基本结构
策略是 Express.js 中间件的包装,其基本结构为:
module.exports = {
name: 'logger',
schema: {
$id: 'N/A',
type: 'object',
properties: {
param: {
type: 'string',
default: 'log request'
}
}
},
policy: (actionParams) => {
return (req, res, next) => {
console.log('executing policy-from-logger-plugin with params', actionParams);
// do something here
// ...
next();
};
}
};
其中:
name
:管道通过这个名称找到这个策略schema
:描述了管道在执行这个策略时要提供进来的参数列表;需要注意的是,$id
可以是这个 schema 的 url(如http://express-gateway.io/schemas/policies/example-policy.json ),也可以不提供 url,但必须显式地声明为N/A
,否则会报错。policy
:返回 Express.js 中间件的一个函数。它的输入参数actionParams
,是引用该策略的管道传入的配置信息,其数据格式要遵循schema
定义的数据结构。
5.2.2.2 举例
我们将本文最开始的例子中,policy
函数做一点修改,改为重新请求的url(原来什么都没做):
module.exports = {
name: 'logger',
schema: {
$id: 'N/A',
type: 'object',
properties: {
param: {
type: 'string',
default: 'log request'
}
}
},
policy: (actionParams) => {
return (req, res, next) => {
console.log('executing policy-from-logger-plugin with params', actionParams);
// Rewrite url
req.url = req.url.replace('/local', '/');
next();
};
}
};
则在成功加载后,我们访问 http://localhost:8080/local
将不再看到最初的内容:
而会变成下面的内容。因为我们把请求 url 中的 local
截断了,实际到达我们的 local 微服务的请求变成了 http://localhost:8080/
5.2.2.3 注册到插件
策略开发完后,需要在插件中注册,这样在插件声明和引入后,策略才能被正常加载进去:
module.exports = {
version: '1.2.0',
init: function (pluginContext) {
const policy = require('./policies/logger.policy')
pluginContext.registerPolicy(policy)
}
}
5.2.2.4 添加白名单
此外,还需要在 gateway.config.yml
文件中将策略添加进白名单:
policies:
# 其他策略
- logger # 加入我们自定义的logger策略
https://www.express-gateway.io/docs/plugins/policy-development/
5.2.2.5 在管道中使用
在管道的配置中,添加对应的策略:
pipelines:
default:
apiEndpoints:
- api
policies:
-
logger: # 我们定义的logger策略
action:
param: "${req.method} ${req.originalUrl}"
# 其他策略
5.2.2.6 Trouble Shooting
如果插件的初始化方法出错,则可能会报下面的错误:
如果没有在白名单中声明策略,会报以下错误:
5.2.3 条件开发
简单来说,条件 condition 就是管道判断要不要执行某个策略的依据。因此,我们一般会把一个条件放在策略里面、放在具体的 action
之前,例如:
pipelines:
apiPipeline:
apiEndpoints:
- api
policies:
- example:
-
condition: # 符合条件的请求才会执行action
name: 'url-match'
expected: '/test'
action:
baseUrl: 'https://example.com'
而条件 condition 的基本结构如下:
module.exports = {
name: 'url-match',
schema: {
$id: 'http://express-gateway.io/schemas/conditions/url-match.json',
type: 'object',
properties: {
expected: {
type: 'string'
}
},
required: ['expected']
},
handler: conditionConfig => req => conditionConfig.expected === req.url
};
其中:
name
:管道通过它找到这个条件schema
:描述了管道在执行这个条件时要提供进来的参数列表;需要注意的是,$id
可以是这个 schema 的 url(如http://express-gateway.io/schemas/conditions/url-match.json ),也可以不提供 url,但必须显式地声明为N/A
,否则会报错handler
:它是一个函数,用于接受Express.js 请求的对象和条件参数,必须返回true
或false
,表示是否满足条件;返回true
则会继续执行下一个条件或执行 action,返回false
表示不满足条件,将不会执行。输入参数是管道中引用这个条件的所有配置选项
条件需要在插件中注册,方法是:
module.exports = {
version: '1.2.0',
init: function (pluginContext) {
const condition = require('./conditions/url-match.js')
pluginContext.registerCondition(condition)
}
}
官方资料:
https://www.express-gateway.io/docs/plugins/condition-development/
5.2.4 路由开发
EG 运行了两个 Express.js 应用,分别是网关和admin;因此我们开针对两个应用的路由进行管理。路由的开发细节不会在本文详细介绍,如需了解更多,可阅读官方文档:
https://www.express-gateway.io/docs/plugins/condition-development/
6.CLI
EG 提供了命令来管理,例如我们在最开始创建 EG 的命令:
eg gateway create
比如,列出全部用户:
# 列出全部用户
eg users list
EG 支持的模块有:
命令模块 | 模式 | 别名 |
---|---|---|
users |
Manage users | user |
apps |
Manage apps | app |
scopes |
Manage scopes | scope |
credentials |
Manage credentials | credential |
credential:scopes |
Manage scopes for credentials | credential:scope |
tokens |
Manage tokens | token |
gateway |
Manage a gateway | 无 |
plugins |
管理插件 | 无 |
7.官方插件资源 Resources
EG 提供了一些基本的资源(策略),我们可以直接使用以减少开发工作。
7.1 用法介绍
以最简单的 logger
为例,我们要在一个管道中增加这个策略。首先,我们要在 gateway.config.yml
的 policies
字段中将 logger
加入:
policies:
# 加入该策略
- logger
然后阅读文档,了解该策略需要的输入参数:
https://www.express-gateway.io/docs/policies/log
将策略加入需要使用的管道中:
pipelines:
local-pipeline:
apiEndpoints:
- client-demo
policies:
- logger:
action:
param: "Logger"
- log: # 使用该策略
- action:
message: ${req.method} ${req.originalUrl} # parameter for log action
- proxy:
- action:
serviceEndpoint: local-service
changeOrigin: true
最后进行测试:
7.2 认证相关Authentication
Basic Auth
Username and Password
https://www.express-gateway.io/docs/policies/basic-authorization/
Key Auth
API keys for users and applications
https://www.express-gateway.io/docs/policies/key-authorization/
OAuth2
Authentications and Authorization Flows
- https://www.express-gateway.io/docs/policies/oauth2/
- https://www.express-gateway.io/docs/policies/oauth2-introspection/
JWT
JSON Web Token issuing and validation
https://www.express-gateway.io/docs/policies/jwt/
7.3 自定义Customization
Expression
Execute JavaScript within the egContext object
https://www.express-gateway.io/docs/policies/expression/
Request Transformer
Modify header or body in the request message
https://www.express-gateway.io/docs/policies/request-transformer/
Response Transformer
Modify header or body in the response message
https://www.express-gateway.io/docs/policies/response-transformer/
7.4 安全 Security
CORS
Cross Origin Resource Sharing
https://www.express-gateway.io/docs/policies/cors/
7.5 Serverless
Lambda
Proxy to AWS Lambda Functions
https://www.express-gateway.io/docs/policies/lambda/
7.6 日志管理 Quality of Service
Simple Logger
Logging Format and Output
https://www.express-gateway.io/docs/policies/log/
Load Balancer Proxy
Load Balance and Proxy to Services
https://www.express-gateway.io/docs/policies/proxy/
Rate Limiter
Throttle API Requests
https://www.express-gateway.io/docs/policies/rate-limiter/
7.7 工具 Utilities
Rewrite
Rewrite URL or Redirect Requests
https://www.express-gateway.io/docs/policies/rewrite/
Terminate
Terminate a request with a status code and message
https://www.express-gateway.io/docs/policies/terminate/
8.更多
8.1 yml即json
例如,本文最开始示例的两个文件分别可以用 json 格式书写。你可以在 config
目录下分别创建 system.config.yml
和 gateway.config.yml
同名文件,同时将原来的 .yml
文件删除。
如果同时存在
.yml
和.json
是同名文件,EG 会优先使用.json
文件!
system.config.yml
-> system.config.json
,内容为:
{
"db": {
"redis": {
"emulate": true,
"namespace": "EG"
}
},
"plugins": {
"express-gateway-plugin-logger": {
"param": "Logger",
"package": "./plugins/logger/manifest.js"
}
},
"crypto": {
"cipherKey": "sensitiveKey",
"algorithm": "aes256",
"saltRounds": 10
},
"session": {
"secret": "keyboard cat",
"resave": false,
"saveUninitialized": false
},
"accessTokens": {
"timeToExpiry": 7200000
},
"refreshTokens": {
"timeToExpiry": 7200000
},
"authorizationCodes": {
"timeToExpiry": 300000
}
}
gateway.config.yml
-> gateway.config.json
,内容为:
{
"http": {
"port": "${GATEWAY_PORT:-8080}"
},
"admin": {
"port": 9876,
"host": "localhost"
},
"apiEndpoints": {
"demo-service": {
"host": "*"
}
},
"serviceEndpoints": {
"demo-service": {
"url": "${DEMO_SERVICE_ENDPOINT:-http://localhost:3000}"
}
},
"policies": [
"basic-auth",
"cors",
"expression",
"key-auth",
"log",
"oauth2",
"proxy",
"logger",
"rate-limit"
],
"pipelines": {
"demo-service": {
"apiEndpoints": [
"demo-service"
],
"policies": [
{
"logger": {
"action": {
"param": "log request"
}
}
},
{
"proxy": [
{
"action": {
"serviceEndpoint": "demo-service",
"changeOrigin": true
}
}
]
}
]
}
}
}
8.2 yml的锚点Anchor
yml的锚点 Anchor 是一个很高效的语法,它可以减少很多重复工作,减少代码的冗余。这一点是 json 文件无法比拟的。
下面代码定义了两个管道 customers
和 orders
:
http:
port: 8080
apiEndpoints:
customers:
host: customers.company.com
orders:
host: orders.company.com
serviceEndpoints:
customers:
url: 'http://customers'
orders:
url: 'http://orders'
policies:
- jwt
- proxy
- rate-limit
pipelines:
customers:
apiEndpoints:
- customers
policies:
- rate-limit:
- action:
max: 1
windowMs: 1000
- jwt:
- action:
secretOrPublicKeyFile: ./key/pubKey.pem
checkCredentialExistence: false
- proxy:
- action:
serviceEndpoint: customers
changeOrigin: true
orders:
apiEndpoints:
- orders
policies:
- rate-limit:
- action:
max: 1
windowMs: 1000
- jwt:
- action:
secretOrPublicKeyFile: ./key/pubKey.pem
checkCredentialExistence: false
- proxy:
- action:
serviceEndpoint: orders
changeOrigin: true
可以看到,两个管道的策略 policies 基本上是一致的,显得非常冗余。为了减少冗余,我们可以在第一个管道的配置信息中设置了两个锚点 &jwt
和 &proxy
:
- rate-limit: &rate-limit
- action:
max: 1
windowMs: 1000
- jwt: &jwt
- action:
secretOrPublicKeyFile: ./key/pubKey.pem
checkCredentialExistence: false
- proxy:
- action: &proxy
serviceEndpoint: customers
changeOrigin: true
在接下来的另一个管道 orders
,我们不需要再写相同的内容,可以直接引用上面的锚点:
policies:
- rate-limit: *rate-limit
- jwt: *jwt
- proxy:
- action:
<<: *proxy
serviceEndpoint: orders
相关规范可阅读该文档:
https://camel.readthedocs.io/en/latest/yamlref.html
9.后记
《Express Gateway 入门》这篇文档是继 《Babel 入门》之后字数最多、查阅资料最频繁的一份资料,经过了近一周的实践、测试、用例编写和修改,花费了五个晚上时间才告一段落。尽管如此,仍然还有一些理解不到位的地方,我也会积极听取大家的意见、不断完善本文。
开篇时已经说明,有一些部分功能不会写入本文,因此到目前为止并不算全面了解了EG;此外 EG 的部署、应用场景还没有做进一步的实践,这距离真正的应用在生产环境也还有一段很长一段路要走。
因此,如果要真正运用好 Express Gateway,我建议大家还应该往以下方面去学习和实践:
- https 的使用
- admin 模块的管理
- 编写和发布插件包
- 编写路由及中间件
- 使用 oAuth 2.0
- EG 应用在个人网站集群
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章