开发自定义插件

<ph type="x-smartling-placeholder"></ph> 您正在查看 Apigee Edge 文档。
转到 Apigee X 文档
信息

Edge Microgateway v. 3.1.x

受众群体

本主题面向希望通过编写代码来扩展 Edge Microgateway 功能的开发者 自定义插件。如果您想编写一个新插件,具备 JavaScript 和 Node.js 方面的经验 必填字段。

什么是自定义 Edge Microgateway 插件?

插件是一个 Node.js 模块,可以为 Edge Microgateway 添加功能。插件模块 遵循一致的模式并存储在 Edge Microgateway 已知的位置,使 以便自动发现并运行在构建容器时,系统会提供几种 安装 Edge Microgateway。其中包括身份验证、高峰控制、配额和 分析。如需了解这些现有插件,请参阅使用插件

您可以通过编写自定义 插件。默认情况下,Edge Microgateway 本质上是一个安全的直通代理, 向目标服务传递请求和响应,向目标服务传递请求和响应。借助自定义插件,您可以 与流经微网关的请求和响应进行交互。

自定义插件代码放置位置

Edge Microgateway 安装过程中包含自定义插件文件夹 此处:

[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins

其中 [prefix]npm 前缀目录,如下所示: 如“Edge Microgateway 的安装位置”中所述参见安装 Edge Microgateway

您可以更改此默认插件目录。请参阅查找位置 插件

查看预定义插件

在尝试开发自己的插件之前,最好先检查 满足您的要求。这些插件位于以下位置:

[prefix]/lib/node_modules/edgemicro/node_modules/microgateway-plugins

其中 [prefix]npm 前缀目录。请参阅 以及“Edge Microgateway 的安装位置”请参阅安装 Edge Microgateway

有关详情,另请参阅预定义 Edge Microgateway 提供的插件

编写一个简单的插件

在本部分中,我们将介绍创建简单插件所需的步骤。此插件 使用字符串“Hello, World!”覆盖响应数据(无论是什么)并将其输出到 终端。

  1. 如果 Edge Microgateway 正在运行,请立即将其停止:
    edgemicro stop
    
  2. cd 添加到自定义插件目录:

    cd [prefix]/lib/node_modules/edgemicro/plugins

    其中 [prefix]npm 前缀目录 如“Edge Microgateway 的安装位置”中所述参见安装 Edge Microgateway

  3. 新建一个名为 response-overridecd 的插件项目 更改为:
    mkdir response-override && cd response-override
    
  4. 创建一个新的 Node.js 项目:
    npm init
    
    多次按“返回”键可接受默认值。
  5. 使用文本编辑器创建名为 index.js 的新文件。
  6. 将以下代码复制到 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");
        }
      };
    }
    
  7. 现在,您已经创建了一个插件,需要将其添加到 Edge Microgateway 配置中。 打开文件 $HOME/.edgemicro/[org]-[env]-config.yaml,其中 orgenv 是您的 Edge 组织和环境名称。
  8. response-override 插件添加到 plugins:sequence 元素中 如下所示。
          ...
          
          plugins:
            dir: ../plugins
            sequence:
              - oauth
              - response-override
              
          ...
    
  9. 重启 Edge Microgateway。
  10. 通过 Edge Microgateway 调用 API。(此 API 调用假定您已设置相同的 如设置 API 密钥安全性的教程中所述, 并配置 Edge Microgateway
    curl -H 'x-api-key: uAM4gBSb6YoMvTHfx5lXJizYIpr5Jd' http://localhost:8000/hello/echo
    Hello, World!
    

插件剖析

以下 Edge Microgateway 示例插件展示了在 开发自己的插件。本部分讨论的示例插件的源代码是 plugins/header-uppercase/index.js.

  • 插件是标准 NPM 模块,具有 package.jsonindex.js 放在根文件夹中
  • 插件必须导出 init() 函数。
  • init() 函数接受三个参数:configloggerstats。“插件”部分介绍了这些参数 init() 函数参数中。
  • init() 会返回一个包含已命名函数处理程序的对象,调用 某些事件在请求的生命周期内发生。

事件处理脚本函数

插件必须实现上述部分或全部事件处理脚本函数。实施这些要素 函数由您自己决定。任何给定的函数都是可选的,典型的插件将在 是这些函数的一个子集。

请求流事件处理脚本

在 Edge Microgateway 中,这些函数在请求事件上调用。

  • onrequest
  • ondata_request
  • onend_request
  • onclose_request
  • onerror_request

onrequest 函数

在客户端请求开始时调用。此函数在 Edge Microgateway 收到 请求时。借助此函数,您可以访问请求标头 网址、查询参数和 HTTP 方法。如果您调用 next 并向其传递真实的第一个参数(例如 Error 实例),则请求处理将停止,并且不会启动目标请求。

示例:

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 Microgateway 中的响应事件上调用。

  • onresponse
  • ondata_response
  • onend_response
  • onclose_response
  • onerror_response

onresponse 函数

在目标响应开始时调用。此函数在 Edge Microgateway 收到 响应时。此函数让您可以访问响应标头 和状态代码。

示例:

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 Microgateway 会处理给定的 API 请求。

  • 每个 init() 函数处理程序(ondata_requestondata_response 等)完成调用后,必须调用 next() 回调 处理。如果您不调用 next(),系统将停止处理并发出请求 会挂起。
  • next() 的第一个参数可能是一个错误,会导致请求 处理以终止。
  • ondata_onend_ 处理程序必须调用 next(),其第二个参数包含要传递给目标的数据 或客户端。如果插件正在缓冲且没有足够的数据 转换。
  • 请注意,插件的单个实例用于为所有请求和响应提供服务。 如果插件希望在调用之间保留每个请求的状态,则可以在 属性添加到提供的 request 对象 (req),其 生命周期是指 API 调用的时长。
  • 请注意捕获所有错误并调用包含相应错误的 next()。失败 调用 next() 将导致 API 调用挂起。
  • 请注意不要引入内存泄漏问题,因为这可能会影响 Edge 的整体性能 Microgateway,并且在内存耗尽时导致其崩溃。
  • 请注意遵循 Node.js 模型,不要在主线程中执行计算密集型任务 线程,因为这可能会对 Edge Microgateway 的性能产生负面影响。

关于插件 init() 函数

本部分介绍了传递给 init() 函数的参数: configloggerstats

config

将 Edge Microgateway 配置文件与 从 Apigee Edge 下载的信息,例如产品和配额。您可以 此对象中的插件特定配置:config.<plugin-name>

添加名为 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

logger

系统日志记录器。当前使用的日志记录器会导出这些函数,其中对象可以是 字符串、HTTP 请求、HTTP 响应或 Error 实例。

  • info(object, message)
  • warn(object, message)
  • error(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 后, 参数,我们说没有错误,请求处理应该可以正常进行。如果 该参数为 true(例如 Error 对象),则请求处理将停止,并且请求 发送到目标。

第二个参数将请求数据传递给链中的下一个函数。如果您不 那么请求数据将原封不动地传递给 API 的目标。 不过,您可以在此方法中修改请求数据,并将修改后的 发送到目标。例如,如果请求数据是 XML,而目标需要 JSON, 然后,您可以向 ondata_request() 方法添加代码,以便 (a) 更改 将请求标头的 Content-Type 转换为 application/json,并转换请求数据 转换为 JSON 格式(例如,您可以使用 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 Microgateway 编写插件时,您需要了解插件使用的顺序 事件处理脚本的执行情况

需要注意的一点是,在 Microgateway 配置文件时,请求处理程序按升序执行, 而响应处理程序按降序执行。

以下示例旨在帮助您了解此执行序列。

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-2plugin-3, 相同的代码(不同之处是需要将 console.log() 语句更改为 plugin-2) 和 plugin-3)。

2. 查看插件代码

导出的插件会在 <microgateway-root-dir>/plugins/plugin-1/index.js 是事件处理脚本, 在请求和响应处理期间的特定时间执行。例如: onrequest 执行收到的请求标头的第一个字节。在此期间, onend_response 会在收到响应数据的最后一个字节之后执行。

看一下处理程序 ondata_response - 每当有响应数据块时,系统都会调用它 。需要注意的一点是, 响应数据不一定要 一次。相反,数据可能会以任意长度的数据块接收。

3. 将插件添加到 插件序列

继续这个示例,我们将插件添加到 Edge 中的插件序列中, Microgateway 配置文件 (~./edgemicro/config.yaml),如下所示。序列是 非常重要。它定义了插件处理程序的执行顺序。

  plugins:
    dir: ../plugins
    sequence:
      - plugin-1
      - plugin-2
      - plugin-3
  

4. 检查调试输出

现在,我们来看看调用这些插件时产生的输出。还有 需要注意以下几点重要事项:

  • 该插件按 Edge Microgateway 配置文件的顺序排列 (~./edgemicro/config.yaml) 用于指定事件处理脚本的顺序 调用。
  • 请求处理程序按升序调用(即 在插件序列 1、2、3 中显示)。
  • 响应处理程序按降序(3、2、1)调用。
  • 对于需要处理的每个数据块,系统都会调用一次 ondata_response 处理程序 。在此示例中(输出如下所示)中,收到了两个分块。

下面是使用这三个插件并且发送请求时生成的调试输出示例 通过 Edge Microgateway 访问 Wi-Fi。只需注意调用处理程序的顺序即可:

  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

摘要

当您尝试 实现自定义插件功能,例如累积和转换请求或响应 数据。

请记住,请求处理程序的执行顺序与插件在 Google Cloud 上的 而响应处理程序会在 顺序相反。

关于在插件中使用全局变量

发送到 Edge Microgateway 的每个请求都会发送到插件的同一实例;因此, 来自另一个客户端的第二个请求的状态将覆盖第一个请求的状态。只有 保存插件状态的方式是将状态存储在请求或响应对象(其 的生命周期限制为请求的有效期)。

在插件中重写目标网址

添加此模块的版本:v2.3.3

您可以通过修改这些变量来动态替换插件中的默认目标网址 req.targetHostnamereq.targetPath

添加此模块的版本:v2.4.x

您还可以替换目标端点端口,并选择 HTTP 或 HTTPS。修改这些内容 变量:req.targetPortreq.targetSecure.要选择 HTTPS,请将 req.targetSecure 设置为 true;对于 HTTP,请将此参数设置为 false。如果您将 req.targetSecure 设置为 请参阅这篇讨论 讨论帖

Edge Microgateway 中添加了一个名为 eurekaclient 的示例插件。这个 插件演示了如何使用 req.targetPort 和 req.targetSecure 变量,以及 说明了 Edge Microgateway 如何使用 Eureka 服务执行动态端点查找 端点目录


示例插件

这些插件随 Edge Microgateway 安装提供。您可以在 在此处安装 Edge Microgateway:

[prefix]/lib/node_modules/edgemicro/plugins

其中 [prefix]npm 前缀目录,如下所示: 如“Edge Microgateway 的安装位置”中所述参见安装 Edge Microgateway

accumulate-request

此插件会将来自客户端的数据块累积到附加到 请求对象。收到所有请求数据后,该数组会连接到一个缓冲区 然后将其传递给序列中的下一个插件。此插件应为第一个插件 以便后续插件能够接收累计的请求数据。

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);

    }

  };

}

accumulate-response

此插件会将目标中的数据块累积到附加到 响应对象。收到所有响应数据后,将数组串联到缓冲区中。 然后将其传递给序列中的下一个插件。由于此插件是在 它们以相反的顺序处理,那么您应该将其作为最后一个插件 序列。

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 Microgateway 发行版包含一个名为 <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);
    }

  };

}