Custom JavaScript: Exemplos
Esta página apresenta exemplos de scripts que podem ser utilizados em interceptores Custom JavaScript.
Os exemplos demonstram cenários comuns de uso no gateway, como:
Controle de fluxo e bloqueio de requisições
Abortar requisições para recursos antigos
Este script pode ser usado para barrar o acesso a uma URL antiga, mas com uma mensagem mais amigável para desenvolvedores que ainda estejam usando essa URL.
Coloque a URL desejada no campo URL Pattern e use os seguintes scripts:
Esse trecho indicará que o gateway não irá aceitar o request. Deve ser executado no fluxo de requisição.
$call.decision.setAccept(false);
Adicione em seguida o trecho que modificará a resposta, setando o status 404 e um corpo fixo.
Deve ser executado no ponto after-response.
$call.response.setStatus(404);
$call.response.getBody().setString(
'{"message": "Este resource foi removido. Use /orders/status no lugar."}', "utf-8"
);
Interromper o fluxo de execução na requisição e retornar a resposta para o cliente
Neste exemplo, um novo atributo no objeto $call foi incluído, possibilitando interromper o fluxo da requisição em qualquer ponto e retornando a resposta para o cliente.
Para interromper a execução da requisição e retornar a resposta sem ir ao backend e sem executar o fluxo de resposta, basta executar:
$call.stopFlow = true;
$call.decision.setAccept( false );
$call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse();
$call.response.setStatus( 400 );
Para interromper a execução da requisição ou resposta, fazendo a requisição para o backend, basta executar:
$call.stopFlow = true;
Manipulação de headers
Adicionar header fixo
Este script pode ser usado para adicionar um header a uma requisição.
No exemplo abaixo, estamos inserindo um header Content-Type: application/json apenas se a requisição não tiver um Content-Type.
if ( $call.request.getHeader( "Content-Type" ) == null )
$call.request.setHeader( "Content-Type", "application/json" );
Adicionar header a partir do extraInfo de um token
Este script pode ser usado para adicionar um header a uma requisição.
O conteúdo desse header será obtido do extraInfo de um dos tokens usados na requisição.
No exemplo abaixo, um header X-Customer-Id será adicionado, contendo o ID do cliente representado pelo access token atual.
var access_token = $call.accessToken;
if (access_token) {
$request.setHeader("X-Customer-Id", access_token.extraInfo.get('customerId'));
}
Para testar o script acima, faça a requisição abaixo.
Este teste assume que existe um access token com código mycustomer, contendo um extraInfo chamado customerId e com valor 123.
curl -H "access_token: mycustomer" http://10.0.0.5:8080/example
Entre os headers recebidos pelo backend, deve existir:
...
"headers": {
...
"X-Customer-Id": "123"
...
}
Observe que no script acima assumimos que sempre existirá um access token. Se o script for executado no fluxo de requisição e o access token for obrigatório (como tipicamente é), então essa premissa é sensata, pois o gateway barraria o acesso se o token não fosse informado.
Se o token for opcional, no entanto, devemos lidar com o caso de ele não ser informado. O exemplo abaixo insere um header indicando que a app está sendo testada em ambiente de sandbox, mas pode ser que a app não seja informada.
var app_token = $call.app;
if (app_token && app_token.extraInfo.get('isSandbox') == "true") {
$request.setHeader("X-Is-Sandbox", "true");
}
Para testar o script acima, faça o request abaixo.
Este teste assume que existe uma app com código myapp, contendo um extraInfo de nome isSandbox e valor true.
curl -H "client_id: myapp" http://10.0.0.5:8080/example
Entre os headers recebidos pelo backend, deve existir:
...
"headers": {
...
"X-Is-Sandbox": "true"
...
}
Modificar header "Authorization"
Este é um script para modificar um header Authorization.
Neste exemplo, o cliente está enviando um header Authorization cujo conteúdo é Basic YWJjZGVmCg==.
Decodificando o valor em Base64, obtemos abcdef, mas com um newline (\n) no final.
Iremos remover o trecho Basic, decodificar o valor restante em Base64, tirar o newline, e codificar novamente:
var auth = $call.request.getHeader( "Authorization" )
if ( auth != null ) {
// Remove o prefixo "Basic: "
if ( auth.toLowerCase().indexOf( "basic" ) == 0 )
auth = auth.substring("Basic".length).trim();
// Decodifica, remove newline e codifica novamente.
// Observe o uso do utilitario $base64.
// Veja outros utilitarios na referencia do desenvolvedor de hooks.
var plain = $base64.atob(auth);
var withoutWhitespace = plain.trim();
var base64 = $base64.btoa(withoutWhitespace);
// Sobrescreve o valor original do header
$call.request.setHeader( "Authorization", base64 );
}
Manipulação de query parameters
Adicionar query params de paginação quando ausentes
Neste exemplo, temos um backend que sempre espera receber parâmetros de paginação na query-string das chamadas (ex: /products?page=5&page_size=10).
Mas queremos que requisições sem esses parâmetros sejam executadas com valores-padrão sensatos (page=0 e page_size=10, por exemplo).
Observe também que não iremos adicionar parâmetros se eles já existirem.
if ( ! $request.getQueryParam("page") )
$request.setQueryParam("page", "0");
if ( ! $request.getQueryParam("page_size") )
$request.setQueryParam("page_size", "20");
Também queremos nos proteger contra chamadas que especifiquem parâmetros de paginação fora do aceitável, então continuamos o script acima com:
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);
Manipulação do corpo da requisição
Obter um parâmetro da URI e colocar no corpo da requisição
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 um parâmetro de PATH
Este exemplo demonstra como capturar um parâmetro definido no PATH da requisição.
var pathParam = $call.getPathParam();
pathParam = pathParam.toString().replace("}","").split("=");
$console.debug("pathParam", pathParam[1]);
Transformação com subelementos JSON e com 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");
Converter corpo da requisição de text para JSON
Este exemplo recebe um request no formato properties (um subset do formato YAML), como exibido abaixo:
invoice : 34843
customer: Benjamin
date : 2001-01-23
tax : 251.42
total : 4443.52
Queremos converter para um objeto JSON como o abaixo:
{
"invoice": 34843,
"customer": "Benjamin",
"date": "2001-01-23",
"tax": 251.42,
"total": 4443.52
}
Para isso, vamos executar o seguinte script no fluxo de requisição:
// Sempre especificamos o encoding.
// Neste caso estamos forçando utf-8,
// mas poderíamos consultar headers da requisição para descobrir isto.
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 += '}';
// Seta o novo corpo da requisição
$call.request.getBody().setString(newBody, "utf-8");
$call.request.setHeader("Content-Type", "application/json");
| O interceptor default TXT to JSON pode ser utilizado para esta transformação. |
Renomear um campo no corpo da requisição
Neste exemplo, o cliente envia uma requisição em um formato ligeiramente antigo; no formato novo, um campo mudou de nome. Queremos apenas traduzir o nome velho para o novo:
old format: new format:
{ {
"id" : 123, "id" : 123,
"fatherId": 456 "parentId": 456
} }
O script deve ser executado no fluxo de requisição, para que ocorra antes do encaminhamento para o 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" );
O script pode ser testado apontando para um endpoint que ecoe o que foi recebido:
curl -H 'Content-Type: application/json' -d '{"fatherId": 123}' http://10.0.0.5:8080/example
O resultado deve incluir o corpo recebido:
...
"body" : "{\"parentId\":123}",
...
Descompactar corpo da requisição compactado com gzip
O código abaixo descompacta o corpo da requisição compactado com gzip.
Essa transformação deve ser aplicada no fluxo de requisição.
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" );
Manipulação do corpo da resposta
Interromper o fluxo de execução na requisição e retornar a resposta para o cliente
$call.stopFlow = true;
$call.decision.setAccept( false );
$call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse();
$call.response.setStatus( 400 );
Tratar exceções do backend
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: "Ocorreu um erro no servidor."});
}
Converter corpo da resposta de JSON para XML
Neste exemplo, temos uma API no backend que retorna uma resposta JSON, e queremos montar um XML equivalente para devolver para o cliente.
| O interceptor default JSON to XML pode ser utilizado para esta transformação. |
Executaremos o seguinte script no fluxo de resposta:
// Faz o parse do json recebido
var json = JSON.parse( $call.response.getBody().getString("utf-8") );
// Esta função converte um objeto para uma representação XML.
// Observe que ela não lida com sub-objetos nem coleções.
var toXml = function(jsonNode, rootName) {
// Usaremos o JDOM para montar o XML.
var result = new Packages.org.jdom.Element(rootName);
// Cada propriedade no jsonNode vira um sub-elemento no elemento acima.
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 o filtro no node raiz.
// Observe que estamos assumindo que o resultado é sempre um objeto e não uma lista.
var root = toXml( json, "root" );
// Serializa e seta no response
$call.response.getBody().setString( $jdom.stringify(root), "utf-8" );
Com um pouco mais de código, podemos fazer a função toXml() lidar com listas de objetos, objetos compostos, etc.
Manipulação de variáveis de ambiente
Esta seção descreve como acessar variáveis de ambiente em um Custom JavaScript no API Gateway e apresenta os principais objetos e comandos disponíveis.
Quando usar
Use variáveis de ambiente quando o mesmo interceptor precisar mudar de comportamento entre ambientes, sem exigir alteração no código JavaScript. Exemplos comuns:
-
URLs de destino para chamadas HTTP
-
tokens e chaves de integração
-
nomes de header
-
flags de comportamento
Como acessar variáveis de ambiente
As variáveis de ambiente ficam disponíveis no objeto $call.environmentVariables, que é um Map<String, String>.
O acesso mais comum é:
var value = $call.environmentVariables.get("NOME_DA_VARIAVEL");
Exemplo simples
var clientId = $call.environmentVariables.get("client_id");
if (clientId != null) {
$request.setHeader("X-Client-Id", clientId);
}
Exemplo com validação obrigatória
Quando a variável for obrigatória, valide antes de usar:
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: "Variavel de ambiente 'destination' nao configurada"
}),
"UTF-8"
);
} else {
var backendResponse = $http.get(destination);
$request.setHeader("response-status", String(backendResponse.getStatus()));
}
Exemplo montando 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 response body sem acento grave (`)
O runtime de Custom JavaScript utiliza o engine Rhino (org.mozilla.javascript) com Context.VERSION_1_8. Na prática, isso significa que a sintaxe suportada não corresponde à do JavaScript moderno de browser ou Node.js.
Por isso, evite recursos de ES6+ no interceptor. Em especial, não use template literal com acento grave:
// Evite este formato
var body = `{"message":"${msg}"}`;
Prefira uma destas alternativas:
var body = '{"message":"' + msg + '"}';
$response.getBody().setString(body, "UTF-8");
var body = {
message: msg
};
$response.getBody().setString(JSON.stringify(body), "UTF-8");
A segunda opção é a mais segura para gerar JSON, porque evita erros de escape e concatenação.
Objetos globais disponíveis no script
Os objetos abaixo são injetados pelo gateway no escopo do Custom JavaScript.
| Objeto | Uso principal | Métodos/operações mais comuns |
|---|---|---|
|
Dados completos da chamada |
|
|
Request atual |
|
|
Response atual |
|
|
Chamada HTTP de dentro do script |
|
|
Log de debug do interceptor |
|
|
Encode/decode Base64 |
|
|
Conversão JSON Java/JS |
|
|
Leitura de cookies |
|
|
Manipulação XML |
|
|
Compressão/descompressão |
|
|
Acionamento de billing |
|
|
Encode/decode JWT |
|
|
Leitura de configurações do gateway |
|
|
Acesso ao grid/cache do gateway |
|
|
Assinaturas mais usadas do $http
Os testes do gateway validam estes 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);
|
Exemplo completo
O exemplo abaixo mostra um uso comum: ler uma variável de ambiente, chamar um backend e devolver uma resposta controlada em caso de erro.
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: "Variavel de ambiente 'destination' nao 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: "Falha ao consumir 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"
);
}
}
Boas práticas
-
Prefira
vare sintaxe JavaScript clássica para manter compatibilidade com o engine atual. -
Sempre valide se a variável de ambiente existe antes de usá-la em URL, header ou credencial.
-
Para JSON, prefira
JSON.stringify(…)em vez de concatenar strings manualmente. -
Use
$console.debug("chave", valor)quando o interceptor estiver comdebughabilitado. -
Ao gravar corpo de request/response, informe o charset explicitamente, por exemplo
"UTF-8".
Resumo rápido
Se a dúvida for apenas "como ler uma env var no Custom JS?", o essencial é isto:
var value = $call.environmentVariables.get("minha_variavel");
Se essa variável for usada para montar JSON de resposta, não utilize acento grave. Prefira JSON.stringify(…) ou concatenação simples.
Manipulação de tokens e extraInfo
Validar URL contra extraInfo
Neste exemplo, vamos criar um script a ser executado apenas nas URLs do tipo /projects/*.
Este padrão deve ser colocado no campo URL Pattern.
Esta API retorna a lista de membros de um determinado projeto.
Porém, cada access token só pode ver alguns projetos.
A lista de projetos permitidos é armazenada no extraInfo do token, no momento do cadastro:
|
O |
Se o token acima for usado para chamar /projects/marketing-site ou /projects/product-support, o acesso deve ser permitido, mas se for usado para chamar /projects/ui-redesign, queremos que seja barrado com erro "HTTP 403 Forbidden" e uma mensagem explicativa.
Como precisamos do token já extraído e validado, vamos colocar o script para rodar no fluxo de requisição:
var accessToken = $call.accessToken;
var extraInfo = accessToken.extraInfo;
// O valor é uma lista separada por vírgulas.
var allowedProjects = ( extraInfo.get('allowedProjects') || "*" ).split(",");
var allowedCall = false;
// Verifica se a URL chamada coincide com algum projeto permitido.
var url = $call.request.getRequestedUrl().getPath();
for ( var i = 0; i < allowedProjects.length; i++ ) {
// Um asterisco significa que este token pode ser usado para qualquer projeto.
if ( allowedProjects[i] == "*" ){
allowedCall = true;
break;
}
// Verifica se a URL é permitida.
// Se for, basta sair do script sem fazer nenhuma alteração.
if ( url.startsWith("/projects/" + allowedProjects[i]) ){
allowedCall = true;
break;
}
}
if ( !allowedCall ){
// Chega aqui se a URL chamada não é de nenhum projeto 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 o uso do utilitário JSON.stringify(),
// que retorna uma string com a representação JSON de um objeto.
$call.response.getBody().setString( JSON.stringify(body), "utf-8" );
}
Com esse script e token, os resultados esperados são:
GET /projects/marketing-site => HTTP 200
GET /projects/product-support => HTTP 200
GET /projects/ui-redesign => HTTP 403
Obter token a partir do extraInfo
Neste exemplo abaixo, temos alguns clientes legados que não podem modificar seus sistemas para enviar Access Tokens OAuth 2.0.
Ao invés disso, eles precisam continuar enviando um par nome-de-usuário/senha em um header proprietário X-Usuario-Senha.
Queremos recuperar esse header, extrair os dados, e montar um novo header Authorization com códigos que já conhecemos.
Para o gateway, tudo acontece como se o sistema legado tivesse enviado um header Authorization, como os demais sistemas. Por isso, este script precisa ser executado no fluxo de requisição. Como sabemos que apenas três sistemas legados estão dessa forma, faremos uma validação hard-coded das credenciais.
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 o valor do extraInfo com o valor do recurso da API
try {
var pass = false;
var url = $call.request.getRequestedUrl().getPath().split("/v1/");
var resource = url[1];
//Pega extrainfos e percorre todos, comparando o valor com o valor
//dos resources da API em questão que estão tentando 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);
}
Alterar a rota de acordo com o extraInfo de um token
Neste exemplo, a rota a ser seguida é diferente dependendo de uma flag no extraInfo de um access token, indicando se ele é um legado (devendo ser encaminhado para http://legacy.mycompany.com) ou não (devendo ser encaminhado para http://api.mycompany.com).
Observe que pelo menos uma rota precisa estar definida. Caso contrário, o gateway irá considerar a requisição como mal-formada e irá retornar HTTP 404 antes de termos a chance de executar 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 testar, crie uma app com o extraInfo isLegacy = true e faça uma requisição.
Depois, modifique a flag (ou crie outra app com isLegacy = false) e faça uma nova requisição.
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
A primeira requisição, feita por uma app moderna, deve atingir http://example.demo.sensedia.com/example/api .
A segunda requisição, feita por uma app legada, deve atingir http://legacy.backend.com.
Integração com serviços externos
Executar outros serviços durante a transformação
A melhor forma de executar outros serviços é usar o interceptor Service Mashup. Porém, também é possível executá-los com interceptores personalizados.
Neste exemplo, realizaremos uma orquestração de serviços. A chamada de serviços pode ser utilizada em qualquer fluxo (requisição/resposta).
Exemplos:
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)
}
Executar requisições em paralelo com threads
Em alguns cenários pode ser necessário executar múltiplas operações de forma paralela dentro de um interceptor, por exemplo ao consultar diferentes serviços externos durante a transformação de uma requisição.
Como o runtime do Custom JavaScript utiliza o motor Rhino, é possível acessar diretamente classes Java disponíveis no ambiente, como java.lang.Thread.
O exemplo abaixo demonstra como executar duas tarefas em paralelo e aguardar sua conclusão antes de continuar o fluxo.
// Exemplo de uso de threads para requisições paralelas
function main() {
try {
var Thread = java.lang.Thread;
var t1 = new Thread(function () {
// Lógica/Requisição 1 aqui
$call.tracer.trace("Thread 1 executada!");
});
var t2 = new Thread(function () {
// Lógica/Requisição 2 aqui
$call.tracer.trace("Thread 2 executada!");
});
var threadTimeout = 2000; // timeout em ms
t1.start();
t2.start();
// Aguarda a finalização das threads
t1.join();
t2.join(threadTimeout);
} catch (exception) {
$console.debug("Exception message", exception);
}
}
main();
Seguir redirecionamento (302) e combinar headers de múltiplas respostas
Este exemplo demonstra um cenário de integração avançada, no qual o interceptor realiza chamadas encadeadas a serviços externos e trata redirecionamentos HTTP (302) manualmente.
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' );
}
Tratamento de erros
Tratar exceções do backend
Neste exemplo, temos um backend que, em caso de erros, retorna um stacktrace em formato HTML ou texto simples.
Ao invés disso, queremos montar uma mensagem apropriada para o cliente.
Vamos executar o script abaixo no ponto 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: "Ocorreu um erro no servidor."});
}
Uma outra opção seria retornar a mensagem padrão de erro dentro de um JSON.
Para isso, execute o código abaixo no ponto 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");
}
Segurança e mascaramento de dados
Suprimir informações sensíveis antes de enviar métricas
Em alguns casos, temos APIs que transferem dados sigilosos entre o backend e o cliente (como números de cartão de crédito), e não queremos expor esses dados nem mesmo para eventuais administradores de sistemas que utilizam o API Manager para investigar problemas.
Neste exemplo, usaremos um script que é executado no ponto before-metric para mascarar esses campos.
| As requisições e respostas não são modificadas pelo script abaixo; apenas as cópias desses objetos, enviadas para armazenamento e análise no Manager, são afetadas. O backend continua recebendo o número de cartão de crédito que foi enviado pelo cliente, e o cliente continua recebendo o número na resposta. |
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 );
}
// Modificamos apenas os dados em $call.metric.
// $call.request e $call.response não são modificados.
$call.metric.requestBody = maskFieldsInBody( $call.metric.requestBody );
$call.metric.responseBody = maskFieldsInBody( $call.metric.responseBody );
Manipulação de cookies
Obter token a partir de um cookie
Neste exemplo, temos uma requisição que envia o access token como um cookie — isto é, misturado aos demais cookies enviados para o servidor. A requisição HTTP é da seguinte forma:
GET http://api.company.com/resources
Cookie: cookie1=value1; cookie2=value2; access_token=abcdef
Observe que existem três tokens no header Cookie, e queremos que o restante do processamento do API Gateway trate a requisição como se o AccessToken abcdef tivesse sido enviado em um header access_token.
Executaremos o script no fluxo de requisição, pois é durante a validação que os tokens serão realmente extraídos e validados.
// parseRequestCookieValues só funciona para a requisição.
// Para interpretar cookies na resposta,
// veja os demais métodos da classe utilitária $cookies.
// O resultado é uma Map case-insensitive de nomes de cookies para seus valores.
var allCookies = $cookies.parseRequestCookieValues($call.request.getHeader("Cookie"));
$call.request.setHeader( "access_token", allCookies.get("access_token") );
Manipulação de billing
Este é um script para manipular billing.
$call.billing = true;
$call.billingValue = 5.85;
$billing.execute($call);
Observações:
-
O atributo
billingValuetrabalha com precisão Double, por isso, é possível que tenha possíveis arredondamentos ou perdas de precisão em casas decimais. -
Para controle e bloqueio de consumo, deve ser selecionada a opção Abort request if fail no editor do interceptor, além de incluir o tratamento de exceção no próprio custom interceptor ou no Raise Exception do fluxo.
Share your suggestions with us!
Click here and then [+ Submit idea]