Custom JavaScript: Ejemplos

Control de flujo y bloqueo de solicitudes

Abortar solicitudes para recursos antiguos

Este script puede ser usado para bloquear el acceso a una URL obsoleta, pero con un mensaje más agradable para los desarrolladores que todavía usan esta URL. Introducir la URL deseada en el campo URL Pattern y usar los siguientes scripts:

Este extracto indicará que el gateway no aceptará la petición. Debe ser ejecutado en el flujo de petición (request).

$call.decision.setAccept(false);

Añadir el siguiente fragmento, modificando la respuesta para establecer un código de estado 404 y un cuerpo fijo. Debe ser ejecutado en el paso after-response.

$call.response.setStatus(404);
$call.response.getBody().setString(
  '{"message": "Este recurso ha sido eliminado. Usar /orders/status en su lugar."}', "utf-8"
);

Interrumpir el flujo de ejecución en la solicitud y devolver la respuesta al cliente

En este ejemplo, añadimos un nuevo atributo al objeto $call, haciendo posible interrumpir el flujo de peticiones (request) en cualquier punto y devolviendo la respuesta al cliente.

Para interrumpir la ejecución de la petición y devolver la respuesta sin acceder al servidor y ejecutar el flujo de respuesta (response), ejecutar:

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

Para interrumpir la ejecución de la petición o respuesta, haciendo la petición al servidor, ejecutar:

$call.stopFlow = true;

Manipulación de encabezados

Agregar encabezado fijo

Este script puede ser usado para añadir un header a una petición. En el ejemplo siguiente, añadimos un header Content-Type: application/json sólo si la petición no contiene un header Content-Type.

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

Agregar encabezado a partir del extraInfo de un token

Este script puede ser usado para añadir un header a una petición. El contenido de este header será tomado del extraInfo de uno de los tokens de acceso utilizados en la petición.

En el ejemplo siguiente, un header X-Customer-Id será añadido, y contiene la identificación del cliente representado por el access token actual.

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

Para probar el guión anterior, hacer la siguiente petición. Esta prueba supone que hay un token de acceso con un código mycustomer, que contiene un campo extraInfo llamado customerId y con un valor de 123.

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

Entre los headers recibidos por el servidor, debe existir:

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

Tenga en cuenta que en el script anterior asumimos que siempre habrá un token de acceso. Se el script se ejecuta en el flujo de petición (request) y se requiere un token de acceso (como suele ser el caso), entonces esta suposición es sensata, ya que el gateway impediría el acceso si el token no fuera informado.

Sin embargo, si el token es opcional, debemos ocuparnos del caso de que no sea informado. En el ejemplo siguiente, se añade un header que indica que esta aplicación se está probando en un entorno de pruebas, pero puede ser que la aplicación no esté informada.

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

Para probar el guión anterior, hacer la siguiente petición. Esta prueba supone que hay una aplicación con un código myapp, que contiene un campo extraInfo llamado isSandbox y con un valor de true.

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

Entre los headers recibidos por el servidor, debe existir:

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

Modificar el encabezado "Authorization"

Este es un ejemplo de cambio de un header Authorization.

En este ejemplo, el cliente envia un header Authorization con contenido Basic YWJjZGVmCg==. Decodificando el valor en Base64, tenemos abcdef, pero con una nueva línea (\n) al final. Vamos a eliminar el fragmento Basic, sacar la nueva línea y re-codificar:

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

    // Eliminar el prefijo "Basic: "
    if ( auth.toLowerCase().indexOf( "basic" ) == 0 )
        auth = auth.substring("Basic".length).trim();

    // Decodificar, sacar nueva línea y re-codificar.
    // Observe el uso de la utilidad $base64.
    // Ver otras utilidades en la referencia del desarrollador de hook.
    var plain = $base64.atob(auth);
    var withoutWhitespace = plain.trim();
    var base64 = $base64.btoa(withoutWhitespace);

    // sobrescribir el valor original del header
    $call.request.setHeader( "Authorization", base64 );
}

Remover un atributo de header de la solicitud antes de enviar la llamada al backend

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

Manipulación de parámetros de consulta

Agregar parámetros de consulta de paginación cuando estén ausentes

En este ejemplo, hay un servidor que siempre que siempre espera recibir parámetros de paginación en la query-string de las llamadas (ejemplo: /products?page=5&page_size=10). Pero queremos que las peticiones sin estos parámetros se ejecuten con valores estándar sensatos (page=0 y page_size=10, por ejemplo).

También tenga en cuenta que no añadiremos parámetros si ya existen.

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

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

También queremos protegernos contra llamadas que especifican parámetros de paginación inaceptables, así que debemos continuar el script anterior con:

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);

Manipulación del cuerpo de la solicitud

Obtener un parámetro de la URI y poner en el cuerpo de la petición

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");
}

Capturar un parámetro de PATH

Este ejemplo demuestra cómo capturar un parámetro definido en el PATH de la solicitud.

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

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

Transformación con subelementos JSON y con 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");

Convertir el cuerpo de la solicitud de texto a JSON

Este ejemplo recibe una petición en el formato properties (un subconjunto de YAML):

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

Queremos convertirla para JSON, como a continuación:

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

Para esto, vamos a ejecutar el siguiente script en el flujo de petición (request):

// Siempre especificar la codificación.
// En este caso, estamos forzando utf-8,
// pero podríamos consultar los headers de la petición para averiguar
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 += '}';

// Establece el nuevo cuerpo de la petición
$call.request.getBody().setString(newBody, "utf-8");
$call.request.setHeader("Content-Type", "application/json");
El interceptor default TXT to JSON puede ser utilizado para esta transformación.

Renombrar un campo en el cuerpo de la solicitud

En este ejemplo, el cliente envía una petición en un formato un anticuado; en el nuevo formato, un campo ha cambiado de nombre. Solo queremos traducir el nombre antiguo al nuevo:

old format:                new format:

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

El script debe ser aplicado en el flujo de petición (request), de modo que se ejecute antes de que la llamada se transmita al servidor.

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" );

El script se puede probar apuntando a un endpoint que haga eco de lo recibido.

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

El resultado debe incluir el cuerpo recibido.

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

Descomprimir el cuerpo de la request con gzip

El código siguiente descomprime el cuerpo de la petición empaquetada con gzip.

Esta transformación debe ser ejecutada en el flujo de petición (request).

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" );

Manipulación del cuerpo de la respuesta

Parar el flujo de ejecución de la petición y devolver la respuesta al cliente

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

Tratar excepciones de servidor

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."});
}

Convertir el cuerpo de la respuesta de JSON a XML

En este ejemplo, tenemos una API en el servidor que devuelve una respuesta JSON, pero queremos ensamblar un XML equivalente para retornar al cliente.

El interceptor default JSON to XML puede ser utilizado para esta transformación.

Ejecutaremos el siguiente script en el flujo de respuesta (response):

// Hace el análisis del JSON recibido
var json = JSON.parse( $call.response.getBody().getString("utf-8") );

// Esta función convierte un objeto en una representación XML
// Observe que no se ocupa de subobjetos ni de colecciones
var toXml = function(jsonNode, rootName) {

    // Estamos utilizando JDOM para ensamblar el XML
    var result = new Packages.org.jdom.Element(rootName);

    // Cada propiedad de jsonNode se convierte en un subelemento del elemento anterior
    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;
};

// Dispara el filtro en el nodo de la raíz
// Note que estamos asumiendo que el resultado es siempre un objeto, no una lista
var root = toXml( json, "root" );

// Serializa y establece la respuesta
$call.response.getBody().setString( $jdom.stringify(root), "utf-8" );

Con un poco más de código, podemos hacer que la función toXml() se ocupe de listas de objetos, objetos complejos, etc.

Crear un XML y agregarlo a la respuesta

Este script crea un XML usando JDOM y lo agrega a la respuesta.

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

Manipulación de variables de entorno

Esta sección describe cómo acceder a variables de entorno en un Custom JavaScript del API Gateway y presenta los principales objetos y comandos disponibles.

Cuándo usar

Use variables de entorno cuando el mismo interceptor necesite cambiar su comportamiento entre entornos, sin requerir cambios en el código JavaScript. Ejemplos comunes:

  • URLs de destino para llamadas HTTP

  • tokens y claves de integración

  • nombres de header

  • flags de comportamiento

Cómo acceder a variables de entorno

Las variables de entorno están disponibles en el objeto $call.environmentVariables, que es un Map<String, String>.

El acceso más común es:

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

Ejemplo simple

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

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

Ejemplo con validación obligatoria

Cuando la variable sea obligatoria, valídela antes de usarla:

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: "Variable de entorno 'destination' no configurada"
        }),
        "UTF-8"
    );
} else {
    var backendResponse = $http.get(destination);
    $request.setHeader("response-status", String(backendResponse.getStatus()));
}

Ejemplo construyendo una URL dinámica

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());
}

Formando el response body sin usar acento grave (`)

El runtime de Custom JavaScript utiliza el engine Rhino (org.mozilla.javascript) con Context.VERSION_1_8. En la práctica, esto significa que la sintaxis soportada no corresponde al JavaScript moderno utilizado en browsers o Node.js.

Por esta razón, evite usar recursos de ES6+ en el interceptor. En particular, no utilice template literals con acento grave:

// Evite este formato
var body = `{"message":"${msg}"}`;

Prefiera una de estas alternativas:

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

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

La segunda opción es la más segura para generar JSON, ya que evita errores de escape y concatenación.

Objetos globales disponibles en el script

Los siguientes objetos son inyectados por el gateway en el alcance de Custom JavaScript.

Objeto Uso principal Métodos/operaciones más comunes

$call

Datos completos de la llamada

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

$request

Request actual

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

$response

Response actual

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

$http

Llamada HTTP desde el script

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

$console

Log de depuración del interceptor

debug("clave", valor)

$base64

Encode/decode Base64

btoa(…​), atob(…​)

$json

Conversión JSON Java/JS

parse(…​), stringify(…​)

$cookies

Lectura de cookies

parseRequestCookieValues(…​), parseResponseCookieValues(…​)

$jdom

Manipulación de XML

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

$gzip

Compresión/descompresión

compress(…​), decompress(…​)

$billing

Activación de billing

execute($call)

$jwt

Encode/decode JWT

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

Config

Lectura de configuraciones del gateway

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

Grid

Acceso al grid/cache del gateway

getGrid(…​)

  • $compositionResponse solo existe en flujos de composición.

  • environmentVariables almacena valores como String. Si necesita un número, conviértalo explícitamente con parseInt(…​), parseFloat(…​) o Number(…​).

  • contextVariables puede utilizarse para compartir datos con otros interceptors o etapas del flujo.

Firmas más utilizadas de $http

Las pruebas del gateway validan los siguientes formatos:

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 puede ser un objeto JavaScript simple, por ejemplo { "content-type": "application/json" }.

  • timeout se maneja en segundos en el contexto del interceptor personalizado.

  • El retorno es un RESTResponse, con métodos como getStatus(), getHeaders() y getResponseText().

Ejemplo completo

El siguiente ejemplo muestra un uso común: leer una variable de entorno, llamar a un backend y devolver una respuesta controlada en caso de 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: "Variable de entorno 'destination' no configurada"
        }),
        "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: "Falla al consumir el backend",
                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"
        );
    }
}

Buenas prácticas

  • Prefiera var y sintaxis JavaScript clásica para mantener compatibilidad con el engine actual.

  • Siempre valide si la variable de entorno existe antes de usarla en una URL, header o credencial.

  • Para JSON, prefiera JSON.stringify(…​) en lugar de concatenar strings manualmente.

  • Use $console.debug("clave", valor) cuando el interceptor tenga debug habilitado.

  • Al escribir el cuerpo de request/response, indique explícitamente el charset, por ejemplo "UTF-8".

Resumen rápido

Si la duda es solo "cómo leer una env var en Custom JS?", lo esencial es esto:

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

Si esa variable se utiliza para generar JSON de respuesta, no use acento grave. Utilice JSON.stringify(…​) o concatenación simple.

Manipulación de tokens y extraInfo

Validar URL contra extraInfo

En el ejemplo siguiente, vamos a crear un script que será ejecutado solo en URLs del tipo /projects/*. Este patrón debe insertarse en el campo URL Pattern.

Esta API devuelve la lista de miembros de un proyecto determinado. Sin embargo, cada token de acceso solo puede ver unos pocos proyectos. La lista de proyectos permitidos se almacena en el extraInfo del token en el momento de registro:

extra info

El extraInfo puede leerse en los interceptors, pero no se persiste cuando se modifica en runtime. Los valores deben definirse en el momento de la creación/registro del token.

Si el token anterior se utiliza para llamar /projects/marketing-site o /projects/product-support, se debe permitir el acceso, pero si se utiliza para llamar /projects/ui-redesign, queremos que se bloquee con un error «HTTP 403 Forbidden» y un mensaje explicativo.

Como necesitamos el token ya extraído y validado, pondremos el script para ser ejecutado en el flujo de petición (request):

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

// El valor es una lista separada por comas.
var allowedProjects = ( extraInfo.get('allowedProjects') || "*" ).split(",");
var allowedCall = false;

// Comprueba si la URL llamada coincide con algún proyecto permitido.
var url = $call.request.getRequestedUrl().getPath();
for ( var i = 0; i < allowedProjects.length; i++ ) {

    // Un '*' significa que el token puede ser usado para cualquier proyecto.
    if ( allowedProjects[i] == "*" ){
        allowedCall = true;
        break;
    }

    // Comprueba si la URL es permitida.
    // Si es, dejar el script sin hacer ningún cambio.
    if ( url.startsWith("/projects/" + allowedProjects[i]) ){
        allowedCall = true;
        break;
    }
}

if ( !allowedCall ){
    // Viene aquí si la URL llamada no es de ningún proyecto permitido.
    $call.decision.setAccept( false );
    $call.response.setStatus( 403 );
    var body = {
        message: "Este token solo puede usarse para los siguientes proyectos: " + allowedProjects.join(",")
    };

    // Observe el uso de la utilidad JSON.stringify(),
    // que devuelve una string con la representación JSON de un objeto.
    $call.response.getBody().setString( JSON.stringify(body), "utf-8" );
}

Con este script y token, los resultados esperados son:

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

Obtener token de extraInfo

En el ejemplo siguiente, tenemos algunos clientes con legacy que no pueden modificar sus sistemas para enviar tokens de acceso OAuth 2.0. En vez de eso, necesitan seguir enviando un par de nombre-de-usuario/contraseña en un header propietario X-Usuario-Senha. Queremos recuperar este header, extraer los datos y ensamblar un nuevo header (Authorization) con códigos que ya conocemos.

Para el gateway, todo sucede como si el sistema heredado enviara un header Authorization como los otros sistemas. Por lo tanto, este script debe ser ejecutado en el flujo de petición. Como sabemos que solo tres sistemas heredados son así, haremos una validación hard-coded de las credenciales.

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 );
}

Comparar el valor de extraInfo con el valor del recurso de la API

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

 //Coge extrainfo y pasa por todos, comparando su valor con el valor
 //de los recursos de la API que están tratando de ser consumidos.
 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);
}

Cambiar la ruta de acuerdo con el extraInfo de un token

En este ejemplo, la ruta a seguir difiere dependiendo de una flag en el campo extraInfo de un token de acceso, indicando si se trata de una legacy (y entonces debe ser direcionado para http://legacy.mycompany.com) o no (en este caso, enviado para http://api.mycompany.com).

Tenga en cuenta que se debe establecer al menos una ruta. De lo contrario, el gateway considerará la petición como malformada y devolverá el código de estado HTTP 404 antes que tengamos la oportunidad de ejecutar este script.

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());
}

Para probar el script, crear una aplicación con extraInfo isLegacy = true y hacer una petición. Después, modificar la flag (o crear una otra aplicación con isLegacy = false) y hacer otra petición.

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

La primera petición, hecha por una aplicación actualizada, debe alcanzar http://example.demo.sensedia.com/example/api. La segunda llamada, hecha por una aplicación heredada, deve alcanzar http://legacy.backend.com.

Integración con servicios externos

Ejecutar otros servicios durante la transformación

La mejor forma de ejecutar otros servicios es utilizar el interceptor Service Mashup. Sim embargo, es possible hacerlo también con interceptores personalizados.

En este ejemplo, realizaremos una orquestación de servicios. La llamada de servicios puede ser utilizada en cualquier flujo (petición/respuesta).

Ejemplos:

    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)
    }

Ejecutar solicitudes en paralelo con threads

En algunos escenarios puede ser necesario ejecutar múltiples operaciones de forma paralela dentro de un interceptor, por ejemplo al consultar diferentes servicios externos durante la transformación de una solicitud.

Como el runtime de Custom JavaScript utiliza el motor Rhino, es posible acceder directamente a clases Java disponibles en el entorno, como java.lang.Thread.

El siguiente ejemplo demuestra cómo ejecutar dos tareas en paralelo y esperar a que finalicen antes de continuar con el flujo.

// Ejemplo de uso de threads para solicitudes paralelas
function main() {
    try {
        var Thread = java.lang.Thread;

        var t1 = new Thread(function () {
            // Lógica/Solicitud 1 aquí
            $call.tracer.trace("¡Thread 1 ejecutada!");
        });

        var t2 = new Thread(function () {
            // Lógica/Solicitud 2 aquí
            $call.tracer.trace("¡Thread 2 ejecutada!");
        });

        var threadTimeout = 2000; // timeout en ms

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

        // Espera a que las threads finalicen
        t1.join();
        t2.join(threadTimeout);

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

main();

Seguir redirección (302) y combinar headers de múltiples respuestas

Este ejemplo demuestra un escenario de integración avanzada, en el cual el interceptor realiza llamadas encadenadas a servicios externos y trata redirecciones HTTP (302) de forma manual.

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' );

}

Tratamiento de errores

Manejar excepciones del backend

En este ejemplo, hay un servidor que, en caso de error, devuelve un stacktrace en formato HTML o texto simple. En cambio, queremos elaborar un mensaje apropiado al cliente. Vamos a ejecutar el script a continuación en el punto after-response.

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: "Ha ocurrido un error de servidor."});
}

Otra opción sería devolver el mensaje de error por defecto dentro de un JSON. En este caso, ejecutar el código a continuación en el punto after-response:

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");
}

Seguridad y enmascaramiento de datos

Suprimir información sensible antes de enviar métricas

En algunos casos, hay APIs que transfieren datos sensibles entre el servidor y el cliente (como los números de las tarjetas de crédito), y no queremos exponer esos datos ni siquiera a los posibles administradores de sistemas que utilizan el API Manager para investigar posibles problemas. En este ejemplo, usaremos un script que se ejecuta en el punto before-metric para enmascarar esos campos.

Las peticiones y respuestas no son modificadas por el script; sólo las copias de estos objectos (que son enviadas para almacenamiento y análisis en el Manager) son afectadas. El servidor continúa recibiendo el número de tarjeta de rédito que envió el cliente y el cliente continúa recibiendo el número en la respuesta.
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 );
}

// Sólo modificamos los datos en $call.metric.
// $call.request y $call.response no se cambian.
$call.metric.requestBody = maskFieldsInBody( $call.metric.requestBody );
$call.metric.responseBody = maskFieldsInBody( $call.metric.responseBody );

Manipulación de cookies

Esto es un script para obtener un token de acceso de una cookie. En el ejemplo a continuación, tenemos una petición que envía un token de acceso como una cookie — es decir, entre las otras cookies enviadas al servidor. La petición HTTP es la siguiente:

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

Nótese que hay tres tokens en el header Cookie y queremos que el procesamiento del API Gateway trate la petición como si el token abcdef hubiera sido enviado en un header access_token. Ejecutaremos este script en el flujo de petición (request), ya que es durante la validación que los tokens serán extraídos y validados.

// parseRequestCookieValues sólo funciona en la petición.
// Para interpretar cookies en la respuesta,
// ver otros métodos de la clase de utilidad $cookies.
// El resultado es un mapa de nombres de cookies que no
// distingue entre mayúsculas y minúsculas para sus valores.
var allCookies = $cookies.parseRequestCookieValues($call.request.getHeader("Cookie"));
$call.request.setHeader( "access_token", allCookies.get("access_token") );

Manipulación de billing

Este es un script para manejar las opciones de facturación.

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

Observación:

  • El atributo billingValue funciona con precisión Doble, por lo que es posible que tenga un posible redondeo o pérdida de precisión hasta los decimales.

  • Para el control y bloqueo del consumo, la opción Abort request if fail debe ser seleccionada en el editor del interceptor, además de incluir el manejo de excepciones en el interceptor personalizado mismo o en el Raise Exception del flujo.

Manipulación del método HTTP

Transformar el método PUT en 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]