您正在查看的是 Apigee Edge 文档。
转到 Apigee X 文档。 信息
Edge Microgate v. 3.1.5 及更高版本
受众群体
本主题面向希望通过编写自定义插件来扩展 Edge Microgate 功能的开发者。如果您想编写新插件,需要具备 JavaScript 和 Node.js 方面的经验。
什么是自定义 Edge Microgate 插件?
插件是一个向 Edge Microgate 添加功能的 Node.js 模块。插件模块遵循一致的模式,并存储在 Edge Microgate 已知的位置,以便被发现并自动运行。安装 Edge Microgate 时,我们提供了多个预定义插件。其中包括用于身份验证、高峰控制、配额和分析的插件。如需了解这些现有插件,请参阅使用插件。
您可以通过编写自定义插件向微网关添加新特性和功能。默认情况下,Edge Microgate 本质上是一种安全的直通式代理,可将请求和响应原封不动地传递到目标服务,或从目标服务传送请求和响应。借助自定义插件,您能够以编程方式与通过微网关的请求和响应进行交互。
自定义插件代码的放置位置
此处的 Edge Microgate 安装过程包含自定义插件的文件夹:
[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins
其中 [prefix]
是 npm
前缀目录,如安装 Edge Microgate 中的“Edge Microgate 的安装位置”部分所述。
您可以更改此默认插件目录。请参阅在哪里可以找到插件。
查看预定义插件
在尝试开发自己的插件之前,最好检查并确保没有任何预定义插件符合您的要求。这些插件位于:
[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins
其中,[prefix]
是 npm
前缀目录。另请参阅安装 Edge Microgate 中的“Edge Microgate 安装的位置”。
如需了解详情,另请参阅 Edge Microgate 提供的预定义插件。
编写一个简单的插件
在本部分中,我们将介绍创建简单插件所需的步骤。此插件会将响应数据(无论数据)替换为字符串“Hello, World!”,并将其输出到终端。
- 如果 Edge Microgate 正在运行,请立即将其停止:
edgemicro stop
-
cd
添加到自定义插件目录中:cd [prefix]/lib/node_modules/edgemicro/plugins
其中
[prefix]
是npm
前缀目录,如安装 Edge Microgate 中的“Edge Microgate 安装位置”部分所述。 - 创建一个名为 response-override 的新插件项目,并向其中添加
cd
:
mkdir response-override && cd response-override
- 创建一个新的 Node.js 项目:
npm init
多次按 Return 以接受默认值。 - 使用文本编辑器创建名为
index.js
的新文件。 - 将以下代码复制到
index.js
中,然后保存文件。
'use strict'; var debug = require('debug') module.exports.init = function(config, logger, stats) { return { ondata_response: function(req, res, data, next) { debug('***** plugin ondata_response'); next(null, null); }, onend_response: function(req, res, data, next) { debug('***** plugin onend_response'); next(null, "Hello, World!\n\n"); } }; }
- 现在您已创建了一个插件,需要将其添加到 Edge Microgate 配置中。打开文件
$HOME/.edgemicro/[org]-[env]-config.yaml
,其中org
和env
是您的 Edge 组织和环境的名称。 - 将
response-override
插件添加到plugins:sequence
元素中,如下所示。
... plugins: dir: ../plugins sequence: - oauth - response-override ...
- 重启 Edge Microgate。
- 通过 Edge Microgate 调用 API。(此 API 调用假定您已按照设置和配置 Edge Microgate 中的说明,设置了与教程相同的 API 密钥安全性配置:
curl -H 'x-api-key: uAM4gBSb6YoMvTHfx5lXJizYIpr5Jd' http://localhost:8000/hello/echo Hello, World!
插件详解
以下 Edge Microgate 示例插件演示了在开发您自己的插件时要遵循的模式。本部分中讨论的示例插件的源代码位于 plugins/header-uppercase/index.js.
- 插件是标准的 NPM 模块,在根文件夹中包含
package.json
和index.js
。 - 插件必须导出 init() 函数。
- init() 函数采用三个参数:config、logger 和 stats。插件 init() 函数参数中介绍了这些参数。
- init() 会返回一个包含命名函数处理程序的对象,以便在请求的生命周期内发生特定事件时调用这些处理程序。
事件处理脚本函数
插件必须实现其中部分或全部事件处理脚本函数。这些函数的实现由您决定。任何给定函数都是可选的,典型插件至少会实现这些函数的一个子集。
请求流事件处理脚本
这些函数在 Edge Microgate 中的请求事件中调用。
onrequest
ondata_request
onend_request
onclose_request
onerror_request
onrequest
函数
在客户端请求开始时调用。当 Edge Microgate 收到请求的第一个字节时,此函数会触发。此函数让您可以访问请求标头、网址、查询参数和 HTTP 方法。如果您使用真实的第一个参数(例如 Error 实例)调用了 next 方法,则请求处理会停止,并且不会启动目标请求。
例如:
onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-start'] = Date.now(); next(); }
ondata_request
函数
在从客户端接收数据块时调用。将请求数据传递给插件序列中的下一个插件。序列中最后一个插件的返回值将发送到目标。下面显示的一个典型用例是在将请求数据发送到目标之前对其进行转换。
例如:
ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
onend_request
函数
从客户端收到所有请求数据时调用。
例如:
onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }
onclose_request
函数
表示客户端连接已关闭。如果客户端连接不可靠,您可以使用此函数。关闭与客户端的套接字连接时,系统会调用此方法。
例如:
onclose_request: function(req, res, next) { debug('plugin onclose_request'); next(); }
onerror_request
函数
在接收客户端请求出错时调用。
例如:
onerror_request: function(req, res, err, next) { debug('plugin onerror_request ' + err); next(); }
响应流事件处理脚本
系统会针对 Edge Microgate 中的响应事件调用这些函数。
onresponse
ondata_response
onend_response
onclose_response
onerror_response
onresponse
函数
在目标响应开始时调用。当 Edge Microgate 收到响应的第一个字节时,此函数会触发。此函数让您可以访问响应标头和状态代码。
例如:
onresponse: function(req, res, next) { debug('plugin onresponse'); res.setHeader('x-foo-response-time', Date.now() - req.headers['x-foo-request-start']) next(); }
ondata_response
函数
在从目标接收数据块时调用。
例如:
ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }
onend_response
函数
在从目标收到所有响应数据时调用。
例如:
onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }
onclose_response
函数
表示目标连接已关闭。如果目标连接不可靠,您可以使用此函数。当与目标的套接字连接关闭时,系统会调用此方法。
例如:
onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); }
onerror_response
函数
在接收目标响应出错时调用。
例如:
onerror_response: function(req, res, err, next) { debug('plugin onerror_response ' + err); next(); }
您需要了解的插件事件处理脚本函数
系统会调用插件事件处理脚本函数来响应在 Edge Microgate 处理给定 API 请求时发生的特定事件。
- 每个 init() 函数处理程序(ondata_request、ondata_response 等)都必须在处理完成后调用 next() 回调。如果您不调用 next(),处理将停止,请求也会挂起。
- next() 的第一个参数可能是一个错误,这会导致请求处理终止。
- ondata_ 和 onend_ 处理程序必须通过包含要传递给目标或客户端的数据的第二个参数调用 next()。如果插件正在缓冲,但此时数据不足,无法进行转换,则此参数可以为 null。
- 请注意,该插件的单个实例用于处理所有请求和响应。如果插件希望在调用之间保留每个请求的状态,它可以将该状态保存在添加到所提供的 request 对象 (req) 的属性中,其生命周期是 API 调用的时长。
- 请务必捕获所有错误,并使用错误调用 next()。未能调用 next() 将导致 API 调用挂起。
- 请注意不要引入内存泄漏,因为这可能会影响 Edge Microgate 的整体性能,并导致它在内存耗尽时崩溃。
- 请务必遵循 Node.js 模型,不要在主线程中执行计算密集型任务,因为这可能会对 Edge Microgate 的性能产生负面影响。
关于插件 init() 函数
本部分介绍传递给 init() 函数的参数:config、logger 和 stats。
config
通过将 Edge Microgate 配置文件与从 Apigee Edge 下载的数据合并而获得的配置数据会放置在名为 config
的对象中。
要将名为 param 且值为 foo 的配置参数添加到名为 response-override 的插件中,请将以下内容放入 default.yaml
文件中:
response-override: param: foo
然后,您可以在插件代码中访问该参数,如下所示:
// Called when response data is received ondata_response: function(req, res, data, next) { debug('***** plugin ondata_response'); debug('***** plugin ondata_response: config.param: ' + config.param); next(null, data); },
在这种情况下,您将在插件调试输出中看到 foo 输出:
Sun, 13 Dec 2015 21:25:08 GMT plugin:response-override ***** plugin ondata_response: config.param: foo
您可以在子对象 config.emgConfigs
中访问合并的微网关配置和下载的 Apigee Edge 数据。例如,您可以在 init
函数中访问此配置数据,如下所示:
module.exports.init = function(config, logger, stats) { let emgconfigs = config.emgConfigs;
下面是 emgConfigs
包含的数据的示例:
{ edgemicro: { port: 8000, max_connections: 1000, config_change_poll_interval: 600, logging: { level: 'error', dir: '/var/tmp', stats_log_interval: 60, rotate_interval: 24, stack_trace: false }, plugins: { sequence: [Array] }, global: { org: 'Your Org', env: 'test' } }, headers: { 'x-forwarded-for': true, 'x-forwarded-host': true, 'x-request-id': true, 'x-response-time': true, via: true }, proxies: [ { max_connections: 1000, name: 'edgemicro_delayed', revision: '1', proxy_name: 'default', base_path: '/edgemicro_delayed', target_name: 'default', url: 'https://httpbin.org/delay/10', timeout: 0 } ], product_to_proxy: { EdgeMicroTestProduct: [ 'edgemicro-auth','edgemicro_delayed',] }, product_to_scopes: {prod4: [ 'Admin', 'Guest', 'Student' ] }, product_to_api_resource: { EdgeMicroTestProduct: [ '/*' ] }, _hash: 0, keys: { key: 'Your key', secret: 'Your key ' }, uid: 'Internally generated uuid', targets: [] }
logger
系统日志记录器。当前使用的日志记录器会导出这些函数,其中对象可以是字符串、HTTP 请求、HTTP 响应或 Error 实例。
info(object, message)
warn(object, message)
error(object, message)
trace(object, message)
debug(object, message)
stats
一个对象,用于存储与通过微网关实例的请求和响应相关的请求、响应、错误以及其他汇总统计信息的计数。
- treqErrors - 有错误的目标请求数。
- treqErrors - 有错误的目标响应数量。
- statusCodes - 包含响应代码计数的对象:
{ 1: number of target responses with 1xx response codes 2: number of target responses with 2xx response codes 3: number of target responses with 3xx response codes 4: number of target responses with 4xx response codes 5: number of target responses with 5xx response codes }
- requests - 请求总数。
- responses - 响应总数。
- connections - 活跃目标连接的数量。
关于 next() 函数
所有插件方法都必须调用 next()
才能继续处理系列中的下一个方法(否则插件进程将挂起)。在请求生命周期中,调用的第一个方法是 onrequest()。下一个要调用的方法是 ondata_request()
方法;不过,只有在请求包含数据时才调用 ondata_request
,例如 POST 请求的情况。调用的下一个方法是 onend_request()
,当请求处理完成时,系统会调用该方法。系统仅在发生错误时调用 onerror_*
函数,该函数允许您根据需要使用自定义代码处理错误。
假设在请求中发送了数据,并且调用了 ondata_request()
。请注意,该函数使用以下两个参数调用 next()
:
next(null, data);
按照惯例,第一个参数用于传递错误信息,您可以在链的后续函数中处理这些信息。将其设置为 null
(一个 falsy 参数)即表示未出现错误,请求处理应该会正常进行。如果此参数为 True(例如 Error 对象),则请求处理会停止,并且请求会发送到目标。
第二个参数将请求数据传递给链中的下一个函数。如果不进行额外的处理,请求数据将原封不动地传递给 API 的目标。不过,您可以在此方法中修改请求数据,并将修改后的请求传递给目标。例如,如果请求数据是 XML 且目标需要 JSON,那么您可以向 ondata_request()
方法添加代码,以便 (a) 将请求标头的 Content-Type 更改为 application/json
,并使用所需的任何方式将请求数据转换为 JSON(例如,您可以使用从 NPM 获得的 Node.js xml2json 转换器)。
我们来看一下:
ondata_request: function(req, res, data, next) { debug('****** plugin ondata_request'); var translated_data = parser.toJson(data); next(null, translated_data); },
在这种情况下,请求数据(假定为 XML)会转换为 JSON,转换后的数据会先通过 next()
传递给请求链中的下一个函数,然后再传递给后端目标。
请注意,您可以再添加一个调试语句来输出转换后的数据,以便进行调试。例如:
ondata_request: function(req, res, data, next) { debug('****** plugin ondata_request'); var translated_data = parser.toJson(data); debug('****** plugin ondata_response: translated_json: ' + translated_json); next(null, translated_data); },
关于插件处理程序执行顺序
为 Edge Microgate 编写插件时,您需要了解插件事件处理脚本的执行顺序。
请务必注意,在 Edge Microgate 配置文件中指定插件序列时,请求处理程序按升序执行,而响应处理程序则按降序执行。
以下示例旨在帮助您了解此执行顺序。
1. 创建三个简单的插件
请参考以下插件。它只是在调用其事件处理脚本时输出控制台输出:
plugins/plugin-1/index.js
module.exports.init = function(config, logger, stats) { return { onrequest: function(req, res, next) { console.log('plugin-1: onrequest'); next(); }, onend_request: function(req, res, data, next) { console.log('plugin-1: onend_request'); next(null, data); }, ondata_response: function(req, res, data, next) { console.log('plugin-1: ondata_response ' + data.length); next(null, data); }, onend_response: function(req, res, data, next) { console.log('plugin-1: onend_response'); next(null, data); } }; }
现在,请考虑使用相同的代码再创建两个插件:plugin-2
和 plugin-3
(但要分别将 console.log()
语句更改为 plugin-2
和 plugin-3
)。
2. 查看插件代码
<microgateway-root-dir>/plugins/plugin-1/index.js
中导出的插件函数是在处理请求和响应期间的特定时间执行的事件处理脚本。例如,onrequest
会执行收到的请求标头的第一个字节。而 onend_response
会在收到响应数据的最后一个字节之后执行。
请查看处理程序 ondata_response;每当收到响应数据块时,系统都会调用该处理程序。需要注意的是,响应数据不一定会一次性收到。相反,数据可能会以任意长度的分块接收。
3. 将插件添加到插件序列
继续此示例,我们将向 Edge Microgate 配置文件 (~./edgemicro/config.yaml
) 中的插件序列添加插件,如下所示。顺序很重要。它定义了插件处理程序的执行顺序。
plugins: dir: ../plugins sequence: - plugin-1 - plugin-2 - plugin-3
4. 检查调试输出
现在,我们来看看调用这些插件时生成的输出。您需要注意以下几个要点:
- Edge Microgate 配置文件 (
~./edgemicro/config.yaml
) 的插件序列指定了事件处理脚本的调用顺序。 - 系统会按升序(即它们在插件序列中的显示顺序 1、2、3)调用请求处理程序。
- 响应处理程序按降序顺序调用,即 3、2、1。
- 对于到达的每个数据块,都会调用一次
ondata_response
处理程序。在本例中(输出如下所示),收到了两个分块。
下面是在使用这三个插件并且通过 Edge Microgate 发送请求时生成的调试输出示例。请注意调用处理程序的顺序:
plugin-1: onrequest plugin-2: onrequest plugin-3: onrequest plugin-1: onend_request plugin-2: onend_request plugin-3: onend_request plugin-3: ondata_response 931 plugin-2: ondata_response 931 plugin-1: ondata_response 931 plugin-3: ondata_response 1808 plugin-3: onend_response plugin-2: ondata_response 1808 plugin-2: onend_response plugin-1: ondata_response 1808 plugin-1: onend_response
摘要
当您尝试实现自定义插件功能(例如累积和转换请求或响应数据)时,了解调用插件处理程序的顺序非常重要。
请记住,请求处理程序的执行顺序与 Edge Microgate 配置文件中指定的插件顺序一致,响应处理程序的执行顺序与之相反。
关于在插件中使用全局变量
对 Edge Microgate 的每个请求都会发送到插件的同一实例;因此,来自其他客户端的第二个请求的状态会覆盖第一个请求的状态。保存插件状态的唯一安全位置就是将状态存储在请求或响应对象的属性中(其生命周期限于请求对象)。
重写插件中的目标网址
添加此模块的版本:v2.3.3
您可以通过修改插件代码中的以下变量来动态替换插件中的默认目标网址:req.targetHostname 和 req.targetPath。
添加此模块的版本:v2.4.x
您还可以替换目标端点端口,并选择 HTTP 或 HTTPS。在插件代码中修改以下变量:req.targetPort 和 req.targetSecure。要选择 HTTPS,请将 req.targetSecure 设置为 true;对于 HTTP 事件,请将其设置为 false。如果您将 req.targetSecure 设置为 true,请参阅此讨论会话了解详情。
示例插件
这些插件随 Edge Microgate 安装一起提供。您可以点击以下链接,在 Edge Microgate 安装中找到它们:
[prefix]/lib/node_modules/edgemicro/plugins
其中 [prefix]
是 npm
前缀目录,如安装 Edge Microgate 中的“Edge Microgate 的安装位置”部分所述。
累积请求
此插件会将来自客户端的数据区块累积到附加到请求对象的数组属性中。收到所有请求数据后,该数组会被串联到一个 Buffer 中,然后传递给序列中的下一个插件。此插件应该是序列中的第一个插件,以便后续插件接收累积的请求数据。
module.exports.init = function(config, logger, stats) { function accumulate(req, data) { if (!req._chunks) req._chunks = []; req._chunks.push(data); } return { ondata_request: function(req, res, data, next) { if (data && data.length > 0) accumulate(req, data); next(null, null); }, onend_request: function(req, res, data, next) { if (data && data.length > 0) accumulate(req, data); var content = null; if (req._chunks && req._chunks.length) { content = Buffer.concat(req._chunks); } delete req._chunks; next(null, content); } }; }
累积响应
此插件会将来自目标的数据区块累积到附加到响应对象的数组属性中。收到所有响应数据后,该数组会被串联到一个 Buffer 中,然后传递给序列中的下一个插件。由于此插件对按反向顺序处理的响应执行操作,因此您应将其放在序列中的最后一个插件位置。
module.exports.init = function(config, logger, stats) { function accumulate(res, data) { if (!res._chunks) res._chunks = []; res._chunks.push(data); } return { ondata_response: function(req, res, data, next) { if (data && data.length > 0) accumulate(res, data); next(null, null); }, onend_response: function(req, res, data, next) { if (data && data.length > 0) accumulate(res, data); var content = Buffer.concat(res._chunks); delete res._chunks; next(null, content); } }; }
header-uppercase 插件
Edge Microgate 发行版包含一个名为 <microgateway-root-dir>/plugins/header-uppercase
的示例插件。该示例包含描述每个函数处理程序的注释。此示例对目标响应执行了一些简单的数据转换,并将自定义标头添加到客户端请求和目标响应中。
以下是 <microgateway-root-dir>/plugins/header-uppercase/index.js
的源代码:
'use strict'; var debug = require('debug')('plugin:header-uppercase'); // required module.exports.init = function(config, logger, stats) { var counter = 0; return { // indicates start of client request // request headers, url, query params, method should be available at this time // request processing stops (and a target request is not initiated) if // next is called with a truthy first argument (an instance of Error, for example) onrequest: function(req, res, next) { debug('plugin onrequest'); req.headers['x-foo-request-id'] = counter++; req.headers['x-foo-request-start'] = Date.now(); next(); }, // indicates start of target response // response headers and status code should be available at this time onresponse: function(req, res, next) { debug('plugin onresponse'); res.setHeader('x-foo-response-id', req.headers['x-foo-request-id']); res.setHeader('x-foo-response-time', Date.now() - req.headers['x-foo-request-start']); next(); }, // chunk of request body data received from client // should return (potentially) transformed data for next plugin in chain // the returned value from the last plugin in the chain is written to the target ondata_request: function(req, res, data, next) { debug('plugin ondata_request ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }, // chunk of response body data received from target // should return (potentially) transformed data for next plugin in chain // the returned value from the last plugin in the chain is written to the client ondata_response: function(req, res, data, next) { debug('plugin ondata_response ' + data.length); var transformed = data.toString().toUpperCase(); next(null, transformed); }, // indicates end of client request onend_request: function(req, res, data, next) { debug('plugin onend_request'); next(null, data); }, // indicates end of target response onend_response: function(req, res, data, next) { debug('plugin onend_response'); next(null, data); }, // error receiving client request onerror_request: function(req, res, err, next) { debug('plugin onerror_request ' + err); next(); }, // error receiving target response onerror_response: function(req, res, err, next) { debug('plugin onerror_response ' + err); next(); }, // indicates client connection closed onclose_request: function(req, res, next) { debug('plugin onclose_request'); next(); }, // indicates target connection closed onclose_response: function(req, res, next) { debug('plugin onclose_response'); next(); } }; }
转换(大写)
这是一个通用转换插件,您可以修改该插件来执行任何所需的转换。本示例只是将响应和请求数据转换为大写。
*/ module.exports.init = function(config, logger, stats) { // perform content transformation here // the result of the transformation must be another Buffer function transform(data) { return new Buffer(data.toString().toUpperCase()); } return { ondata_response: function(req, res, data, next) { // transform each chunk as it is received next(null, data ? transform(data) : null); }, onend_response: function(req, res, data, next) { // transform accumulated data, if any next(null, data ? transform(data) : null); }, ondata_request: function(req, res, data, next) { // transform each chunk as it is received next(null, data ? transform(data) : null); }, onend_request: function(req, res, data, next) { // transform accumulated data, if any next(null, data ? transform(data) : null); } }; }