JavaScript Policy in Calling the Target Endpoint Multiple Times

Hi Apigee Community,

I have this target endpoint that is a Apigee Proxy also. It accepts billingId as a query parameter. The challenge is, the target endpoint only returns one response per billingId, so they suggested to call the API multiple times and then mapped all the response.  Any suggestion on how to make this using JavaScript policy? I am using Apigee Edge. 

Sample Repeated Requests:
<Proxyendpoint>/customer?billingId=1111

<Proxyendpoint>/customer?billingId=222

<Proxyendpoint>/customer?billingId=333

Then map all the response into one response.


Thanks!

Solved Solved
0 4 627
1 ACCEPTED SOLUTION

Yes, this is a common question here on the Apigee community.  I'd suggest looking through some prior discussions.

In your case, you could use JavaScript with the httpClient and the callback function, to do what you want. 

<Javascript name='JS-Invoke-Calls' timeLimit='4200' >
  <Properties>
    <Property name='target'>https://my-endpoint.com/path</Property>
    <Property name='items'>111,222,333</Property>
    <Property name='qparam'>billingId</Property>
  </Properties>
  <ResourceURL>jsc://invoke-calls-in-parallel.js</ResourceURL>
</Javascript>

And then the JS looks like this: 

var start = Date.now();
var responses = [];

function indexedOnComplete(index, numberOfCalls, response, error) {
  var prefix = 'jscall.'+ index.toFixed(0);
  context.setVariable(prefix +'.completed-at', String(Date.now() - start));
  if (response) {
    context.setVariable(prefix +'.response-status', response.status);
    // collect the response content
    responses.push(JSON.parse(response.content));
  }
  else {
    context.setVariable(prefix +'.error', 'Whoops: ' + error);
    responses.push(null);
  }

  // check if this is the final response. If so, set result variables in context.
  if (numberOfCalls == responses.length) {
    context.setVariable('jscall.all-done', String(Date.now() - start));
    // all of the responses
    context.setVariable('jscall.aggregated-result',
                        JSON.stringify(responses, null, 2));
  }
}

// invoke one outbound http call, using the callback method
function invokeOne(item, index, cb) {
  var url = properties.target + '?' + properties.qparam + '=' + item ;
  var headers = { 'Item-Index' : index };
  httpClient.send(new Request(url, 'GET', headers), cb);
}

// ====================================================================
var items = properties.items.split(new RegExp('\\s*,\\s*'));
var numberOfCalls = items.length;
context.setVariable('number-of-calls', numberOfCalls.toFixed(0));

items.forEach(function(currentItem, index) {
  context.setVariable('jscall.' + index.toFixed(0) + '.item', currentItem);
  var cb = function(response, error) {
        return indexedOnComplete(index, numberOfCalls, response, error);
      };
  return invokeOne(currentItem, index, cb);
});

View solution in original post

4 REPLIES 4

Yes, this is a common question here on the Apigee community.  I'd suggest looking through some prior discussions.

In your case, you could use JavaScript with the httpClient and the callback function, to do what you want. 

<Javascript name='JS-Invoke-Calls' timeLimit='4200' >
  <Properties>
    <Property name='target'>https://my-endpoint.com/path</Property>
    <Property name='items'>111,222,333</Property>
    <Property name='qparam'>billingId</Property>
  </Properties>
  <ResourceURL>jsc://invoke-calls-in-parallel.js</ResourceURL>
</Javascript>

And then the JS looks like this: 

var start = Date.now();
var responses = [];

function indexedOnComplete(index, numberOfCalls, response, error) {
  var prefix = 'jscall.'+ index.toFixed(0);
  context.setVariable(prefix +'.completed-at', String(Date.now() - start));
  if (response) {
    context.setVariable(prefix +'.response-status', response.status);
    // collect the response content
    responses.push(JSON.parse(response.content));
  }
  else {
    context.setVariable(prefix +'.error', 'Whoops: ' + error);
    responses.push(null);
  }

  // check if this is the final response. If so, set result variables in context.
  if (numberOfCalls == responses.length) {
    context.setVariable('jscall.all-done', String(Date.now() - start));
    // all of the responses
    context.setVariable('jscall.aggregated-result',
                        JSON.stringify(responses, null, 2));
  }
}

// invoke one outbound http call, using the callback method
function invokeOne(item, index, cb) {
  var url = properties.target + '?' + properties.qparam + '=' + item ;
  var headers = { 'Item-Index' : index };
  httpClient.send(new Request(url, 'GET', headers), cb);
}

// ====================================================================
var items = properties.items.split(new RegExp('\\s*,\\s*'));
var numberOfCalls = items.length;
context.setVariable('number-of-calls', numberOfCalls.toFixed(0));

items.forEach(function(currentItem, index) {
  context.setVariable('jscall.' + index.toFixed(0) + '.item', currentItem);
  var cb = function(response, error) {
        return indexedOnComplete(index, numberOfCalls, response, error);
      };
  return invokeOne(currentItem, index, cb);
});

Thanks Dino, this is a big help. I am now studying on how can I use it more on my use case. I have this incoming POST request with the request body in JSON. And I want to use the multiple secondary Id as an item on your given code that is passed as a query parameter. I am targeting to make a looping call that extracts that Id's and add it to the URL of the httpclient. For example: items = jsonRequest.Input[i].orders[j].secondaryId so it can also supports multiple billingId and Orders.

 

{
	"Input": [{
			"billingId": "12345",
			"Orders": [{
					"primaryId": "1",
					"secondaryId": ["11", "12"]
				}
			]
		}
	]
}

 

In my example JavaScript, line 34, it retrieves the items to query from the policy configuration:

 

var items = properties.items.split(new RegExp('\\s*,\\s*'));

 

But you want to retrieve your item list from the inbound request json. So remove line 34 from my example, and replace with something like this:

 

var request_payload = JSON.parse(context.getVariable('request.content'));
var items = request_payload.Input[0].Orders[0].secondaryId; // an array

 

and of course eliminate the unnecessary <Property name='items'...> element from the JS policy configuration.

Yes Dino, thanks, I already updated my items and get it from the JSON request body. Last thing, can you or someone explain the below code? Especially the arguments that we are passing.
Thanks very much!

items.forEach(function(currentItem, index) {
  context.setVariable('jscall.' + index.toFixed(0) + '.item', currentItem);
  var cb = function(response, error) {
        return indexedOnComplete(index, numberOfCalls, response, error);
      };
  return invokeOne(currentItem, index, cb);
});