I"m working in a scenario where I'm getting xml object from the httpClient's getResponse().content. And I need to deal with values that are present in the response. But its not allowing me to extract those values in javascript.
Note: I cannot use XML-to-JSON like policies in my use-case; I can only work with javascript. Because I'm calling multiple HTTP requests based on the ids present in the input.
var url = properties.endpoint + '?billingId=' + billingIds[index]; var myRequest = new Request(url, "GET"); context.setVariable("myRequesttest", myRequest); var exchange = httpClient.send(myRequest); exchange.waitForComplete(); if (exchange.isSuccess()) { var responseObj = exchange.getResponse().content; context.setVariable("response.content", responseObj); context.setVariable("myResponse", responseObj); } else if (exchange.isError()) { context.setVariable("myResponseError", exchange.getError()); }
help?
Solved! Go to Solution.
You can use the JS callout to invoke multiple external systems (via the builtin httpClient), and you can then use logic in the JS to combine the results into one object or payload. This is nice if the number of external systems you need to invoke is not determined until runtime. Maybe it's dependent upon the data provided in the input request, or based on data returned by the first call. So this is handy.
Having said that, there are limitations. The JS callout has a timeout on it, and if execution time exceeds that timeout, then the JS policy will fail. If the responses from your remote systems do not all return before the JS policy timeout expires, you won't get the results. Also, in a JS callout you cannot use the various npm modules that help you out with parsing XML and so on. For a more general solution then, I'd suggest a dedicated app logic host - something like Google App Engine, Cloud Run, or similar. But you can do it with a JS callout if you are comfortable with the limitations.
OK with the caveats and warnings out of the way, this is what worked for me.
// invokeTransformAndCombine.js var baseUrl = 'https://my-base-url.com/foobar'; var ids = [182872, 19882, 2133]; var accumulator = {}; // the asynchronous response handler function onComplete(id) { return function(response, error) { if (response) { if (response.status == 200) { var xmlobj = new XML(response.content); var json = E4XtoJSON(xmlobj); accumulator[id] = json; if (Object.keys(accumulator).length == ids.length) { context.setVariable('combined', JSON.stringify(accumulator, null, 2)); } } } else { context.setVariable('requesterror_' + id, 'Whoops: ' + error); } }; } // send out one request function invokeOne(id) { var url = baseUrl + '?id=' + id ; var headers = { Authorization : 'optional, pass whatever you like', 'ID-Index' : id }; var req = new Request(url, 'GET', headers); httpClient.send(req, onComplete(id)); } // set the default value (empty object) context.setVariable('combined', JSON.stringify(accumulator, null, 2)); // action starts here ids.forEach(invokeOne);
Let me explain the code.
The inner callback - there is some magic going on there. It checks the status code and only in the case of success (200 code) it does a few things.
var xmlobj = new XML(response.content); var json = E4XtoJSON(xmlobj); accumulator[id] = json; if (Object.keys(accumulator).length == ids.length) { context.setVariable('combined', JSON.stringify(accumulator, null, 2)); }
All of that should be pretty clear. The remaining mystery is the E4XtoJSON function. I got that from this gist. To use it within a JS inside Apigee, I had to drop that code into a separate 'resource' under the jsc directory in the proxy bundle, and then configure the policy this way:
<Javascript name='JS-InvokeTransformAndCombine' timeLimit='2200' > <IncludeURL>jsc://E4XtoJSON.js</IncludeURL> <ResourceURL>jsc://invokeTransformAndCombine.js</ResourceURL> </Javascript>
When I invoke this repeatedly with a service that returns XML shaped like this:
<billinfo> <stamp>1613696215531</stamp> <localtime>2021-02-19T00:56:55Z</localtime> <id>902</id> <status>rejected</status> <code>JN23H</code> <amount>376.44</amount> </billinfo>
... the output of the Javascript I showed above is a combined JSON blob that looks like this:
{ "19882": { "stamp": "1613696265138", "localtime": "2021-02-19T00:57:45Z", "id": "19882", "status": "ready", "code": "E8237k", "amount": "788.10" }, "182872": { "stamp": "1613696265136", "localtime": "2021-02-19T00:57:45Z", "id": "182872", "status": "split", "code": "JN23H", "amount": "280.94" }, "2133": { "stamp": "1613696265141", "localtime": "2021-02-19T00:57:45Z", "id": "2133", "status": "rejected", "code": "SIWU97", "amount": "847.50" } }
If you don't want the full combined thing, but rather just a few fields extracted from the XML response, you can take the same approach but just grab the fields you want.
I haven't tried the E4XtoJSON with XML that uses namespaces. That's a wrinkle you will have to sort out.
helpful?