NGINX.COM
Web Server Load Balancing with NGINX Plus

The version of the NGINX JavaScript module released with NGINX Plus R15 can now issue subrequests, meaning that requests can be initiated in JavaScript code. This enables a whole new set of use cases to be addressed.

One of these use cases is batching API requests so that a single API request from a client can be turned into multiple API requests to a set of backend servers, and the responses aggregated into a single response to the client. In this blog we’re using an ecommerce site as the example – when the client requests information about a particular product, subrequests are made to three backend services: catalog, inventory, and customer reviews.

This post builds on the subrequest example in the NGINX Plus R15 announcement, which shows how to use subrequests to send the same request to two backend servers, and return only the first response to the client.

[Editor – This post is one of several that explore use cases for the NGINX JavaScript module. For a complete list, see Use Cases for the NGINX JavaScript Module.]

Introduction

The goal of this post is to provide working examples of API request batching that are simple and straightforward. The examples meet all of the following requirements:

  • The HTTP GET method is used for all requests.
  • All responses are in JSON format.
  • Two styles of API requests are supported:

    • The argument to the API program is the final element of the URI. We call this the final‑element request style. In our example, a client request for /batch-api/product/14286 spawns requests to /myapi/catalog/14286, /myapi/inventory/14286, and /myapi/review/14286.
    • The argument is identified in the query string of the URI. We call this the query‑string request style. In the example, a request for /batch-api2/product?item=14286 spawns requests to /myapi/catalog.php?item=14286, /myapi/inventory.php?item=14286, and /myapi/review.php?item=14286.

    Note that with either style there can be additional arguments in the query string.

  • The set of subrequests triggered by a client request is dynamically configurable, meaning we can change it without manually editing the NGINX Plus configuration and reloading it.
  • The API requests to the backend servers are independent of one another.

Solution Overview

In addition to using the new subrequest feature of NGINX JavaScript, this solution utilizes NGINX Plus’ key‑value store feature, allowing configuration changes to be made dynamically with the NGINX Plus API.

The following graphics illustrate the two supported request styles.

Final‑element request style:

Client makes final-element style requests to an API

Query‑string request style:

Client makes query-string style requests to an API

In both examples, the client needs to get information about a product from the catalog, inventory, and review services. It sends one request to NGINX Plus, either passing the item number as part of the URI or in the query string. Requests are then sent to the three services and the responses are aggregated into a single response to the client.

There are two components required to get this working with NGINX Plus: a JavaScript program and the NGINX Plus configuration.

The JavaScript Program

The JavaScript function batchAPI() is called for each client request. It gets the comma‑separated list of API URIs from the key‑value store and iterates through them, making a subrequest for each, and specifying the callback function done() to process each response. For final‑element style requests, the last element of the URI, stored in the NGINX variable $uri_suffix, is passed as the query string of each subrequest, along with any other query‑string arguments in the original client URI. For query‑string style requests, the query string from the client requests is passed as the query string of each subrequest.

The calls are made in the order they are listed in the key‑value store, but are asynchronous, so the responses can come back in any order. The responses are aggregated into one response to the client in the order in which they are received. Here is a minimal version of the JavaScript program:

A version of the JavaScript program with more extensive error handling, logging, and comments is available in the GitHub Gist repo as batch-api.js.

Minimal NGINX Plus Configuration

The following NGINX Plus configuration implements the two request styles discussed above, using a group of upstream servers. To keep things simple, this configuration assumes that the upstream servers are able to translate a call of the form /myapi/service/item# (final‑element style) to /myapi/service.php/?item=item# (query‑string style), which is the “native” URI format for PHP, the language for our sample catalog, inventory, and review services.

For a more extensive NGINX Plus configuration that performs the translation itself, see Expanding the NGINX Configuration to Translate URIs below.

Let’s look at the configuration file section by section and explain what each part does:

  • Specify the JavaScript file:

  • Define a key‑value store to hold the list of API requests where the last element of the URI identifies the argument to the API program. The key uses the $uri_prefix variable to capture the portion of the URI before the last /:

    In NGINX Plus R16 and later, you can take advantage of two additional key‑value features:

    • Set an expiration time for the entries in a key‑value store, by adding the timeout parameter to the keyval_zone directive. For example, to have each batched URI expire daily, add timeout=1d.
    • Synchronize the key‑value store across a cluster of NGINX Plus instances, by adding the sync parameter to the keyval_zone directive. You must also include the timeout parameter in this case.

    For instructions on setting up synchronization for key-value stores, see the NGINX Plus Admin Guide.

  • Define another key‑value store for APIs where the argument is identified in the query string. Here the key ($uri) captures the entire URI, not including the query string:

  • Define two maps to split the URI into two parts for APIs that accept requests where the argument is the last element of the URI. The $uri_prefix variable captures the URI elements before the last / and $uri_suffix captures the final element:

  • Define the upstream group of API servers:

  • Define the virtual server that handles client requests and subrequests:

  • Define a location to handle client requests that use the final‑element style, which is indicated to the batchAPI function by setting the $batch_api_arg_in_uri variable to on:

  • Define a location to handle client requests that use the query‑string style, which is indicated to the batchAPI function by setting the $batch_api_arg_in_uri variable to off:

  • Define the location that handles the subrequests:

  • Enable the NGINX Plus API so that data can be maintained in the key‑value store:

Here’s the complete configuration:

Configuring the Batched API Requests

We’re using the NGINX Plus key‑value store feature to map inbound client requests to a set of API requests to execute. The configuration in the previous section defines separate key‑value stores for the two request styles:

  • The key‑value store for the final‑element style is named batch_api and the key is the $uri_prefix variable, which captures the elements before the final / of the request URI.
  • The key‑value store for the query‑string style is named batch_api2 and the key is the $uri variable, which captures the complete request URI without the query string.

In both key‑value stores, the value associated with each key is a comma‑separated list of URIs that are made available at runtime in the $batch_api or $batch_api2 variables.

For final‑element style requests, we want to map a client request for /batch-api/product to requests to the three services /myapi/catalog, /myapi/inventory, and /myapi/review, such that a request for /batch-api/product/14286 results in requests to /myapi/catalog/14286, /myapi/product/14286 and /myapi/review/14286.

To add these mappings to the batch_api key‑value store, we send the following request to the NGINX Plus instance running on the local machine:

$ curl -iX POST -d '{"/batch-api/product":"/myapi/catalog,/myapi/inventory,/myapi/review"}' http://localhost/api/3/http/keyvals/batch_api

For the query‑string style requests, we want to map a client request for /batch-api2/product to requests to the three services /myapi/catalog.php, /myapi/inventory.php, and /myapi/review.php, such that a request for /batch-api2/product?item=14286 results in requests to /myapi/catalog?item=14286, /myapi/product?item=14286 and /myapi/review?item=14286.

To add these mappings to the batch_api2 key‑value store, we send this request:

$ curl -iX POST -d '{"/batch-api2/product":"/myapi/catalog.php,/myapi/inventory.php,/myapi/review.php"}' http://localhost/api/3/http/keyvals/batch_api2

Running the Examples

The same PHP pages handle requests made in both request styles. For simplicity, we assume the backend servers are able to translate final‑element style URIs (/myapi/service/item#) to query‑string style URIs (/myapi/service.php?item=item#). It’s not necessary to translate query‑string style URIs, as that is the “native” URI format for PHP programs.

All of the services return a JSON‑formatted response that includes the service name and item argument. For example, a request for /myapi/catalog.php?item=14286 results in the following response from catalog.php:

{"service":"Catalog","item":"14286"}

Although the PHP programs used in these examples include the service name in the response, this might not be the case for all services. To help match responses to the services that generated them, the JavaScript program includes the URI in the aggregated response.

For example, the aggregated response for a request for /batch-api/product/14286 is as follows (although the order of the three component responses might vary):

[["/myapi/review/14286",{"service":"Review","item":"14286"}],["/myapi/inventory/14286",{"service":"Inventory","item":"14286"}],["/myapi/catalog/14286",{"service":"Catalog","item":"14286"}]]

Expanding the NGINX Plus Configuration to Translate URIs

As mentioned, the minimal NGINX Plus configuration discussed above assumes that the API servers are able to translate URIs of the format /myapi/service/item# to /myapi/service.php/?item=item#. We can also implement the translation in the NGINX Plus configuration by making the following changes.

  • We add a new upstream group to receive the subrequests:

  • We add a new virtual server to listen for requests received by the services upstream group. When query‑string style requests are received, they are simply proxied to the api_servers upstream group because the URI already has the .php extension. When final‑element style requests are received, the URI is rewritten so that the last element in the URI is removed and converted to a query‑string argument:

  • We change the /myapi location to proxy to the new upstream group:

Here’s the complete configuration:

Additional Components

The sample JavaScript programs and NGINX Plus configurations can be adapted to other styles of APIs, but if you want to test using all the components used to create this blog post, they are available in our Gist repo on GitHub, including the NGINX Unit configuration for serving the PHP pages:

batch-api.js
catalog.php
inventory.php
review.php
unit.config

Conclusion

These examples show how to batch API requests and provide an aggregated response to the client. The NGINX Plus key‑value store allows us to configure the API requests dynamically with the NGINX Plus API. API systems vary quite widely in their exact operations so there are many enhancements that can be made to this example to fit the needs of a particular API.

You can start testing NGINX JavaScript subrequests with NGINX Open Source, but if you want to try the API batching examples or test other use cases that take advantage of the NGINX Plus features like the key‑value store and NGINX Plus API, request a 30-day free trial and start experimenting.

Hero image
免费白皮书:
NGINX 企阅版全解析

助力企业用户规避开源治理风险,应对开源使用挑战

关于作者

Rick Nelson

Rick Nelson

方案工程区域副总裁

Rick Nelson is the Manager of Pre‑Sales, with over 30 years of experience in technical and leadership roles at a variety of technology companies, including Riverbed Technology. From virtualization to load balancing to accelerating application delivery, Rick brings deep technical expertise and a proven approach to maximizing customer success.

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。