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