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

upload.png

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

如果不出意外,你会在控制台看到:
upload.png

如果要跟着官方介绍走,则选择 “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,还需要通过一个管道把它们串联起来。而这个管道就是我们可以”做文章“的地方。不过在本例中,我们先不做任何处理,仅仅是把微服务和客户端串起来。
upload.png

在 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

不出意外的话,可以在控制台看到这样的打印:
upload.png


在浏览器中,输入 http://localhost:8080/local,可以看到以下内容:
upload.png

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)。
屏幕快照 2021-01-25 22.23.45.png

每一个管道都有一组(一个或多个)策略来管理每一个请求,可以对请求进行修改、转发、拦截等操作。策略在 EG 启动的时候进行配置。
屏幕快照 2021-01-25 22.24.14.png

请求进入 EG 后,会根据设定进入一个管道(pipeline);每一个管道内都会有一个或多个策略,请求将逐个通过它们;如果一个请求符合某个策略的条件(conditions),则会执行该策略指定的行为(actions),否则会进入下一个策略;执行完后,也一样会进入下一个策略中。
屏幕快照 2021-01-25 22.24.03.png

参考资料:
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 启动顺序图

屏幕快照 2021-01-26 00.11.28.png

3.4.2 初始化阶段

  1. 初始化配置
    EG 会加载所有的配置文件(gateway.config.yml, system.config.yml 或对应的 json 文件)

  2. 注册插件
    EG 会依次加载 system.config.yml 文件里 plugins 字段声明的插件。通过加载全部插件,EG 可以获取这些插件所提供的所有的策略(Policies)和条件(Conditions)等实体(entities)。

其中,插件通过下面语句加载:

require('plugin-name')(pluginContext)

通过 pluginContext,插件可以获取所需的上下文。需要注意的是,在这个阶段里,任何策略、条件或中间件都不会执行;同时,任何 http 服务器都不会接受请求。因为在这个阶段,EG 仅仅是在汇总全部的拓展以支持真正的运行阶段

3.4.3 初始化网关

  1. 初始化网关服务
    EG 在这个阶段会首先创建一个线程用于处理所有进入 EG 的请求。

  2. 加载网关的拓展
    EG 会在这个阶段初始化全部已注册的条件、策略、路由和中间件;在此之后,这些实体就能够运用在请求的处理了。

  3. 初始化管道引擎
    所有管道都会被转换为 ExpressJS 的路由,同时注册到 EG 的 app 中。

  4. 启动网关服务
    最终,EG 的 app 启动,并暴露了 HTTP 和 HTTPS 服务,EG 开始监听配置文件里指定的端口号。

  5. 触发ready事件
    在完成了上述步骤后,EG 会主动广播两个服务就绪的消息:http-ready 和 https-ready

其中,加载网关的拓展 中:

  • 路由和中间件会挂载到 EG 的 app 中
  • 条件和策略会被注册到管道的引擎

3.4.4 初始化admin

  1. 启动admin服务
    创建一个用于处理 admin api 请求的线程。

  2. 加载admin的拓展
    初始化已注册的路由和中间件。

  3. 启动 admin 服务
    admin 的 app 会以 node.js 服务的方式暴露,同时监听配置的端口。此后,admin 就可以开始处理相关请求了。

  4. 触发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:helpapiexample 及 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>。数组内元素可以是 GETPOSTPUTDELETE 等的一个或多个,也可以不指定(即全部接收、不进行过滤)
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.compath 为 /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/ 目录下的文件夹名称:
upload.png

当我们自定义了一个插件、里面会有对应的策略,则首先要将这个插件在 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 为例,它可以接收的参数如:
upload.png
更详细的说明,可参看这个链接: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 将不再看到最初的内容:
upload.png

而会变成下面的内容。因为我们把请求 url 中的 local 截断了,实际到达我们的 local 微服务的请求变成了 http://localhost:8080/
upload.png

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

如果插件的初始化方法出错,则可能会报下面的错误:
upload.png


如果没有在白名单中声明策略,会报以下错误:
upload.png

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
upload.png

将策略加入需要使用的管道中:

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

最后进行测试:
upload.png

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

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.参考资料

阅读(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

upload.png

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

如果不出意外,你会在控制台看到:
upload.png

如果要跟着官方介绍走,则选择 “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,还需要通过一个管道把它们串联起来。而这个管道就是我们可以”做文章“的地方。不过在本例中,我们先不做任何处理,仅仅是把微服务和客户端串起来。
upload.png

在 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

不出意外的话,可以在控制台看到这样的打印:
upload.png


在浏览器中,输入 http://localhost:8080/local,可以看到以下内容:
upload.png

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)。
屏幕快照 2021-01-25 22.23.45.png

每一个管道都有一组(一个或多个)策略来管理每一个请求,可以对请求进行修改、转发、拦截等操作。策略在 EG 启动的时候进行配置。
屏幕快照 2021-01-25 22.24.14.png

请求进入 EG 后,会根据设定进入一个管道(pipeline);每一个管道内都会有一个或多个策略,请求将逐个通过它们;如果一个请求符合某个策略的条件(conditions),则会执行该策略指定的行为(actions),否则会进入下一个策略;执行完后,也一样会进入下一个策略中。
屏幕快照 2021-01-25 22.24.03.png

参考资料:
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 启动顺序图

屏幕快照 2021-01-26 00.11.28.png

3.4.2 初始化阶段

  1. 初始化配置
    EG 会加载所有的配置文件(gateway.config.yml, system.config.yml 或对应的 json 文件)

  2. 注册插件
    EG 会依次加载 system.config.yml 文件里 plugins 字段声明的插件。通过加载全部插件,EG 可以获取这些插件所提供的所有的策略(Policies)和条件(Conditions)等实体(entities)。

其中,插件通过下面语句加载:

require('plugin-name')(pluginContext)

通过 pluginContext,插件可以获取所需的上下文。需要注意的是,在这个阶段里,任何策略、条件或中间件都不会执行;同时,任何 http 服务器都不会接受请求。因为在这个阶段,EG 仅仅是在汇总全部的拓展以支持真正的运行阶段

3.4.3 初始化网关

  1. 初始化网关服务
    EG 在这个阶段会首先创建一个线程用于处理所有进入 EG 的请求。

  2. 加载网关的拓展
    EG 会在这个阶段初始化全部已注册的条件、策略、路由和中间件;在此之后,这些实体就能够运用在请求的处理了。

  3. 初始化管道引擎
    所有管道都会被转换为 ExpressJS 的路由,同时注册到 EG 的 app 中。

  4. 启动网关服务
    最终,EG 的 app 启动,并暴露了 HTTP 和 HTTPS 服务,EG 开始监听配置文件里指定的端口号。

  5. 触发ready事件
    在完成了上述步骤后,EG 会主动广播两个服务就绪的消息:http-ready 和 https-ready

其中,加载网关的拓展 中:

  • 路由和中间件会挂载到 EG 的 app 中
  • 条件和策略会被注册到管道的引擎

3.4.4 初始化admin

  1. 启动admin服务
    创建一个用于处理 admin api 请求的线程。

  2. 加载admin的拓展
    初始化已注册的路由和中间件。

  3. 启动 admin 服务
    admin 的 app 会以 node.js 服务的方式暴露,同时监听配置的端口。此后,admin 就可以开始处理相关请求了。

  4. 触发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:helpapiexample 及 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>。数组内元素可以是 GETPOSTPUTDELETE 等的一个或多个,也可以不指定(即全部接收、不进行过滤)
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.compath 为 /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/ 目录下的文件夹名称:
upload.png

当我们自定义了一个插件、里面会有对应的策略,则首先要将这个插件在 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 为例,它可以接收的参数如:
upload.png
更详细的说明,可参看这个链接: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 将不再看到最初的内容:
upload.png

而会变成下面的内容。因为我们把请求 url 中的 local 截断了,实际到达我们的 local 微服务的请求变成了 http://localhost:8080/
upload.png

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

如果插件的初始化方法出错,则可能会报下面的错误:
upload.png


如果没有在白名单中声明策略,会报以下错误:
upload.png

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
upload.png

将策略加入需要使用的管道中:

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

最后进行测试:
upload.png

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

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.参考资料

posted @   张永全-PLM顾问  阅读(638)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
点击右上角即可分享
微信分享提示