Last active
February 16, 2023 03:01
-
-
Save nginx-gists/ffad47e81322d6b948ee187e56a39600 to your computer and use it in GitHub Desktop.
Batching API Requests with NGINX Plus and the NGINX JavaScript Module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
js_import batch-api-min.js; | |
# keyval_zone for APIs where the last portion of the URI is an argument | |
# The key is the portion of the URL before the last part | |
keyval_zone zone=batch_api:64k state=/etc/nginx/state-files/batch-api.json; | |
keyval $uri_prefix $batch_api zone=batch_api; | |
# keyval_zone for APIs where the last portion of the URI is an argument | |
# The key is the URI | |
keyval_zone zone=batch_api2:64k state=/etc/nginx/state-files/batch-api2.json; | |
keyval $uri $batch_api2 zone=batch_api2; | |
map $uri $uri_prefix { | |
~^(?<p>.+)\/.+$ $p; | |
} | |
map $uri $uri_suffix { | |
~^.+\/(?<s>.+)$ $s; | |
} | |
upstream api_servers { | |
zone api_servers 64k; | |
server 127.0.0.1:9080; | |
server 127.0.0.1:9081; | |
} | |
server { | |
listen 80; | |
location /batch-api { | |
set $batch_api_arg_in_uri on; | |
js_content batch-api-min.batchAPI; | |
} | |
location /batch-api2 { | |
set $batch_api_arg_in_uri off; | |
js_content batch-api-min.batchAPI; | |
} | |
location /myapi { | |
proxy_pass http://api_servers; | |
} | |
location /api { | |
api write=on; | |
# directives to restrict access to the API | |
} | |
} | |
# vim: syntax=nginx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* Copyright (C) 2019 NGINX, Inc., 2022 F5, Inc. | |
* This program is provided for demonstration purposes only. | |
*******************************************************************************/ | |
function batchAPI(r) { | |
var n = 0, requestCount = 0; | |
var resp = "["; | |
var errorOccured = false; | |
var keyval = "batch_api"; | |
function done(reply) { // Callback for completed subrequests | |
n++; | |
if (errorOccured) { /* Once one response has an error stop processing | |
any more responses */ | |
return; | |
} | |
if (n < requestCount) { | |
if (reply.status != 200) { | |
errorOccured = true; | |
r.log("Error in response " + n.toString() + " " + reply.uri + | |
" " + reply.status.toString()); | |
r.return(reply.status, "Error in response " + n.toString() + | |
" " + reply.uri + "\n"); | |
} else { | |
resp += '["' + reply.uri + '",' + reply.body + '],'; | |
} | |
} else { // Last response | |
if (reply.status != 200) { | |
errorOccured = true; | |
r.log("Error in response " + n.toString() + " " + reply.uri + | |
" " + reply.status.toString()); | |
r.return(reply.status, "Error in response " + n.toString() + | |
" " + reply.uri + "\n"); | |
} else { | |
resp += '["' + reply.uri + '",' + reply.body + ']'; | |
r.return(200, resp); | |
} | |
} | |
} | |
var argInURI = r.variables.batch_api_arg_in_uri.toLowerCase(); | |
if (argInURI != "on") { | |
keyval = "batch_api2"; | |
} | |
var apiURIs = r.variables[keyval].split(","); | |
requestCount = apiURIs.length; | |
for (var i = 0; i < requestCount; i++) { | |
if (argInURI == "on") { | |
r.subrequest(apiURIs[i] + "/" + r.variables.uri_suffix, | |
r.variables.args, done); | |
} else { | |
r.subrequest(apiURIs[i], r.variables.args, done); | |
} | |
} | |
} | |
export default { batchAPI } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
js_import batch-api.js; | |
# keyval_zone for APIs where the last portion of the URI is an argument | |
# The key is the portion of the URL before the last part set in the map | |
keyval_zone zone=batch_api:64k state=/etc/nginx/state-files/batch-api.json; | |
keyval $uri_prefix $batch_api zone=batch_api; | |
# keyval_zone for APIs where the last portion of the URI is an argument | |
# The key is the URI | |
keyval_zone zone=batch_api2:64k state=/etc/nginx/state-files/batch-api2.json; | |
keyval $uri $batch_api2 zone=batch_api2; | |
# These maps are for breaking the URI into two parts for APIs where the | |
# last part of the URI is an argument. For URIs of the form: | |
# /<part 1>/<part 2>/<part n> | |
# $uri_prefix = /<part 1>/<part 2> | |
# $uri_suffix = <part n> | |
map $uri $uri_prefix { | |
~^(?<p>.+)\/.+$ $p; | |
} | |
map $uri $uri_suffix { | |
~^.+\/(?<s>.+)$ $s; | |
} | |
upstream api_servers { | |
zone api_servers 64k; | |
server 127.0.0.1:9080; | |
server 127.0.0.1:9081; | |
} | |
upstream services { | |
zone services 64k; | |
server 127.0.0.1:9000; | |
} | |
server { | |
listen 9000; | |
location / { | |
rewrite ^(.+)\/(.+)$ $1.php?item=$2 last; | |
} | |
location ~ \.php$ { | |
proxy_pass http://api_servers; | |
} | |
} | |
server { | |
listen 80; | |
set $batch_api_verbose on; | |
location /batch-api { | |
set $batch_api_arg_in_uri on; | |
js_content batch-api.batchAPI; | |
} | |
location /batch-api2 { | |
set $batch_api_arg_in_uri off; | |
js_content batch-api.batchAPI; | |
} | |
location /myapi { | |
proxy_pass http://services; | |
} | |
location /api { | |
api write=on; | |
# directives to restrict access to the API | |
} | |
} | |
# vim: syntax=nginx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* Copyright (C) 2019 NGINX, Inc., 2022 F5, Inc. | |
* | |
* This program is provided for demonstration purposes only. | |
* | |
* Make a series of API subrequests based on one input request and return an | |
* aggregated response in JSON format as list. | |
* | |
* Two styles of API requests are supported: | |
* 2. The last portion of the URI is a value to be included in all | |
* requests. For example, a request to /batch-api/product/123 will result | |
* in requests to /myapi/catalog/123, /myapi/inventory/123 and | |
* /myapi/review/123. | |
* 1. The URI specifies a endpoint and arguments required are passed in the | |
* query string. For example, a request to /batch-api2/product?item=123 | |
* will result in requests for /myapi/catalog.php?item=123, | |
* /myapi/inventory.php?item=123 and /myapi/review.php?item=123. Some | |
* APIs may pass required arguments in a prior request so this code does | |
* not require any arguments. | |
* | |
* All request arguments included in the intiial request are passed to all the | |
* batched requests. | |
* | |
* The NGINX variable batch_api_arg_in_uri controls which of the above two styles | |
* to use. The first style is specified with a value of "o n" and the second | |
* with a value of "off". The default is "on". | |
* | |
* The NGINX Plus key-value store is used to keep mappings of input URIs and | |
* a comma seperated list of the API URIs to call. | |
* | |
* For the first style, the key | |
* is the value of the URI before the last slash. For example, a request for | |
* /batch-api/product/123 would have a key of /batch-api/product. An NGINX | |
* map is used to get the value of the key as the variable $uri_prefix and | |
* the value of the argument as $uri_suffix. | |
* | |
* For the second style, the key is the URI. For example, a request to | |
* /batch-api2/product?item=123 would have a key of /batch-api2/product. | |
* | |
* The name of the key-value store for the first style is "batch_api" and for | |
* the second it is "batch_api2". These names could be set using NGINX | |
* variables, but for simplicity they have been hardcoded. | |
* | |
* The subrequests are made in order, but they are asynchronous so the requests | |
* are executed in parallel so the responses may come out of order. | |
* | |
* If there is an error for any requests, an error is returned to the client | |
* and the none of the data from any of the responses is returned. | |
* | |
* A message to the NGINX error log is written for any errors. Additional | |
* informationa messages will be written to the error log if the NGINX variable | |
* batchapi_verbose is set to "on". | |
* | |
* NGINX Plus Key-Value Store | |
* | |
* Example NGINX Plus configuration - style 1: | |
* keyval_zone zone=batch_api:64k; | |
* keyval $uri_prefix $batch_api zone=batch_api; | |
* | |
* Data: | |
* Key: "/batch-api/product" | |
* Value: "/myapi/catalog,/myapi/inventory,/myapi/review" | |
* | |
* Example NGINX Plus configuration - style 2: | |
* keyval_zone zone=batch_api2:64k; | |
* keyval $uri $batch_api2 zone=batch_api2; | |
* | |
* Data: | |
* Key: "/batch-api2/product" | |
* Value: "/myapi/catalog.php,/myapi/inventory.php,/myapi/review.php" | |
*******************************************************************************/ | |
/******************************************************************************* | |
* Make a series of API requests as defined in the NGINX Plus key value store | |
*******************************************************************************/ | |
function batchAPI(r) { | |
var n = 0, requestCount = 0; | |
var resp = "["; | |
var errorOccured = false; | |
var keyval = "batch_api"; | |
function done(reply) { // Callback for completed subrequests | |
n++; | |
if (errorOccured) { /* Once one response has an error stop processing | |
any more responses */ | |
logVerbose(verbose, r, "Response " + n.toString() + " " + | |
reply.uri + " " + reply.status.toString() + | |
" Error occured in previous request"); | |
return; | |
} | |
if (n < requestCount) { | |
if (reply.status != 200) { | |
errorOccured = true; | |
r.log("Error in response " + n.toString() + " " + reply.uri + | |
" " + reply.status.toString()); | |
r.return(reply.status, "Error in response " + n.toString() + | |
" " + reply.uri + "\n"); | |
} else { | |
logVerbose(verbose, r, "Response " + n.toString() + " " + | |
reply.uri + " " + reply.status.toString()); | |
resp += '["' + reply.uri + '",' + reply.body + '],'; | |
} | |
} else { // Last response | |
if (reply.status != 200) { | |
errorOccured = true; | |
r.log("Error in response " + n.toString() + " " + reply.uri + | |
" " + reply.status.toString()); | |
r.return(reply.status, "Error in response " + n.toString() + | |
" " + reply.uri + "\n"); | |
} else { | |
logVerbose(verbose, r, "Response " + n.toString() + " " + | |
reply.uri + " " + reply.status.toString()); | |
resp += '["' + reply.uri + '",' + reply.body + ']'; | |
logVerbose(verbose, r, "Aggregated Response: " + resp); | |
r.return(200, resp); | |
} | |
} | |
} | |
var argInURI = r.variables.batch_api_arg_in_uri.toLowerCase(); | |
if (argInURI != "on") { | |
keyval = "batch_api2"; | |
} | |
var verbose = r.variables.batch_api_verbose.toLowerCase(); | |
logVerbose(verbose, r, "argInURI: " + argInURI + " keyval: " + keyval); | |
logVerbose(verbose, r, "prefix: " + r.variables.uri_prefix + | |
" suffix: " + r.variables.uri_suffix); | |
// There must be a key value for the request uri | |
if (r.variables[keyval] == '') { | |
r.log("Error: No value for " + r.uri + " in key-value store " + | |
keyval); | |
r.return(400, "Invalid request: No API requests specified for " + | |
r.variables.uri + "\n"); | |
} else { | |
logVerbose(verbose, r, "API URIs: " + r.variables[keyval]); | |
logVerbose(verbose, r, "Args: " + r.variables.args); | |
var apiURIs = r.variables[keyval].split(","); | |
requestCount = apiURIs.length; | |
for (var i = 0; i < requestCount; i++) { | |
if (argInURI == "on") { | |
logVerbose(verbose, r, "Subrequest: " + apiURIs[i] + "/" + | |
r.variables.uri_suffix + "?" + r.variables.args); | |
r.subrequest(apiURIs[i] + "/" + r.variables.uri_suffix, | |
r.variables.args, done); | |
} else { | |
logVerbose(verbose, r, "Subrequest: " + apiURIs[i] + "?" + | |
r.variables.args); | |
r.subrequest(apiURIs[i], r.variables.args, done); | |
} | |
} | |
} | |
} | |
/******************************************************************************* | |
* Log Verbose Message | |
*******************************************************************************/ | |
function logVerbose(verbose, r, message) { | |
if (verbose == "on") { | |
r.log(message); | |
} | |
} | |
export default { batchAPI } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if (isset($_GET['item'])) { | |
$item = $_GET['item']; | |
} else { | |
$item = 'unset'; | |
} | |
if (isset($_GET['x'])) { | |
$x = $_GET['x']; | |
} else { | |
$x = 'unset'; | |
} | |
print('{"service":"Catalog","item":"' . $item . '"}' . "\n"); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if (isset($_GET['item'])) { | |
$item = $_GET['item']; | |
} else { | |
$item = 'unset'; | |
} | |
print('{"service":"Inventory","item":"' . $item . '"}' . "\n"); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if (isset($_GET['item'])) { | |
$item = $_GET['item']; | |
} else { | |
$item = 'unset'; | |
} | |
print('{"service":"Review","item":"' . $item . '"}' . "\n"); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"applications": { | |
"batch-api": { | |
"type": "php", | |
"workers": 20, | |
"root": "/srv/app/content", | |
"limits": { | |
"timeout": 65 | |
} | |
} | |
}, | |
"listeners": { | |
"*:9080": { | |
"application": "batch-api" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For a discussion of these files, see Batching API Requests with NGINX Plus and the NGINX JavaScript Module