From c1f6ee891bb38cbf8e9337ce94188df58335eb54 Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Mon, 10 Apr 2017 13:22:54 +0200 Subject: [PATCH] #9 Number of retries and custom timeout --- CHANGELOG.md | 4 + pom.xml | 2 +- .../jvault/connector/HTTPVaultConnector.java | 92 +++++++++++++++---- .../factory/HTTPVaultConnectorFactory.java | 30 +++++- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b3f28..63c231d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.0 [work in progress] +* [feature] Custom timeout and number of retries (#9) +* [fix] `SecretResponse` does not throw NPE on `get(key)` and `getData()` + ## 0.5.0 [2017-03-18] * [feature] Convenience methods for DB credentials (#7) * [fix] Minor bugfix in TokenBuilder diff --git a/pom.xml b/pom.xml index f371b7c..d8ce5e4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ de.stklcode.jvault connector - 0.5.1-SNAPSHOT + 0.6.0-SNAPSHOT jar diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java index 0fec43b..f63dbe9 100644 --- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java +++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java @@ -23,11 +23,13 @@ import de.stklcode.jvault.connector.model.*; import de.stklcode.jvault.connector.model.response.*; import de.stklcode.jvault.connector.model.response.embedded.AuthMethod; import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.*; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.params.HttpConnectionParams; import org.apache.http.util.EntityUtils; import javax.net.ssl.*; @@ -64,6 +66,8 @@ public class HTTPVaultConnector implements VaultConnector { private final String baseURL; /* Base URL of Vault */ private final SSLContext sslContext; /* Custom SSLSocketFactory */ + private final int retries; /* Number of retries on 5xx errors */ + private final Integer timeout; /* Timeout in milliseconds */ private boolean authorized = false; /* authorization status */ private String token; /* current token */ @@ -115,11 +119,26 @@ public class HTTPVaultConnector implements VaultConnector { * @param sslContext Custom SSL Context */ public HTTPVaultConnector(String hostname, boolean useTLS, Integer port, String prefix, SSLContext sslContext) { + this(hostname, useTLS, port, prefix, sslContext, 0, null); + } + + /** + * Create connector using hostname, schema, port, path and trusted certificate. + * + * @param hostname The hostname + * @param useTLS If TRUE, use HTTPS, otherwise HTTP + * @param port The port + * @param prefix HTTP API prefix (default: /v1/) + * @param sslContext Custom SSL Context + */ + public HTTPVaultConnector(String hostname, boolean useTLS, Integer port, String prefix, SSLContext sslContext, int numberOfRetries, Integer timeout) { this(((useTLS) ? "https" : "http") + "://" + hostname + ((port != null) ? ":" + port : "") + prefix, - sslContext); + sslContext, + numberOfRetries, + timeout); } /** @@ -138,8 +157,32 @@ public class HTTPVaultConnector implements VaultConnector { * @param sslContext Custom SSL Context */ public HTTPVaultConnector(String baseURL, SSLContext sslContext) { + this(baseURL, sslContext, 0, null); + } + + /** + * Create connector using full URL and trusted certificate. + * + * @param baseURL The URL + * @param sslContext Custom SSL Context + * @param numberOfRetries number of retries on 5xx errors + */ + public HTTPVaultConnector(String baseURL, SSLContext sslContext, int numberOfRetries) { + this(baseURL, sslContext, numberOfRetries, null); + } + + /** + * Create connector using full URL and trusted certificate. + * + * @param baseURL The URL + * @param sslContext Custom SSL Context + * @param numberOfRetries number of retries on 5xx errors + */ + public HTTPVaultConnector(String baseURL, SSLContext sslContext, int numberOfRetries, Integer timeout) { this.baseURL = baseURL; this.sslContext = sslContext; + this.retries = numberOfRetries; + this.timeout = timeout; this.jsonMapper = new ObjectMapper(); } @@ -634,7 +677,7 @@ public class HTTPVaultConnector implements VaultConnector { if (token != null) post.addHeader("X-Vault-Token", token); - return request(post); + return request(post, retries); } /** @@ -661,7 +704,7 @@ public class HTTPVaultConnector implements VaultConnector { if (token != null) put.addHeader("X-Vault-Token", token); - return request(put); + return request(put, retries); } /** @@ -678,7 +721,7 @@ public class HTTPVaultConnector implements VaultConnector { if (token != null) delete.addHeader("X-Vault-Token", token); - return request(delete); + return request(delete, retries); } /** @@ -701,22 +744,27 @@ public class HTTPVaultConnector implements VaultConnector { if (token != null) get.addHeader("X-Vault-Token", token); - return request(get); + return request(get, retries); } /** * Execute prepared HTTP request and return result. * - * @param base Prepares Request + * @param base Prepares Request + * @param retries number of retries * @return HTTP response * @throws VaultConnectorException on connection error */ - private String request(HttpRequestBase base) throws VaultConnectorException { + private String request(HttpRequestBase base, int retries) throws VaultConnectorException { /* Set JSON Header */ base.addHeader("accept", "application/json"); HttpResponse response = null; try (CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(sslContext).build()) { + /* Set custom timeout, if defined */ + if (this.timeout != null) + base.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setConnectTimeout(timeout).build()); + /* Execute request */ response = httpClient.execute(base); /* Check if response is valid */ if (response == null) @@ -733,20 +781,26 @@ public class HTTPVaultConnector implements VaultConnector { case 403: throw new PermissionDeniedException(); default: - InvalidResponseException ex = new InvalidResponseException("Invalid response code") - .withStatusCode(response.getStatusLine().getStatusCode()); - if (response.getEntity() != null) { - try (BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) { - String responseString = br.lines().collect(Collectors.joining("\n")); - ErrorResponse er = jsonMapper.readValue(responseString, ErrorResponse.class); - /* Check for "permission denied" response */ - if (er.getErrors().size() > 0 && er.getErrors().get(0).equals("permission denied")) - throw new PermissionDeniedException(); - throw ex.withResponse(er.toString()); - } catch (IOException ignored) { + if (response.getStatusLine().getStatusCode() >= 500 && response.getStatusLine().getStatusCode() < 600 && retries > 0) { + /* Retry on 5xx errors */ + return request(base, retries - 1); + } else { + /* Fail on different error code and/or no retries left */ + InvalidResponseException ex = new InvalidResponseException("Invalid response code") + .withStatusCode(response.getStatusLine().getStatusCode()); + if (response.getEntity() != null) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) { + String responseString = br.lines().collect(Collectors.joining("\n")); + ErrorResponse er = jsonMapper.readValue(responseString, ErrorResponse.class); + /* Check for "permission denied" response */ + if (er.getErrors().size() > 0 && er.getErrors().get(0).equals("permission denied")) + throw new PermissionDeniedException(); + throw ex.withResponse(er.toString()); + } catch (IOException ignored) { + } } + throw ex; } - throw ex; } } catch (IOException e) { throw new InvalidResponseException("Unable to read response", e); diff --git a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java index 0b8b45b..b35233e 100644 --- a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java +++ b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java @@ -43,12 +43,15 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory { public static final Integer DEFAULT_PORT = 8200; public static final boolean DEFAULT_TLS = true; public static final String DEFAULT_PREFIX = "/v1/"; + public static final int DEFAULT_NUMBER_OF_RETRIES = 0; private String host; private Integer port; private boolean tls; private String prefix; private SSLContext sslContext; + private int numberOfRetries; + private Integer timeout; /** * Default empty constructor. @@ -59,6 +62,7 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory { port = DEFAULT_PORT; tls = DEFAULT_TLS; prefix = DEFAULT_PREFIX; + numberOfRetries = DEFAULT_NUMBER_OF_RETRIES; } /** @@ -150,9 +154,33 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory { return this; } + /** + * Define the number of retries to attempt on 5xx errors. + * + * @param numberOfRetries The number of retries to attempt on 5xx errors (default: 0) + * @return self + * @since 0.6.0 + */ + public HTTPVaultConnectorFactory withNumberOfRetries(int numberOfRetries) { + this.numberOfRetries = numberOfRetries; + return this; + } + + /** + * Define a custom timeout for the HTTP connection. + * + * @param milliseconds Timeout value in milliseconds. + * @return self + * @since 0.6.0 + */ + public HTTPVaultConnectorFactory withTimeout(int milliseconds) { + this.timeout = milliseconds; + return this; + } + @Override public HTTPVaultConnector build() { - return new HTTPVaultConnector(host, tls, port, prefix, sslContext); + return new HTTPVaultConnector(host, tls, port, prefix, sslContext, numberOfRetries, timeout); } /**