X

Best Practices from Oracle Development's A‑Team

Service Callouts in API Platform

Andy Knight
Principal Solution Architect

What is a Service Callout?

A Service Callout is an invocation of some arbitrary endpoint at some stage during processing of the Request pipeline. Service Callouts are not available in the Response pipeline.

Using the Service Callout Policy

The Service Callout Policy is documented here

The key aspect of this Policy is the fact that its use is limited to the evaluation of the invoked endpoint's HTTP return code. You might, for example, invoke an endpoint and check that it responds with HTTP 200. If the response does not match the configured value, the pipeline processing will terminate with an exception and HTTP 403 will be returned to the client. Note that the response to the client is not necessarily the status value returned by the invoked endpoint. For example, if the endpoint returns 201 (and only 200 is classed as PASS) then the client will see 403 (FORBIDDEN).

What you will immediately realise is that this Policy can not be used to retrieve any payload from the invoked endpoint. In order to do that you will need to use a Groovy Policy.

Service Callouts from a Groovy Policy

Here is the essential framework that you will need to implement a Service Callout from within a Groovy Policy.

ExternalServiceCallout callout = context.newCallout()
ExternalServiceCallout.Callback callback = new ExternalServiceCallout.Callback() {
  boolean onCompleted(ServiceResponse response) throws PolicyProcessingException {
    return true
  }
  boolean onThrowable(Throwable throwable) throws PolicyProcessingException {
    return false
  }
}
callout.setMethod("GET") // Or whatever method is appropriate
callout.setRequestURL("<ServiceEndpointURL>")
callout.sendAsync(callback)

The methods indicated in this code fragment and invoked on the ExternalServiceCallout object will all be needed as a minimum requirement for any kind of functionality. Other methods are available for setting headers, query parameters and, where appropriate, a body.

The name of the sendAsync method gives a clue as to its behaviour. The service endpoint will be executed asynchronously with respect to any other APIs currently being managed by the gateway. This means that processing of the Request pipeline effectively ceases until such time as either the onCompleted or onThrowable methods of the callback instance have been invoked.

Let's contrive a simple use-case.

The Service Request that our API definition ultimately invokes requires a JSON payload that includes the current date and time. However, our client doesn't provide this information in its POST payload - so we need to get it from somewhere. Of course we could just use standard Java libraries to do this but we're going to call a public API because we want to show off our skills with making external service callouts ;-)

Our Service Request requires a payload that looks like this:-

{ "name": "some name", "description": "some description", "date": "a date in Zulu time"}

...but the client only POSTs this:-

{ "name": "", "description": ""}

We know that there is a convenient public API that can help us. It lives here:- http://worldclockapi.com/api/json/utc/now

The response from that API has this structure:-

{"$id":"1",
  "currentDateTime":"2019-0807T14:55Z",
  "utcOffset":"00:00:00",
  "isDayLightSavingsTime":false,
  "dayOfTheWeek":"Wednesday",
  "timeZoneName":"UTC",
  "currentFileTime":132096633561154206,
  "ordinalDate":"2019-219",
  "serviceResponse":null}

In summary therefore, we need to invoke this API (Service Callout), extract the currentDateTime key and enrich the payload being POSTed to our defined Service Request. This is how we would do it:-

ExternalServiceCallout callout = context.newCallout()
ExternalServiceCallout.Callback callback = new ExternalServiceCallout.Callback() {
  boolean onCompleted(ServiceResponse response) throws PolicyProcessingException {
  def body = response.getBody().asJSONObject() // The JSON response from worldclockapi
  def datetime = body.getString("currentDateTime") // extract the currentDateTime value
  body = context.getApiRequest().getBody().asJSONObject() // Get the API request body to be enriched
  body.put("date", datetime) // Add the datetime value to the Service Request Payload
  context.getServiceRequest().setBody(new StringBodyImpl(body.toString(), null)) // Reset the Service Request body with the enriched JSON
  return true
  }
  boolean onThrowable(Throwable throwable) throws PolicyProcessingException {
    return false
  }
}
callout.setMethod("GET")
callout.setRequestURL("http://worldclockapi.com/api/json/utc/now")
callout.sendAsync(callback)

Assuming that the worldclockapi is accessible and that it returns a JSON payload containing a currentDateTime key, this will work. However, things can go wrong and how we handle that will be dealt with in a forthcoming blog.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha