Custom JavaScript: Ejemplos
Esta página presenta ejemplos de scripts que pueden utilizarse en interceptores Custom JavaScript.
Los ejemplos muestran escenarios comunes de uso en el gateway, como:
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.
Introduzir la URL deseada en el campo URL Pattern y usar los siguientes scripts:
Esto 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 un de los tokens de acceso utilizados en la petición.
En el ejemplo siguiente, un header X-Customer-Id será añadida, 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 );
}
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 text 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
Para el flujo de ejecución en 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 assemblear 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 assemblear 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.
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 |
|---|---|---|
|
Datos completos de la llamada |
|
|
Request actual |
|
|
Response actual |
|
|
Llamada HTTP desde el script |
|
|
Log de depuración del interceptor |
|
|
Encode/decode Base64 |
|
|
Conversión JSON Java/JS |
|
|
Lectura de cookies |
|
|
Manipulación de XML |
|
|
Compresión/descompresión |
|
|
Activación de billing |
|
|
Encode/decode JWT |
|
|
Lectura de configuraciones del gateway |
|
|
Acceso al grid/cache del gateway |
|
|
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);
|
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
vary 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 tengadebughabilitado. -
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 crear un script que será ejecutado sólo en URL de tipo /projects/*.
Este patrón deve ser insertado en el campo URL Pattern.
Esta API devuelve la lista de miembros de un proyecto determinado.
Pero cada token de acceso sólo puede ver unos pocos proyectos.
La lista de proyectos permitidos se almacena en el extraInfo del token en el momento de registro:
|
El |
Se el token anterior se utiliza para llamar /projects/marketing-site o /projects/product-support, se deve 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 so pode ser usado para os seguintes projetos: " + 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 proprietario X-Usuario-Senha.
ueremos 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 sólo 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 maldormada y devolverá el código de estado HTTP 404 antes que tenhamos 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 em formato HTML o texto simple.
En cambio, queremos elaborar un mensaje apropriado 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
Obtener token a partir de una cookie
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,
// veer 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
billingValuefunciona 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.
Share your suggestions with us!
Click here and then [+ Submit idea]