Using httpClient from within JavaScript callout, is waitForComplete() necessary?

Suppose I would like to have a JS callout send a "fire and forget" request. Is it possible?

I know that I can use the httpClient to perform asynchronous requests. For example, in the request flow, I can have this JS:

var r = httpClient.get("https://google.com");
// set the pending request into a context variable
context.setVariable('pendingResponse', r); 

...and then follow it up with a companion JSC in the response flow that does something like this:

var r = context.getVariable('pendingResponse');
if (r) {
    // retrieve the pending request from the context variable
    r.waitForComplete();
    if (r.isSuccess()) {
      context.setVariable('asyncResponseContent', r.getResponse().content);
    }
}

What that combination of things does is: it sends the HTTP GET during the request flow, then does not block. Apigee may then invoke the target. Then, in the response flow, the JSC blocks for the response to the GET, and retrieves the contents.

My question is, suppose the request is a POST, and I don't care about the response at all?

Can I just omit the r.waitForComplete() part ? Will the http request complete as desired?

Solved Solved
1 4 16.2K
1 ACCEPTED SOLUTION

Yes, it is possible to use the httpClient without calling waitForComplete() .

You might want to do this to avoid introducing latency in the proxy request or response flow. The HTTP request will actually complete, but the proxy flow will not wait for it.

For example, suppose you are sending a log message out to a log aggregation service, over HTTP. It might require 100ms in round-trip time to send that log message. Rather than forcing your API Proxy to wait for that log message to complete, before returning a response to the client, you can just omit the waitForComplete(), and respond "right now" to the client.

Example working JS code:

// log-To-Stackdriver.js
// ------------------------------------------------------------------
//
// Send a POST to stackdriver without waiting for a response.
//
// created: Wed Feb 15 16:28:55 2017
// last saved: <2017-February-15 18:43:10>


var variableNameRe = "[^ \t\n\"',/\\\\]+?"; // non-greedy capture
var varPrefixRe = '{';
var varSuffixRe = '}';
var variableRegex = new RegExp( varPrefixRe + '(' + variableNameRe + ')' + varSuffixRe, 'g');


function fillTemplate(template) {
  // substitute all names surrounded by {curly_braces} in the template
  // with the value of the corresponding context variables
  var match;
  while ((match = variableRegex.exec(template)) !== null) {
    var variableName = match[1];
    var value = context.getVariable(variableName);
    if (value && value !== '') {
      template = template.replace('{' + variableName + '}', value);
    }
    else {
      template = template.replace('{' + variableName + '}', 'n/a');
    }
  }
  return template + ''; // coerce to JS String
}


// fire and forget
var payload = fillTemplate(properties.payload);
var headers = {
      'Content-Type' : 'application/json',
      'Authorization' : fillTemplate(properties.authz_header)
    };
var url = fillTemplate(properties.endpoint);
var req = new Request(url, 'POST', headers, payload);
var exchange = httpClient.send(req);

Companion Policy configuration:

<Javascript name='JS-Log-To-Stackdriver' timeLimit='400'>
  <Properties>
    <Property name='authz_header'>Bearer {stackdriver.token}</Property>
    <Property name='payload'>{
  "logName": "projects/{stackdriver.projectid}/logs/{stackdriver.logid}",
  "resource" : {
    "type": "api",
    "labels": {}
  },
  "labels": {
      "flavor": "test"
  },
  "entries": [{
      "severity" : "INFO",
      "textPayload" : "{stackdriver.logpayload}"
     }
  ],
  "partialSuccess": true
}</Property>
    <Property name='endpoint'>https://logging.googleapis.com/v2/entries:write</Property>
  </Properties>
  <ResourceURL>jsc://log-To-Stackdriver.js</ResourceURL>
</Javascript>

Doing it this way means your API proxy logic will not be able to catch errors if the log aggregation service begins rejecting requests. Therefore you should use this "fire and forget" pattern if and only if you have some other way to ensure that your messages are flowing through to the external system. For example, some after-the-fact correlation of log messages or transaction IDs. Or even a gross count of transactions.

View solution in original post

4 REPLIES 4

Yes, it is possible to use the httpClient without calling waitForComplete() .

You might want to do this to avoid introducing latency in the proxy request or response flow. The HTTP request will actually complete, but the proxy flow will not wait for it.

For example, suppose you are sending a log message out to a log aggregation service, over HTTP. It might require 100ms in round-trip time to send that log message. Rather than forcing your API Proxy to wait for that log message to complete, before returning a response to the client, you can just omit the waitForComplete(), and respond "right now" to the client.

Example working JS code:

// log-To-Stackdriver.js
// ------------------------------------------------------------------
//
// Send a POST to stackdriver without waiting for a response.
//
// created: Wed Feb 15 16:28:55 2017
// last saved: <2017-February-15 18:43:10>


var variableNameRe = "[^ \t\n\"',/\\\\]+?"; // non-greedy capture
var varPrefixRe = '{';
var varSuffixRe = '}';
var variableRegex = new RegExp( varPrefixRe + '(' + variableNameRe + ')' + varSuffixRe, 'g');


function fillTemplate(template) {
  // substitute all names surrounded by {curly_braces} in the template
  // with the value of the corresponding context variables
  var match;
  while ((match = variableRegex.exec(template)) !== null) {
    var variableName = match[1];
    var value = context.getVariable(variableName);
    if (value && value !== '') {
      template = template.replace('{' + variableName + '}', value);
    }
    else {
      template = template.replace('{' + variableName + '}', 'n/a');
    }
  }
  return template + ''; // coerce to JS String
}


// fire and forget
var payload = fillTemplate(properties.payload);
var headers = {
      'Content-Type' : 'application/json',
      'Authorization' : fillTemplate(properties.authz_header)
    };
var url = fillTemplate(properties.endpoint);
var req = new Request(url, 'POST', headers, payload);
var exchange = httpClient.send(req);

Companion Policy configuration:

<Javascript name='JS-Log-To-Stackdriver' timeLimit='400'>
  <Properties>
    <Property name='authz_header'>Bearer {stackdriver.token}</Property>
    <Property name='payload'>{
  "logName": "projects/{stackdriver.projectid}/logs/{stackdriver.logid}",
  "resource" : {
    "type": "api",
    "labels": {}
  },
  "labels": {
      "flavor": "test"
  },
  "entries": [{
      "severity" : "INFO",
      "textPayload" : "{stackdriver.logpayload}"
     }
  ],
  "partialSuccess": true
}</Property>
    <Property name='endpoint'>https://logging.googleapis.com/v2/entries:write</Property>
  </Properties>
  <ResourceURL>jsc://log-To-Stackdriver.js</ResourceURL>
</Javascript>

Doing it this way means your API proxy logic will not be able to catch errors if the log aggregation service begins rejecting requests. Therefore you should use this "fire and forget" pattern if and only if you have some other way to ensure that your messages are flowing through to the external system. For example, some after-the-fact correlation of log messages or transaction IDs. Or even a gross count of transactions.

Yes, I am aware that I answered my own question.

And now I'm commenting on my answer to my question.

@Dino How do we invoke 2 way SSL using the above approach? I know its possible in Node JS. But is it possible in Javascript?

@psunda AFAIK you can't do that.
The best you can do is have a different proxy with target server (2 way SSL) and javascript making calls to that proxy if you want the entire logic in javascript.

I know this is not efficient as there is no local chaining from the javascript to the proxy like the proxy - proxy local connections.

One thing I don't know or you want to check is if Apigee support team can configure an internal ELB for this purpose so that you can have a separate virtual host for just these kind of calls and the javascript to the routing proxy with 2 way SSL to the backend executes fast.