Custom JavaScript: Examples

Flow Control and Request Blocking

Abort requests to deprecated resources

This script can be used to block access to an old URL, but with a friendly message to developers still using that URL. You should follow the scripts below, adding the URL to the URL Pattern field.

This snippet indicates that the Gateway will not accept the request. It must be added to the request flow.

$call.decision.setAccept(false);

Add the following snippet, modifying the response to set a 404 status code and fixed body. It should be executed in the after-response step.

$call.response.setStatus(404);
$call.response.getBody().setString(
  '{"message": "This resource has been removed. Use /orders/status instead."}', "utf-8"
);

Interrupt the request execution flow and return the response to the client

In this example, a new attribute was included in the object $call, enabling the interruption of the request flow at each point, thus returning the response to the client.

To stop the request and return the response without going to the backend and executing the response flow, add this script:

$call.stopFlow = true;
$call.decision.setAccept( false );
$call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse();
$call.response.setStatus( 400 );

To interrupt the request or response, making the request to the backend:

$call.stopFlow = true;

Header Manipulation

Add a fixed header

This script can be used to add a header to the request. In the example below, we are inserting a Content-Type: application/json only if the request does not contain a Content-Type

if ( $call.request.getHeader( "Content-Type" ) == null )
    $call.request.setHeader( "Content-Type", "application/json" );

Add a header from a token’s extraInfo

This script can be used to add a header to a request. The content of this header will be obtained from the extraInfo of one of the tokens used in the request.

In the example below, a header X-Customer-Id will be added, containing the client ID represented by the existing access token.

var access_token = $call.accessToken;
if (access_token) {
    $request.setHeader("X-Customer-Id", access_token.extraInfo.get('customerId'));
}

To test the script above, make the following request. This test assumes that there is an access token with the code mycustomer containing an extraInfo called customerId and value 123.

curl -H "access_token: mycustomer" http://10.0.0.5:8080/example

Among the headers received by the backend, there must be:

...
"headers": {
    ...
    "X-Customer-Id": "123"
    ...
}

See that in the script above we are assuming that there will always be an access token. If the script is executed in the request flow and the access token is required (as it usually is), then this is a sound assumption, since the gateway would block the access if the token was not informed.

If the token is optional, however, we must deal with the case of it not being informed. The example below adds a header indicating that this app is being tested in a sandbox environment, but the app might not be informed.

var app_token = $call.app;
if ( app_token && app_token.extraInfo.get('isSandbox') == "true" ) {
    $request.setHeader( "X-Is-Sandbox", "true" );
}

To test the script above, make the request below. This test assumes that there is an app with the code myapp, containing an extraInfo called isSandbox and value true.

curl -H "client_id: myapp" http://10.0.0.5:8080/example

Among the headers received by the backend, there must be:

...
"headers": {
    ...
    "X-Is-Sandbox": "true"
    ...
}

Modify the "Authorization" header

This is a script to modify an Authorization header.

In the example below, the client is sending an Authorization header with this content: Basic YWJjZGVmCg==. Decoding the Base64 value, we obtain abcdef, but with a newline (\n) at the end. We will remove the Basic part, decode the rest of the value in Base64, remove the newline, and encode again:

var auth = $call.request.getHeader( "Authorization" )
if ( auth != null ) {

    // Removes the prefix "Basic: "
    if ( auth.toLowerCase().indexOf( "basic" ) == 0 )
        auth = auth.substring("Basic".length).trim();

    // Decodes, removes the newline e encodes again.
    // Note the use of the utility $base64.
    // See other utilities in the hooks developer reference.
    var plain = $base64.atob(auth);
    var withoutWhitespace = plain.trim();
    var base64 = $base64.btoa(withoutWhitespace);

    // Overwrites the original header value
    $call.request.setHeader( "Authorization", base64 );
}

Remove a request header attribute before forwarding the call to the backend

try{
   $call.request.getAllHeaders().remove("seu-header");
} catch(e){
   $call.tracer.trace(e);
}

Query Parameter Manipulation

Add pagination query parameters when absent

In this example, we have a backend that always expects to receive pagination parameters in query-strings (e.g., /products?page=5&page_size=10). But we want requests without these parameters to be executed with sensible default values (page=0 and page_size=10, for instance).

Note that we do not want to add parameters if they already exist.

if ( ! $request.getQueryParam("page") )
    $request.setQueryParam("page", "0");

if ( ! $request.getQueryParam("page_size") )
    $request.setQueryParam("page_size", "20");

We also want to protect ourselves against calls that specify pagination parameters beyond the expected, so we must continue the script above with:

var page = parseInt($request.getQueryParam("page"));
if (page > 100 || page < 0)
    $request.setQueryParam("page", 0);

var pageSize = parseInt($request.getQueryParam("page_size"));
if (pageSize > 100 || pageSize < 1)
    $request.setQueryParam("page_size", 20);

Request Body Manipulation

Obtain a parameter from the URI and add it to the request body

if ( $call.request.getQueryParam("userId") != null )
{
    var json = JSON.parse($call.request.getBody().getString("utf-8"));
    json["userId"] = String($call.request.getQueryParam("userId"));

    $call.request.getBody().setString(JSON.stringify(json), "utf-8");
    $call.request.setHeader("Content-Type", "application/json");
}

Capture a PATH parameter

This example demonstrates how to capture a parameter defined in the request PATH.

var pathParam = $call.getPathParam();
pathParam = pathParam.toString().replace("}","").split("=");

$console.debug("pathParam", pathParam[1]);

Transformation with JSON sub-elements and JSON array

var json = JSON.parse( $call.response.getBody().getString("utf-8") );

var toXml = function(jsonNode, elementName) {
    var result = new Packages.org.jdom.Element(elementName);
    for ( var key in jsonNode ) {
        var value = jsonNode[key];
        if(value != null && value != 'null' && value != undefined){
            if(value.toString().indexOf('[object Object]') == -1 ){
                var element = new Packages.org.jdom.Element(key);
                element.setText(value.toString());
                result.addContent(element);
            }else{
                try{
                    var subElement = toXml(value, key);
                    result.addContent(subElement);
                }catch(err){
                    try{
                        for ( var keyArray in value ) {
                            var valueArray = value[keyArray];
                            var subElementArray = toXml(valueArray, key);
                            result.addContent(subElementArray);
                        }
                    }catch(err){
                            var elementError = new Packages.org.jdom.Element(key);
                            elementError.setText(err);
                            result.addContent(elementError);
                    }
                }
            }
        }
    };
    return result;
}

var root = toXml( json, "root" );

$call.response.getBody().setString( $jdom.stringify(root), "utf-8" );
$call.response.setHeader("Content-Type", "application/xml");

Convert the request body from text to JSON

This example receives a request in the properties format (a subset of YAML):

invoice : 34843
customer: Benjamin
date    : 2001-01-23
tax     : 251.42
total   : 4443.52

We want to convert it to a JSON, as the one below:

{
  "invoice": 34843,
  "customer": "Benjamin",
  "date": "2001-01-23",
  "tax": 251.42,
  "total": 4443.52
}

For such, let us execute the following script in the request flow:

// Always specify the encoding.
// In this case, we are forcing utf-8,
// but we could consult the request headers to find out
var text = $call.request.getBody().getString("utf-8");
var newBody = "{";
text.split("\n").forEach(function(line) {
    var pieces = line.split(":");
    if (pieces[0].trim() == 'invoice' || pieces[0].trim() == 'tax' || pieces[0].trim() == 'total')
        newBody += '"' + pieces[0].trim() + '"' + ':' + pieces[1].trim() + ',';
    else
        newBody += '"' + pieces[0].trim() + '"' + ':' + '"' + pieces[1].trim() + '",';
});
if ( newBody.length > 1 )
    newBody = newBody.substring(0,newBody.length -1);
newBody += '}';

// Sets the new request body
$call.request.getBody().setString(newBody, "utf-8");
$call.request.setHeader("Content-Type", "application/json");
The default interceptor TXT to JSON can be used to carry out this transformation.

Rename a field in the request body

In this example, the client sends a request in a format that is a bit old; in the new format, a field has changed names. We only want to translate the old name into the new one:

old format:                new format:

{                          {
  "id"      : 123,           "id"      : 123,
  "fatherId": 456            "parentId": 456
}                          }

The script must be executed in the request flow, so that it runs before the call is forwarded to the backend.

var json = JSON.parse( $call.request.getBody().getString( "utf-8" ) );
if ( json['fatherId'] != null ) {
    json['parentId'] = json['fatherId'];
    delete json['fatherId'];
}

$call.request.getBody().setString( JSON.stringify(json), "utf-8" );

The script can be tested pointing to an endpoint that echoes what was received:

curl -H 'Content-Type: application/json' -d '{"fatherId": 123}' http://10.0.0.5:8080/example

The result must include the body received:

...
"body" : "{\"parentId\":123}",
...

Unpack the request body with gzip

The following code unpacks the request body compressed using gzip.

This transformation must be applied to the request flow.

var decompressed = $gzip.decompress( $call.request.getBody().getBytes() );
var json = JSON.parse(decompressed);
json.test = 2;
$call.request.getBody().setString( JSON.stringify(json) , "utf-8" );

Response Body Manipulation

Stop the execution flow in the request and return the response to the client

$call.stopFlow = true;
$call.decision.setAccept( false );
$call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse();
$call.response.setStatus( 400 );

Treat back-end exceptions

var respBody = $call.response.getBody().getString("utf-8") || "";
respBody = respBody.trim();
if ($call.response.getStatus() > 400 &&
    respBody.length > 0 &&
    respBody[0] != '[' &&
    respBody[0] != '{') {

    respBody = JSON.stringify({message: "A server error occurred."});
}

Convert the response body from JSON to XML

In this example, we have an API at the backend which returns a JSON response, but we want to set up an equivalent XML to return to the client.

The default interceptor JSON to XML can be used to carry out this transformation.

This script must be executed in the response flow.

// Parses the JSON received
var json = JSON.parse( $call.response.getBody().getString("utf-8") );

// This function converts an object into an XML representation.
// Note that it does not deal with sub-objects or collections
var toXml = function(jsonNode, rootName) {

    // We are using JDOM to set up the XML
    var result = new Packages.org.jdom.Element(rootName);

    // Each jsonNode property becomes a sub-element of the element above
    for ( var key in jsonNode ) {
        var value = jsonNode[key];

        var element = new Packages.org.jdom.Element(key);
        element.setText(value.toString());
        result.addContent(element);
    };
    return result;
};

// Triggers the filter at the root node
// Note that we are assuming that the result is always an object, not a list
var root = toXml( json, "root" );

// Serializes and sets the response
$call.response.getBody().setString( $jdom.stringify(root), "utf-8" );

With a bit more code, we can make the function toXml() deal with object lists, complex objects, etc.

Create an XML and add it to the response

This script creates a XML using JDOM and adds it to the response.

var result = new org.jdom.Element("sensedia");
result.setText("xml");
$call.response.getBody().setString( $jdom.stringify(result), "utf-8" );

Environment Variable Manipulation

This section describes how to access environment variables in a Custom JavaScript in the API Gateway and presents the main objects and commands available.

When to use

Use environment variables when the same interceptor needs to change behavior across environments without requiring changes to the JavaScript code. Common examples include:

  • destination URLs for HTTP calls

  • integration tokens and keys

  • header names

  • behavior flags

How to access environment variables

Environment variables are available through the $call.environmentVariables object, which is a Map<String, String>.

The most common access pattern is:

var value = $call.environmentVariables.get("VARIABLE_NAME");

Simple example

var clientId = $call.environmentVariables.get("client_id");

if (clientId != null) {
    $request.setHeader("X-Client-Id", clientId);
}

Example with required validation

When the variable is required, validate it before using it:

var destination = $call.environmentVariables.get("destination");

if (destination == null || destination == "") {
    $call.decision.setAccept(false);
    $response.setStatus(500);
    $response.setHeader("Content-Type", "application/json");
    $response.getBody().setString(
        JSON.stringify({
            message: "Environment variable 'destination' not configured"
        }),
        "UTF-8"
    );
} else {
    var backendResponse = $http.get(destination);
    $request.setHeader("response-status", String(backendResponse.getStatus()));
}

Example building a dynamic URL

var baseUrl = $call.environmentVariables.get("base_url");
var customerId = $request.getHeader("X-Customer-Id");

if (baseUrl != null && customerId != null) {
    var url = baseUrl + "/customers/" + customerId;
    var response = $http.get(url);
    $call.addContextVariables("customer-response", response.getResponseText());
}

Building a response body without using backticks (`)

The Custom JavaScript runtime uses the Rhino engine (org.mozilla.javascript) with Context.VERSION_1_8. In practice, this means that the supported syntax does not match modern JavaScript used in browsers or Node.js.

For this reason, avoid ES6+ features in the interceptor. In particular, do not use template literals with backticks:

// Avoid this format
var body = `{"message":"${msg}"}`;

Instead, prefer one of the following alternatives:

var body = '{"message":"' + msg + '"}';
$response.getBody().setString(body, "UTF-8");
var body = {
    message: msg
};

$response.getBody().setString(JSON.stringify(body), "UTF-8");

The second option is the safest way to generate JSON because it avoids escaping and string concatenation errors.

Global objects available in the script

The objects below are injected by the gateway into the Custom JavaScript scope.

Object Primary use Most common methods/operations

$call

Full call data

environmentVariables.get(…​), decision.setAccept(…​), contextVariables.put(…​), addContextVariables(…​)

$request

Current request

getHeader(…​), setHeader(…​), addHeader(…​), getQueryParam(…​), setQueryParam(…​), setMethod(…​), getBody().getString(…​)

$response

Current response

setStatus(…​), setHeader(…​), getBody().setString(…​), getBody().getString(…​)

$http

HTTP calls from within the script

get(…​), post(…​), put(…​), delete(…​), patch(…​)

$console

Interceptor debug log

debug("key", value)

$base64

Base64 encode/decode

btoa(…​), atob(…​)

$json

JSON Java/JS conversion

parse(…​), stringify(…​)

$cookies

Cookie reading

parseRequestCookieValues(…​), parseResponseCookieValues(…​)

$jdom

XML manipulation

parse(…​), findElements(…​), stringify(…​)

$gzip

Compression/decompression

compress(…​), decompress(…​)

$billing

Billing trigger

execute($call)

$jwt

JWT encode/decode

encode(payload, secret), decode(token, secret)

Config

Gateway configuration access

getString(…​), getBoolean(…​), getInteger(…​), getLong(…​), contains(…​)

Grid

Gateway grid/cache access

getGrid(…​)

  • $compositionResponse only exists in composition flows.

  • environmentVariables stores values as String. If you need a number, explicitly convert it using parseInt(…​), parseFloat(…​), or Number(…​).

  • contextVariables can be used to share data with other interceptors or flow stages.

Most common $http signatures

Gateway tests validate the following formats:

var response = $http.get(url);
var response = $http.get(url, headers);
var response = $http.get(url, headers, timeout);

var response = $http.post(url, body);
var response = $http.post(url, headers, body);
var response = $http.post(url, headers, body, timeout);

var response = $http.put(url, body);
var response = $http.delete(url);
var response = $http.patch(url, body);
  • headers can be a simple JavaScript object, for example { "content-type": "application/json" }.

  • timeout is handled in seconds in the context of the custom interceptor.

  • The return type is a RESTResponse, with methods such as getStatus(), getHeaders(), and getResponseText().

Complete example

The example below shows a common scenario: reading an environment variable, calling a backend, and returning a controlled response in case of error.

var destination = $call.environmentVariables.get("destination");

if (destination == null || destination == "") {
    $call.decision.setAccept(false);
    $response.setStatus(500);
    $response.setHeader("Content-Type", "application/json");
    $response.getBody().setString(
        JSON.stringify({
            message: "Environment variable 'destination' not configured"
        }),
        "UTF-8"
    );
} else {
    var backendResponse = $http.get(destination);

    if (backendResponse.getStatus() >= 400) {
        $call.decision.setAccept(false);
        $response.setStatus(backendResponse.getStatus());
        $response.setHeader("Content-Type", "application/json");
        $response.getBody().setString(
            JSON.stringify({
                message: "Backend call failed",
                status: backendResponse.getStatus(),
                body: backendResponse.getResponseText()
            }),
            "UTF-8"
        );
    } else {
        $response.setStatus(200);
        $response.setHeader("Content-Type", "application/json");
        $response.getBody().setString(
            JSON.stringify({
                target: destination,
                backendStatus: backendResponse.getStatus()
            }),
            "UTF-8"
        );
    }
}

Best practices

  • Prefer var and classic JavaScript syntax to maintain compatibility with the current engine.

  • Always validate that the environment variable exists before using it in a URL, header, or credential.

  • For JSON, prefer JSON.stringify(…​) instead of manually concatenating strings.

  • Use $console.debug("key", value) when the interceptor has debug enabled.

  • When writing request/response bodies, explicitly specify the charset, for example "UTF-8".

Quick summary

If the only question is "how to read an env var in Custom JS?", the essential line is:

var value = $call.environmentVariables.get("my_variable");

If this variable is used to build a JSON response, avoid backticks. Use JSON.stringify(…​) or simple string concatenation.

Token and extraInfo Manipulation

Validate URL against extraInfo

In this example, we will create a script to be executed only for the URLs of the type /projects/*. This pattern must be inserted in the field URL Pattern.

This API returns a list of members of a given project. However, each access token can only see certain projects. The list of allowed projects is stored in the token’s extraInfo, at the moment of registration.

extra info

The extraInfo can be read in interceptors but is not persisted when modified at runtime. Values must be defined at the time of token creation/registration.

If the token above is used to call /projects/marketing-site or /projects/product-support, it must be allowed access; but if it tried to call /projects/ui-redesign, we want it to be barred with an "HTTP 403 Forbidden" error and a message providing explanation.

As we need the token extracted and validated, we will insert the script in the request flow:

var accessToken = $call.accessToken;
var extraInfo = accessToken.extraInfo;

// The value is a list separated by commas
var allowedProjects = ( extraInfo.get('allowedProjects') || "*" ).split(",");
var allowedCall = false;

// Verifies if a called URL matches an allowed project
var url = $call.request.getRequestedUrl().getPath();
for ( var i = 0; i < allowedProjects.length; i++ ) {

    // An asterisk means that this token can be used for any project
    if ( allowedProjects[i] == "*" ){
        allowedCall = true;
        break;
    }

    // Verifies if the URL is allowed.
    // If so, leave the script without any alteration
    if ( url.startsWith("/projects/" + allowedProjects[i]) ){
        allowedCall = true;
        break;
    }
}

if ( !allowedCall ){
    // Arrives here if the URL called is from no allowed project
    $call.decision.setAccept( false );
    $call.response.setStatus( 403 );
    var body = {
        message: "This token can only be used for the following projects: " + allowedProjects.join(",")
    };

    // Note the use of the utility JSON.stringfy(),
    // which returns a string with the JSON representation of an object.
    $call.response.getBody().setString( JSON.stringify(body), "utf-8" );
}

With this script and token, these are the expected results:

GET /projects/marketing-site               => HTTP 200
GET /projects/product-support              => HTTP 200
GET /projects/ui-redesign                  => HTTP 403

Obtain token from extraInfo

In the example below, we have some legacy clients that cannot modify their systems to send oAuth 2.0 access tokens. Instead, they need to keep on sending a username/password pair in a proprietary header X-User-Password. We want to recover this header, extract the data, and set up a new Authorization header with codes that we already know.

For the Gateway, everything happens as if the legacy system had sent an Authorization header, as the other systems. Therefore, this script needs to be executed in the request flow. As we know that only three legacy systems are working this way, we will perform a hard-coded validation of the credentials.

var oldHeader = $call.request.getHeader("X-User-Password");
oldHeader = $base64.atob(oldHeader);

var legacySystems = {
    "system1:password1": "abc123",
    "system2:password2": "def456",
    "system3:password3": "ghi789"
}

var correctToken = legacySystems[oldHeader];
if ( correctToken == null ) {
    $call.decision.setAccept( false );
    $call.response.setStatus( 403 );
    var body = { message: "Bad username/password" };
    $call.response.getBody().setString( JSON.stringify(body), "utf-8" );
} else {
    $call.request.setHeader( "Authorization", "Basic " + correctToken );
}

Compare the extraInfo value with the API resource value

try {
 var pass = false;
 var url = $call.request.getRequestedUrl().getPath().split("/v1/");
 var resource = url[1];

 //Gets extraInfos and runs through all of them, comparing their value
 //with the value of the API resources that are being consumed
 var extraInfos = $call.app.extraInfo;
 if(extraInfos){
  var extraInfosArray = extraInfos.values().iterator();
  while(extraInfosArray.hasNext()){
   if (resource == extraInfosArray.next()){
    pass = true;
   }
  }

 }
 if(!pass){
  $call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse();
  $call.decision.setAccept(false);
  $call.response.setStatus(403);
  $call.response.getBody().setString('{"message": "Forbidden"}','utf-8');
 }
} catch (e) {
 $call.tracer.trace(e);
}

Change the route according to a token’s extraInfo

In this example, the route to be followed differs depending on a flag in the extraInfo of an access token indicating whether it is a legacy or not. If so, it should be routed to http://legacy.mycompany.com); if not, it should be forwarded to http://api.mycompany.com).

Note that at least one route must be defined, otherwise the Gateway will consider the request as irregular and will return an HTTP 404 status code before the script is executed.

var access_token = $call.accessToken;
if (access_token && access_token.extraInfo.get('isLegacy') == "true") {
    var newDest = 'http://legacy.backend.com';

    $call.setDestinationUri(new Packages.java.net.URI(newDest));
    $call.request.setHeader('Host', $call.destinationUri.getHost());
}

As a test, you can create an app with the extraInfo isLegacy = true and make a request. After, modify the flag (or create another app with isLegacy = false) and make another request.

curl -H 'client_id: mynewapp' http://10.0.0.5:8080/example/api

curl -H 'client_id: mylegacyapp' http://10.0.0.5:8080/example/api

The first request, by a modern app, should hit http://example.demo.sensedia.com/example/api. The second request, by a legacy app, should be routed to http://legacy.backend.com.

Integration with External Services

Execute other services during transformation

The best way to execute other services is running the Service Mashup interceptor. However, it is also possible to execute them with custom interceptors.

This script orchestrates services, which can be called in any flow (request or response).

    if($http.get("http://sensedia.com").status == 200)
    {
        $http.get("http://sensedia.com/blog")
    }



    if($http.get("http://sensedia.com").status == 200)
    {
        var header = { 'Content-Type' : 'application/json' }
        var body = { "hello": "world" }
        $http.post("http://www.mocky.io/v2/551018c499386d1a0b53b04b", header, body)
    }

Execute requests in parallel with threads

In some scenarios, it may be necessary to execute multiple operations in parallel within an interceptor, for example when querying different external services during the transformation of a request.

Since the Custom JavaScript runtime uses the Rhino engine, it is possible to directly access Java classes available in the environment, such as java.lang.Thread.

The example below demonstrates how to execute two tasks in parallel and wait for their completion before continuing the flow.

// Example of using threads for parallel requests
function main() {
    try {
        var Thread = java.lang.Thread;

        var t1 = new Thread(function () {
            // Logic/Request 1 here
            $call.tracer.trace("Thread 1 executed!");
        });

        var t2 = new Thread(function () {
            // Logic/Request 2 here
            $call.tracer.trace("Thread 2 executed!");
        });

        var threadTimeout = 2000; // timeout in ms

        t1.start();
        t2.start();

        // Wait for the threads to finish
        t1.join();
        t2.join(threadTimeout);

    } catch (exception) {
        $console.debug("Exception message", exception);
    }
}

main();

Follow redirect (302) and combine headers from multiple responses

This example demonstrates an advanced integration scenario in which the interceptor performs chained calls to external services and manually handles HTTP (302) redirects.

var destination = $call.destinationUri.toString();

var requestHeaders = {};

var requestHeaderskeyArray = $call.request.getAllHeaderNames().toArray();
for( var i = 0; i < requestHeaderskeyArray.length; i++ ) {

    var key = requestHeaderskeyArray[i];

    if ( key != 'host' && key != 'content-length' ) {
        requestHeaders[key] = $call.request.getHeader(key);
    }

}

$call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse();
$call.decision.setAccept( false );

var firstResponse = $http.post( destination, requestHeaders, $call.request.getBody().getString( 'UTF-8' ) );

var firstResponseHeaderskeyArray = firstResponse.getHeaders().keySet().toArray();
for( var i = 0; i < firstResponseHeaderskeyArray.length; i++ ) {

    var key = firstResponseHeaderskeyArray[i];
    $call.response.setHeader( key, firstResponse.getHeaders().get( key ) );

}

if ( firstResponse.getStatus() == 302 ){

    destination = firstResponse.getHeaders().get( 'Location' );
    var secondResponse = $http.post( destination, requestHeaders, $call.request.getBody().getString( 'UTF-8' ) );

    var secondResponseHeaderskeyArray = secondResponse.getHeaders().keySet().toArray();
    for( var i = 0; i < secondResponseHeaderskeyArray.length; i++ ) {

        var key = secondResponseHeaderskeyArray[i];
        $call.response.setHeader( key, secondResponse.getHeaders().get( key ) );

    }

    $call.response.setStatus( secondResponse.getStatus() );

    $call.response.getBody().setString( secondResponse.responseText, 'UTF-8' );

} else {

    $call.response.setStatus( firstResponse.getStatus() );

    $call.response.getBody().setString( firstResponse.responseText, 'UTF-8' );

}

Error Handling

Handle backend exceptions

In this example, there is a backend which, in case of errors, returns a stacktrace in HTML or simple text formats. Instead, we want to set an appropriate response to the client. We will execute the script at the after-response stage.

var respBody = $call.response.getBody().getString("utf-8") || "";
respBody = respBody.trim();
if ($call.response.getStatus() > 400 &&
    respBody.length > 0 &&
    respBody[0] != '[' &&
    respBody[0] != '{') {

    respBody = JSON.stringify({message: "A server error occurred."});
}

Another option would be returning a standard error message inside a JSON. To do that, execute the code below at the after-response stage:

if($call.response.getStatus() == 401){
    var errorBody = $call.response.getBody().getString("utf-8");
    var jsonError = {};
    jsonError.status = String($call.response.getStatus());
    jsonError.message = String(errorBody);
    $call.response.getBody().setString(JSON.stringify(jsonError), "utf-8");
    $call.response.setHeader("Content-Type", "application/json");
}

Data Security and Masking

Suppress sensitive information before sending metrics

In some cases, we have APIs that transfer confidential data between the backend and the client (such as credit card numbers) and we don’t want to expose these data, not even to system administrators that might use the API Manager to look into problems. In this example, we will use a script that runs at the before-metric point to mask these fields.

The request and the response are not modified by the script below; only the copies of these objects, sent to the Manager for storage and analysis, are affected. The backend keeps on receiving the credit card number that was send by the client, and the client keeps on receiving the number in the response.
var maskField = function(s) {
    if ( s == null ) return null;
    return "****-****-****-" + s.substring( 15 );
}

var maskFieldsInBody = function(bodyString) {
    var json = JSON.parse( bodyString );
    json["creditCard"] = maskField( json["creditCard"]  );
    return JSON.stringify( json );
}

// We modified only the data in $call.metric.
// $call.request and $call.response are not altered.
$call.metric.requestBody = maskFieldsInBody( $call.metric.requestBody );
$call.metric.responseBody = maskFieldsInBody( $call.metric.responseBody );

Cookie Manipulation

This is a script to obtain a token from a cookie. In the example below, we have a request that sends the access token as a cookie — that is, among the other cookies sent to the server. The HTTP request is as follows:

GET http://api.company.com/resources
Cookie: cookie1=value1; cookie2=value2; access_token=abcdef

Note that there are three token in the Cookie header, and we want the API Gateway’s processing to treat the request as if the abcdef AccessToken had been sent in an access_token header. We will execute the script in the request flow, since it is during validation that the tokens will be extracted and validated.

// parseRequestCookieValues only works in the request.
// To interpret response cookies,
// see the other methods of the $cookies utility class.
// The result is a case-insensitive Map of cookie names for your values.
var allCookies = $cookies.parseRequestCookieValues($call.request.getHeader("Cookie"));
$call.request.setHeader( "access_token", allCookies.get("access_token") );

Billing Manipulation

This is a script to handle billing.

$call.billing = true;
$call.billingValue = 5.85;
$billing.execute($call);

Observations:

  • The attribute billingValue works with Double precision, so it might be rounded or lose accuracy in decimal places.

  • For consumption control and blocking, select the Abort request if fail option on the editor, besides including exception handling in the custom interceptor itself or in the Raise Exception in the flow.

HTTP Method Manipulation

Transform the PUT method into GET

if ( $call.request.getMethod() == "PUT" )
{
    $call.request.setMethod( "GET" );
}
Thanks for your feedback!
EDIT

Share your suggestions with us!
Click here and then [+ Submit idea]