From 1ff8e16a138c6caaa22e01258c115ab3341aadcf Mon Sep 17 00:00:00 2001 From: David BRASSELY Date: Sat, 9 Feb 2019 13:52:34 +0100 Subject: [PATCH] feat(callout-http): Provide an option to terminate request process in case of an error during callout Closes gravitee-io/issues#1904 --- README.adoc | 32 +++- pom.xml | 2 +- .../policy/callout/CalloutHttpPolicy.java | 62 +++++--- .../CalloutHttpPolicyConfiguration.java | 41 +++++ src/main/resources/schemas/schema-form.json | 150 +++++++++++++++++- 5 files changed, 257 insertions(+), 30 deletions(-) diff --git a/README.adoc b/README.adoc index 6ddd865..4c5e0c8 100644 --- a/README.adoc +++ b/README.adoc @@ -45,28 +45,52 @@ execution. If no variable has been configured the result of the callout will not .^|url ^.^|X -|URL invoked by the HTTP client +|URL invoked by the HTTP client (support EL) ^.^|URL ^.^|- .^|headers ^.^|X -|List of HTTP headers used to invoke the URL (support EL). +|List of HTTP headers used to invoke the URL (support EL) ^.^|HTTP Headers ^.^|- .^|body ^.^|X -|The body content send when calling the URL (support EL). +|The body content send when calling the URL (support EL) ^.^|string ^.^|- .^|variables ^.^|X -|The variables to set in the execution context when retrieving content of HTTP call (support EL). +|The variables to set in the execution context when retrieving content of HTTP call (support EL) ^.^|List of variables ^.^|- +.^|exitOnError +^.^|X +|Terminate the request if the error condition is true +^.^|boolean +^.^|false + +.^|errorCondition +^.^|X +|The condition which will be verified to end the request (support EL) +^.^|string +^.^|{#calloutResponse.status >= 400 and #calloutResponse.status <= 599} + +.^|errorStatusCode +^.^|X +|HTTP Status Code send to the consumer if the condition is true +^.^|int +^.^|500 + +.^|errorContent +^.^|X +|The body response of the error if the condition is true (support EL) +^.^|string +^.^|- + |=== [source, json] diff --git a/pom.xml b/pom.xml index c1025a3..57b7217 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ - 1.12.0 + 1.13.0-SNAPSHOT 1.3.0 1.13.0 4.12 diff --git a/src/main/java/io/gravitee/policy/callout/CalloutHttpPolicy.java b/src/main/java/io/gravitee/policy/callout/CalloutHttpPolicy.java index ae158a9..2c36c39 100644 --- a/src/main/java/io/gravitee/policy/callout/CalloutHttpPolicy.java +++ b/src/main/java/io/gravitee/policy/callout/CalloutHttpPolicy.java @@ -18,6 +18,7 @@ import io.gravitee.gateway.api.ExecutionContext; import io.gravitee.gateway.api.Request; import io.gravitee.gateway.api.Response; +import io.gravitee.gateway.api.expression.TemplateEngine; import io.gravitee.policy.api.PolicyChain; import io.gravitee.policy.api.PolicyResult; import io.gravitee.policy.api.annotations.OnRequest; @@ -77,30 +78,47 @@ public void handle(HttpClientResponse httpResponse) { httpResponse.bodyHandler(new Handler() { @Override public void handle(Buffer body) { + TemplateEngine tplEngine = context.getTemplateEngine(); + // Put response ino template variable for EL - context.getTemplateEngine().getTemplateContext() + tplEngine.getTemplateContext() .setVariable(TEMPLATE_VARIABLE, new CalloutResponse(httpResponse, body.toString())); - // Set context variables - configuration.getVariables().forEach(variable -> { - try { - String extValue = (variable.getValue() != null) ? - context.getTemplateEngine().convert(variable.getValue()) : null; - - context.setAttribute(variable.getName(), extValue); - } catch (Exception ex) { - // Do nothing - ex.printStackTrace(); - } - }); - - context.getTemplateEngine().getTemplateContext() - .setVariable(TEMPLATE_VARIABLE, null); - - // Finally continue chaining - policyChain.doNext(request, response); - + // Close HTTP client httpClient.close(); + + // Process callout response + boolean exit = false; + + if (configuration.isExitOnError()) { + exit = tplEngine.getValue(configuration.getErrorCondition(), Boolean.class); + } + + if (! exit) { + // Set context variables + configuration.getVariables().forEach(variable -> { + try { + String extValue = (variable.getValue() != null) ? + tplEngine.getValue(variable.getValue(), String.class) : null; + + context.setAttribute(variable.getName(), extValue); + } catch (Exception ex) { + // Do nothing + } + }); + + tplEngine.getTemplateContext() + .setVariable(TEMPLATE_VARIABLE, null); + + // Finally continue chaining + policyChain.doNext(request, response); + } else { + policyChain.failWith( + PolicyResult + .failure( + configuration.getErrorStatusCode(), + tplEngine.getValue(configuration.getErrorContent(), String.class))); + } } }); } @@ -121,13 +139,13 @@ public void handle(Buffer body) { } } catch (Exception ex) { // Do nothing - ex.printStackTrace(); } }); } if (configuration.getBody() != null && !configuration.getBody().isEmpty()) { - String body = context.getTemplateEngine().convert(configuration.getBody()); + String body = context.getTemplateEngine() + .getValue(configuration.getBody(), String.class); httpRequest.headers().remove(HttpHeaders.TRANSFER_ENCODING); httpRequest.putHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(body.length())); httpRequest.end(Buffer.buffer(body)); diff --git a/src/main/java/io/gravitee/policy/callout/configuration/CalloutHttpPolicyConfiguration.java b/src/main/java/io/gravitee/policy/callout/configuration/CalloutHttpPolicyConfiguration.java index 4d136a8..9b1b096 100644 --- a/src/main/java/io/gravitee/policy/callout/configuration/CalloutHttpPolicyConfiguration.java +++ b/src/main/java/io/gravitee/policy/callout/configuration/CalloutHttpPolicyConfiguration.java @@ -16,6 +16,7 @@ package io.gravitee.policy.callout.configuration; import io.gravitee.common.http.HttpMethod; +import io.gravitee.common.http.HttpStatusCode; import io.gravitee.policy.api.PolicyConfiguration; import java.util.ArrayList; @@ -37,6 +38,14 @@ public class CalloutHttpPolicyConfiguration implements PolicyConfiguration { private List variables = new ArrayList<>(); + private boolean exitOnError; + + private String errorCondition; + + private int errorStatusCode = HttpStatusCode.INTERNAL_SERVER_ERROR_500; + + private String errorContent; + public String getUrl() { return url; } @@ -76,4 +85,36 @@ public List getVariables() { public void setVariables(List variables) { this.variables = variables; } + + public boolean isExitOnError() { + return exitOnError; + } + + public void setExitOnError(boolean exitOnError) { + this.exitOnError = exitOnError; + } + + public String getErrorCondition() { + return errorCondition; + } + + public void setErrorCondition(String errorCondition) { + this.errorCondition = errorCondition; + } + + public int getErrorStatusCode() { + return errorStatusCode; + } + + public void setErrorStatusCode(int errorStatusCode) { + this.errorStatusCode = errorStatusCode; + } + + public String getErrorContent() { + return errorContent; + } + + public void setErrorContent(String errorContent) { + this.errorContent = errorContent; + } } diff --git a/src/main/resources/schemas/schema-form.json b/src/main/resources/schemas/schema-form.json index 06d16aa..bcc2b6b 100644 --- a/src/main/resources/schemas/schema-form.json +++ b/src/main/resources/schemas/schema-form.json @@ -40,8 +40,14 @@ "title": "Request body", "type" : "string", "x-schema-form": { - "type": "textarea", - "placeholder": "Put your request body here" + "type": "codemirror", + "codemirrorOptions": { + "placeholder": "Put request body here", + "lineWrapping": true, + "lineNumbers": true, + "allowDropFileTypes": true, + "autoCloseTags": true + } } }, "variables" : { @@ -66,10 +72,148 @@ "name", "value" ] + }, + "exitOnError": { + "title": "Exit on error", + "description": "Terminate the request if the error condition is true", + "type" : "boolean", + "default": false + }, + "errorCondition": { + "title": "Error condition", + "description": "The condition which will be verified to end the request (support EL).", + "default": "{#calloutResponse.status >= 400 and #calloutResponse.status <= 599}", + "type" : "string" + }, + "errorStatusCode": { + "title": "Error status code", + "description": "HTTP Status Code send to the consumer if the condition is true", + "type" : "string", + "default": "500", + "enum": [ + "100", + "101", + "102", + "200", + "201", + "202", + "203", + "204", + "205", + "206", + "207", + "300", + "301", + "302", + "302", + "303", + "304", + "305", + "307", + "400", + "401", + "402", + "403", + "404", + "405", + "406", + "407", + "408", + "409", + "410", + "411", + "412", + "413", + "414", + "415", + "416", + "417", + "422", + "423", + "424", + "429", + "500", + "501", + "502", + "503", + "504", + "505", + "507" + ], + "x-schema-form": { + "type": "select", + "titleMap": { + "100": "100 - CONTINUE", + "101": "101 - SWITCHING_PROTOCOLS", + "102": "102 - PROCESSING", + "200": "200 - OK", + "201": "201 - CREATED", + "202": "202 - ACCEPTED", + "203": "203 - NON_AUTHORITATIVE_INFORMATION", + "204": "204 - NO_CONTENT", + "205": "205 - RESET_CONTENT", + "206": "206 - PARTIAL_CONTENT", + "207": "207 - MULTI_STATUS", + "300": "300 - MULTIPLE_CHOICES", + "301": "301 - MOVED_PERMANENTLY", + "302": "302 - MOVED_TEMPORARILY", + "302": "302 - FOUND", + "303": "303 - SEE_OTHER", + "304": "304 - NOT_MODIFIED", + "305": "305 - USE_PROXY", + "307": "307 - TEMPORARY_REDIRECT", + "400": "400 - BAD_REQUEST", + "401": "401 - UNAUTHORIZED", + "402": "402 - PAYMENT_REQUIRED", + "403": "403 - FORBIDDEN", + "404": "404 - NOT_FOUND", + "405": "405 - METHOD_NOT_ALLOWED", + "406": "406 - NOT_ACCEPTABLE", + "407": "407 - PROXY_AUTHENTICATION_REQUIRED", + "408": "408 - REQUEST_TIMEOUT", + "409": "409 - CONFLICT", + "410": "410 - GONE", + "411": "411 - LENGTH_REQUIRED", + "412": "412 - PRECONDITION_FAILED", + "413": "413 - REQUEST_ENTITY_TOO_LARGE", + "414": "414 - REQUEST_URI_TOO_LONG", + "415": "415 - UNSUPPORTED_MEDIA_TYPE", + "416": "416 - REQUESTED_RANGE_NOT_SATISFIABLE", + "417": "417 - EXPECTATION_FAILED", + "422": "422 - UNPROCESSABLE_ENTITY", + "423": "423 - LOCKED", + "424": "424 - FAILED_DEPENDENCY", + "429": "429 - TOO_MANY_REQUESTS", + "500": "500 - INTERNAL_SERVER_ERROR", + "501": "501 - NOT_IMPLEMENTED", + "502": "502 - BAD_GATEWAY", + "503": "503 - SERVICE_UNAVAILABLE", + "504": "504 - GATEWAY_TIMEOUT", + "505": "505 - HTTP_VERSION_NOT_SUPPORTED", + "507": "507 - INSUFFICIENT_STORAGE" + } + } + }, + "errorContent": { + "title": "Error response body", + "description": "The body response of the error if the condition is true (support EL)", + "type": "string", + "x-schema-form": { + "type": "codemirror", + "codemirrorOptions": { + "placeholder": "Put response body here", + "lineWrapping": true, + "lineNumbers": true, + "allowDropFileTypes": true, + "autoCloseTags": true, + "mode": "javascript" + } + } } },"required": [ "url", "method", - "variables" + "variables", + "exitOnError" ] } \ No newline at end of file