Inbound REST through the integration agent (GIG part 8)

Upgrades! The latest version of SmartOvens™ introduces an outbound REST webhook! This is very apropos because Master Chef needed another oven and having one integration agent on each oven isn’t very scalable. Since REST is a network operation rather than an operating system operation, we can move the integration agent to a central server on the network and have each oven communicate to that central server! So read on as we introduce REST web services and port our ovens integration to use an inbound REST call.

 

Despite how it sounds, REST webhooks are not lazy. On the contrary, they are the industry standard for making web service integrations. If you are familiar with SOAP web services, then you know how much fun dealing with WSDLs, bindings and building data type appropriate XML messages can be. For those not familiar with SOAP, consider yourself lucky. REST web services are much lighter weight and provide for greater flexibility both on the server and client side.

In a RESTful transaction, there are two sides, a client and a server. The client makes a request to the server who then replies with a response. Some REST messages are sent as XML, but typically they will be JSON payloads.
In the image below, the client is making a request with a payload of [{"city":"Paris", "units":"C"}] to the server’s /service/weather REST interface. I prefer the term “endpoint” instead of interface and that is what our awesome docs team uses, so going forward I’ll use that term. The server replies with a payload of [{"low":"16", "high":"23"}].

 

Ok, now that we’ve got that out of the way, let’s get digging. Oh, darn, one more thing, we’ll need to upgrade to at least the 5.1.5 Integration Agent. This version now includes support for accepting generic HTTP POST messages.

The new ovens upgrade sends out HTTP POSTs in a form data type format. For example:
ovenname=Main_SW&timerstart=03%2F29%2F2015%2012%3A05&timerend=03%2F29%2F2015%2012%3A35

This looks pretty daunting, but if we break it up on the &’s we get:

ovenname=Main_SW
timerstart=03%2F29%2F2015%2012%3A05
timerend=03%2F29%2F2015%2012%3A35

Which looks a little better. A keen eye will notice that the timerstart and timerend are just url encoded dates. If we "urldecode" these we get:

ovenname=Main_SW
timerstart=03/29/2015 12:05
timerend=03/29/2015 12:35

That looks better. We’ll need to translate the names into the names in the ovens Comm Plan form, but luckily a little javascript will do that no problem.

Now that we understand what the oven is sending over, we can dig into the IA code. Fire up your favorite text editor and open the IAHOME/integrationservices/ovens/ovens.js file. In part 6, we used the apia_event function to deal with the incoming command line request. For this incoming HTTP request, we are going to use the apia_http function. This function is passed two parameters:

  • httpRequestProperties - The incoming request object including headers and body. A wrapper for ServletRequest
  • httpResponse - The response object to respond back to the http client. A wrapper for ServletResponse

In order to use the code below, you'll need to import a couple of Java classes. Fortunately, this is as simple as pasting in the following couple of lines at the top of your .js file:

importClass(Packages.org.apache.commons.httpclient.HttpVersion);
importClass(Packages.org.apache.commons.httpclient.Header);
importClass(Packages.org.mule.providers.http.HttpResponse);

I’ve pasted the rest of the code here and made plenty of comments that should explain what each item is doing. If you have further questions, drop them in the comments.

function apia_http(httpRequestProperties, httpResponse) {

  // First some debugging. State we are entering the
  // apia_http function. This helps sort out where
  // we are in the code.
  IALOG.debug("Enter - apia_http");


  // Get the payload that is being sent over and dump it to the log.
  var payloadSTR = httpRequestProperties.getProperty("REQUEST_BODY");
  IALOG.debug("PAYLOAD: " + payloadSTR );

  // Generate our event object
  var eventObj = XMUtil.createEventTemplate();
  eventObj.properties = {};

  // Parse the incoming data so we can turn it
  // into the JSON required for xM REST web services
  // First break it up based on the &'s
  var items = payloadSTR.split( '&' );

  // For each item in the array...
  for( i in items ) {

    // Now split on the ='s to
    // get the name/value pair
    temp = items[i].split( '=' );

    name = temp[0];
    value = '' + temp[1]; // Force it to a string
    IALOG.debug( 'Name: ' + name + '. Val: ' + value );

    // Next, add each item to the appropriate
    // place in the event object. We have to
    // do some logic here because the names
    // from the oven don't quite match what
    // we need in xM.
    if( name == "ovenname" )
        eventObj.properties.Oven_Name = value;
    else if( name == "timerstart" )
        eventObj.properties.Timer_Start_Time = decodeURIComponent( value );
    else if( name == "timerend" )
        eventObj.properties.Timer_End_Time = decodeURIComponent( value );
  }

  // Add in chef as the recipient
  eventObj.recipients = [ {"targetName":"chef"} ];


  // Let's just print our event to the log so we can see what it looks like
  IALOG.info("Event:\n" + JSON.stringify(eventObj, null, 2)); // pretty print json

  // Now send the event over to our ovens form.
  // The XMIO.post takes a string, so we stringify our
  // eventObj
  XMIO.post( JSON.stringify( eventObj ));

  // Finally, let's return a nice message to
  // the system sending the request.
  var responseBody = "";
  var responseStatus = "";

  // Set the headers
  var header = new Header("Content-Type", httpRequestProperties.getProperty("Content-Type"));
  httpResponse.setHeader(header);

  // The http status code of 200 means A-OK.
  responseStatus = 200;
  responseBody = "OK";

  httpResponse.setStatusLine(HttpVersion.HTTP_1_1, responseStatus);
  httpResponse.setBodyString(responseBody);

  IALOG.debug("Exit - apia_http");

  // Send the response object back to the client
  return httpResponse;

}

Save the file and restart the integration agent. (Remember we need the integration agent to pick up the code changes, so we restart it.) Then I’ll kick off the oven time and a while later, we see stuff posted to our integration agent logs:
Enter the function:

2015-03-31 19:25:18,397 [applications|ovens-1] DEBUG - Enter - apia_http

Our incoming payload!

2015-03-31 19:25:18,400 [applications|ovens-1] DEBUG - PAYLOAD: ovenname=Main_SW&timerstart=03%2F29%2F2015%2012%3A05&timerend=03%2F29%2F2015%2012%3A35

Random integration agent overhead stuff:

2015-03-31 19:25:18,400 [applications|ovens-1] INFO - Determining if there is an active Integration Service (applications,ovens) on any Integration Agent...
2015-03-31 19:25:18,401 [applications|ovens-1] DEBUG - Searching local Integration Agent ip-222-222-222-222/888.888.888.888:8081...
2015-03-31 19:25:18,401 [applications|ovens-1] INFO - Lookup succeeded.  Found an active Integration Service on Integration Agent ip-222-222-222-222/888.888.888.888:8081.

Next comes the parsing of the name/value pairs:

2015-03-31 19:25:18,410 [applications|ovens-1] DEBUG - Name: ovenname. Val: Main_SW
2015-03-31 19:25:18,411 [applications|ovens-1] DEBUG - Name: timerstart. Val: 03%2F29%2F2015%2012%3A05
2015-03-31 19:25:18,411 [applications|ovens-1] DEBUG - Name: timerend. Val: 03%2F29%2F2015%2012%3A35

Our eventObj! Woah, wait where did that callback stuff come from? The XMUtil.createEventTemplate function builds this in based on the CALLBACKS variable in our configuration.js file. Since I had just “response” in there, it only added that one.

2015-03-31 19:25:18,413 [applications|ovens-1] INFO - Event:
{
  "callbacks": [
    {
      "url": "ia://ovens",
      "type": "response",
      "iaId": "ip-222-222-222-222/888.888.888.888:8081"
    }
  ],
  "properties": {
    "Oven_Name": "Main_SW",
    "Timer_Start_Time": "03/29/2015 12:05",
    "Timer_End_Time": "03/29/2015 12:35"
  },
  "recipients": [
    {
      "targetName": "chef"
    }
  ]
}

More integration agent stuff, showing the HTTP endpoint and payload.

2015-03-31 19:25:18,460 [applications|ovens-1] DEBUG - POST to: https://company.dc.xmatters.com/reapi/2015-01-01/forms/898fb04f-5922-4194-ab0a-852fbaea3645/triggers with payload: {"callbacks":[{"url":"ia://ovens","type":"response","iaId":"ip-172-31-28-212/172.31.28.212:8081"}],"properties":{"Oven_Name":"Main_SW","Timer_Start_Time":"03/29/2015 12:05","Timer_End_Time":"03/29/2015 12:35"},"recipients":[{"targetName":"chef"}]}

Woo! Our event ID!

2015-03-31 19:25:19,098 [applications|ovens-1] INFO - xMatters response code: 200 and payload: {"id":"992008"}
2015-03-31 19:25:19,100 [applications|ovens-1] DEBUG - Exit - apia_http
2015-03-31 19:25:19,100 [applications|ovens-1] INFO - The JavaScript method apia_http returned an object of type class org.mule.providers.http.HttpResponse.

Not bad. We were able to get the integration agent to accept an incoming HTTP POST in an arbitrary format, translate that into the Comm Plan JSON format and pass that off to the Comm Plan form.

When I am building these things, I find that picking apart several ways to do the same thing gives good perspective on when I need to do it a different way. So, if the stuff above doesn’t get all the information you need, the integration agent comes packaged with a working example in the IAHOME/integrationservices/applications/sample-relevance-engine directory. This comes complete with a sample-relevance-engine-http-inject executable file. If you crack this open, it is just a curl command that will send the payload over to the integration. The sample-relevance-engine.js file then parses this XML and translates it into a JSON format and fires it over to the Comm Plan form.

Click here for the next installment of the GIG!

Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.
Powered by Zendesk