随NGINX JavaScript 模块一同发布的NGINX Plus R15版本现在可以发起子请求,即可以在JavaScript代码中发起请求。这使其能够支持一系列全新的用例。
其中一个用例是批量处理API请求,将客户端发出的单个API请求转换为向一组后端服务器发出的多个API请求,再将各响应汇总为单个响应返回给客户端。本文以电子商务网站为例:当客户端请求某个特定产品的信息时,系统会向三个后端服务发出子请求,分别是产品目录、库存和客户评价。
本文以NGINX Plus R15 公告中的子请求示例为基础。该示例展示了如何使用子请求将同一请求发送到两台后端服务器,并仅将最先收到的响应返回给客户端。
[编辑注:本文是探讨NGINX JavaScript模块用例的系列文章之一。完整列表请参阅《NGINX JavaScript模块用例》。
本文已更新为使用 js_import指令,该指令在NGINX Plus R23及更高版本中取代了js_include指令。有关详细信息,请参阅NGINX JavaScript模块的参考文档,其中的配置示例部分展示了NGINX配置文件和JavaScript文件的正确语法。]
简介
本文旨在提供简单明了且可实际运行的API请求批处理示例。这些示例满足以下所有要求:
- 所有请求均使用HTTP“
GET”方法。 - 所有响应均采用JSON格式。
- 支持两种形式的API请求。请注意,无论采用哪种形式,查询字符串中都可以包含其他参数。
- API程序的参数是URI的最后一个元素。我们将其称为“末元素请求样式”。在本例中,客户端对/batch-api/product/14286的请求会生成对/myapi/catalog/14286、/myapi/inventory/14286和/myapi/review/14286的请求。
- 参数在URI的查询字符串中指定。我们将其称为“查询字符串请求样式”。在此示例中,对/batch-api2/product?item=14286的请求会生成对以下地址的请求:/myapi/catalog.php?item=14286、/myapi/inventory.php?item=14286和/myapi/review.php?item=14286。
- 客户端请求触发的子请求集合可动态配置,这意味着无需手动编辑并重新加载NGINX Plus配置即可更改该集合。
- 向后端服务器发出的API请求彼此独立。
解决方案概述
除了使用NGINX JavaScript的新子请求功能外,此解决方案还利用NGINX Plus的键值存储功能,从而可通过NGINX Plus API动态更改配置。
下图展示了支持的两种请求方式。
最终元素请求样式:

查询字符串请求样式:

在这两个示例中,客户端都需要从目录、库存和评论服务获取某个产品的信息。客户端向NGINX Plus发送一个请求,并将商品编号作为URI的一部分或通过查询字符串传递。随后,请求会发送到这三个服务,并将各服务的响应聚合为一个响应返回给客户端。
要在NGINX Plus中实现此功能,需要两个组件:一个JavaScript程序和NGINX Plus配置。
JavaScript程序
对于每个客户端请求,都会调用JavaScript函数batchAPI()。该函数从键值存储中获取以逗号分隔的API URI列表并逐一遍历,为每个URI发出子请求,同时指定回调函数done()来处理每个响应。对于末尾元素样式的请求,URI的最后一个元素存储在NGINX变量$uri_suffix中,并作为每个子请求的查询字符串传递,同时还会传递原始客户端URI中的其他所有查询字符串参数。对于查询字符串样式的请求,客户端请求中的查询字符串会作为每个子请求的查询字符串传递。
调用按其在键值存储中的列出顺序发起,但由于调用是异步的,响应可能以任意顺序返回。系统按接收顺序聚合这些响应,并将其作为一个响应返回给客户端。以下是该JavaScript程序的精简版本:
正在加载代码示例…
GitHub Gist存储库中提供了该JavaScript程序的一个版本,其中包含更全面的错误处理、日志记录和注释,文件名为batch-api.js。
NGINX Plus 最简配置
以下NGINX Plus配置使用一组上游服务器,实现了上述两种请求样式。为简化说明,此配置假定上游服务器能够将以下形式的调用:/myapi/service/item#(末尾元素样式)转换为/myapi/service.php/?item=item#(查询字符串样式)。后者是PHP的“原生”URI格式;示例中的目录、库存和评论服务均使用PHP编写。
如需了解由NGINX Plus自行执行转换的更完整配置,请参阅下文的“扩展NGINX配置以转换URI”。
下面逐节查看配置文件,并说明各部分的作用:
- 导入JavaScript文件:
正在加载代码示例…
- 定义一个键值存储,用于保存API请求列表,其中URI的最后一个元素用于标识API程序的参数。键使用
$uri_prefix变量捕获URI中最后一个/之前的部分:正在加载代码示例…
在NGINX Plus R16及更高版本中,可以使用另外两项键值功能。有关如何为键值存储设置同步的说明,请参阅NGINX Plus管理指南。- 为键值存储中的条目设置过期时间,方法是向
timeout参数添加到keyval_zone指令中。例如,要让每个批处理URI每天过期,请添加timeout=1d。 - 要在NGINX Plus实例集群中同步键值存储,请向
sync参数添加到keyval_zone指令中。在这种情况下,还必须包含timeout参数。
- 为键值存储中的条目设置过期时间,方法是向
- 为参数通过查询字符串标识的API定义另一个键值存储。此处,键(
$uri)会捕获不含查询字符串的完整URI:正在加载代码示例…
- 定义两个映射,将URI拆分为两部分,适用于接受参数位于URI最后一个元素中的请求的API。变量
$uri_prefix捕获最后一个/之前的URI元素,$uri_suffix捕获最后一个元素:正在加载代码示例…
- 定义API服务器的“上游组”(此处,这些服务器与NGINX Plus一同在localhost上运行):
正在加载代码示例…
- 定义用于处理客户端请求和子请求的虚拟服务器:
正在加载代码示例…
- 定义一个location,用于处理采用末尾元素样式的客户端请求。将变量
batchAPI函数的$batch_api_arg_in_uri设置为on,即可向该函数指明此样式:正在加载代码示例…
- 定义一个location,用于处理采用查询字符串样式的客户端请求。通过将
batchAPI函数的$batch_api_arg_in_uri变量设置为off来指示此样式:正在加载代码示例…
- 定义用于处理子请求的location:
正在加载代码示例…
- 启用NGINX Plus API,以便维护键值存储中的数据:
正在加载代码示例…
完整配置如下:
正在加载代码示例…
配置批量API请求
我们使用NGINX Plus键值存储功能,将入站客户端请求映射到一组待执行的API请求。上一节中的配置为两种请求方式分别定义了键值存储:
- 最终元素样式的键值存储名为batch_api,键为
$uri_prefix变量,该变量会捕获请求URI中最后一个/之前的元素。 - 查询字符串样式的键值存储名为batch_api2,键为
$uri变量,该变量用于捕获不含查询字符串的完整请求URI。
在这两个键值存储中,与每个键关联的值都是以逗号分隔的URI列表,这些URI在运行时可通过变量$batch_api或$batch_api2使用。
对于最终元素样式的请求,我们希望将客户端对/batch-api/product的请求映射到以下3项服务的请求:/myapi/catalog、/myapi/inventory和/myapi/review。这样,对/batch-api/product/14286的请求将生成对以下服务的请求:/myapi/catalog/14286、/myapi/product/14286和/myapi/review/14286。
要将这些映射添加到batch_api键值存储,请向本地计算机上运行的NGINX Plus实例发送以下请求:
$ curl -iX POST -d '{"/batch-api/product":"/myapi/catalog,/myapi/inventory,/myapi/review"}' http://localhost/api/3/http/keyvals/batch_api对于查询字符串形式的请求,我们希望将客户端对/batch-api2/product的请求映射到以下3个服务:/myapi/catalog.php、/myapi/inventory.php和/myapi/review.php。这样,对/batch-api2/product?item=14286的请求将生成对以下服务的请求:/myapi/catalog?item=14286、/myapi/product?item=14286和/myapi/review?item=14286。
要将这些映射添加到batch_api2键值存储中,我们发送以下请求:
$ curl -iX POST -d '{"/batch-api2/product":"/myapi/catalog.php,/myapi/inventory.php,/myapi/review.php"}' http://localhost/api/3/http/keyvals/batch_api2运行示例
同一组PHP页面可处理以这两种请求样式发出的请求。为简单起见,我们假设后端服务器能够将末尾元素样式的URI(/myapi/service/item#)转换为查询字符串样式的URI(/myapi/service.php?item=item#)。无需转换查询字符串样式的URI,因为这是PHP程序的“原生”URI格式。
所有服务都会返回JSON格式的响应,其中包含服务名称和item参数。例如,请求/myapi/catalog.php?item=14286后,catalog.php会返回以下响应:
{"service":"Catalog","item":"14286"}虽然这些示例中使用的PHP程序会在响应中包含服务名称,但并非所有服务都一定如此。为便于将响应与生成该响应的服务相匹配,JavaScript程序会在聚合响应中包含URI。
例如,对/batch-api/product/14286的请求所返回的聚合响应如下(其中3个组成响应的顺序可能有所不同):
[["/myapi/review/14286",{"service":"Review","item":"14286"}],["/myapi/inventory/14286",{"service":"Inventory","item":"14286"}],["/myapi/catalog/14286",{"service":"Catalog","item":"14286"}]]扩展NGINX Plus配置以转换URI
如前所述,上文讨论的最简NGINX Plus配置假定API服务器能够将以下格式的URI转换为/myapi/service/item#:/myapi/service.php/?item=item#。也可以通过进行以下更改,在NGINX Plus配置中实现此转换。
- 添加一个新的上游组,用于接收子请求:
正在加载代码示例…
- 添加一个新的虚拟服务器,用于侦听由services上游组接收的请求。收到查询字符串形式的请求时,只需将其代理到api_servers上游组,因为URI已包含 .php扩展名。收到末尾元素形式的请求时,将重写URI,移除URI中的最后一个元素,并将其转换为查询字符串参数:
正在加载代码示例…
- 将/myapi location更改为代理到新的上游组:
正在加载代码示例…
完整配置如下:
正在加载代码示例…
其他组件
示例JavaScript程序和NGINX Plus配置可调整用于其他风格的API。如果您希望使用创建本博文时所用的全部组件进行测试,可以在我们位于GitHub的Gist存储库中获取这些组件,其中包括用于提供PHP页面的NGINX Unit配置:
batch-api.js
catalog.php
inventory.php
review.php
unit.config
结论
这些示例展示了如何批量处理API请求,并向客户端提供聚合响应。借助NGINX Plus键值存储,可以通过NGINX Plus API动态配置API请求。不同API系统的具体操作差异很大,因此可以对本示例进行多种改进,以满足特定API的需求。
可以使用NGINX Open Source开始测试NGINX JavaScript子请求。如果想尝试API批处理示例,或测试利用NGINX Plus功能(如键值存储和NGINX Plus API)的其他用例,请申请30天免费试用并开始体验。
关于作者

相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
