diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java index f7dbed53..d932fe4a 100644 --- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java +++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java @@ -1,6 +1,7 @@ package io.github.jopenlibs.vault; import java.io.Serializable; +import java.net.http.HttpClient; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -43,6 +44,7 @@ public class VaultConfig implements Serializable { private Integer globalEngineVersion; private String nameSpace; private EnvironmentLoader environmentLoader; + private HttpClient httpClient; /** *
The code used to load environment variables is encapsulated here, so that a mock version @@ -278,6 +280,19 @@ public VaultConfig prefixPath(String prefixPath) { return prefixPathDepth(countElements + 1); } + /** + *
Set a preconfigured HttpClient instance to use by REST API calls. This allows to reuse + * http resources (connections, worker threads) between calls. If a preconfigured HttpClient is specified, then + * sslConfig and openTimeout values passed to VaultConfig are ignored. + * + * @param httpClient preconfigured http client instance + * @return VaultConfig + */ + public VaultConfig httpClient(HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + /** *
Sets the maximum number of times that an API operation will retry upon failure.
* @@ -318,7 +333,6 @@ void setEngineVersion(final Integer engineVersion) { this.globalEngineVersion = engineVersion; } - /** *This is the terminating method in the builder pattern. The method that validates all of
* the fields that has been set already, uses environment variables when available to populate
@@ -414,4 +428,8 @@ public String getNameSpace() {
public int getPrefixPathDepth() {
return prefixPathDepth;
}
+
+ public HttpClient getHttpClient() {
+ return httpClient;
+ }
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
index f05e7cf8..321481e7 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
@@ -11,7 +11,6 @@
import io.github.jopenlibs.vault.response.LookupResponse;
import io.github.jopenlibs.vault.response.UnwrapResponse;
import io.github.jopenlibs.vault.response.WrapResponse;
-import io.github.jopenlibs.vault.rest.Rest;
import io.github.jopenlibs.vault.rest.RestResponse;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
@@ -376,7 +375,7 @@ public AuthResponse createToken(final TokenRequest tokenRequest, final String to
final String url = urlBuilder.toString();
// HTTP request to Vault
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(url)
.header("X-Vault-Token", config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -437,7 +436,7 @@ public AuthResponse loginByAppID(final String path, final String appId, final St
// HTTP request to Vault
final String requestJson = Json.object().add("app_id", appId).add("user_id", userId)
.toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + path)
.header("X-Vault-Namespace", this.nameSpace)
.body(requestJson.getBytes(StandardCharsets.UTF_8))
@@ -525,7 +524,7 @@ public AuthResponse loginByAppRole(final String path, final String roleId,
// HTTP request to Vault
final String requestJson = Json.object().add("role_id", roleId)
.add("secret_id", secretId).toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + path + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -602,7 +601,7 @@ public AuthResponse loginByUserPass(final String username, final String password
return retry(attempt -> {
// HTTP request to Vault
final String requestJson = Json.object().add("password", password).toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/login/" + username)
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -721,7 +720,7 @@ public AuthResponse loginByAwsEc2(final String role, final String identity,
}
final String requestJson = request.toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/login")
.body(requestJson.getBytes(StandardCharsets.UTF_8))
.header("X-Vault-Namespace", this.nameSpace)
@@ -789,7 +788,7 @@ public AuthResponse loginByAwsEc2(final String role, final String pkcs7, final S
request.add("nonce", nonce);
}
final String requestJson = request.toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -866,7 +865,7 @@ public AuthResponse loginByAwsIam(final String role, final String iamRequestUrl,
request.add("role", role);
}
final String requestJson = request.toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -939,7 +938,7 @@ public AuthResponse loginByGithub(final String githubToken, final String githubA
return retry(attempt -> {
// HTTP request to Vault
final String requestJson = Json.object().add("token", githubToken).toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -1020,7 +1019,7 @@ public AuthResponse loginByJwt(final String provider, final String role, final S
// HTTP request to Vault
final String requestJson = Json.object().add("role", role).add("jwt", jwt)
.toString();
- final RestResponse restResponse = new Rest()
+ final RestResponse restResponse = getRest()
.url(config.getAddress() + "/v1/" + authPath + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -1179,7 +1178,7 @@ public AuthResponse loginByCert(final String certAuthMount) throws VaultExceptio
final String mount = certAuthMount != null ? certAuthMount : "cert";
return retry(attempt -> {
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/login")
.header("X-Vault-Namespace", this.nameSpace)
.header("X-Vault-Request", "true")
@@ -1251,7 +1250,7 @@ public AuthResponse renewSelf(final long increment, final String tokenAuthMount)
return retry(attempt -> {
// HTTP request to Vault
final String requestJson = Json.object().add("increment", increment).toString();
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/renew-self")
.header("X-Vault-Token", config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -1307,7 +1306,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
return retry(attempt -> {
// HTTP request to Vault
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/lookup-self")
.header("X-Vault-Token", config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
@@ -1384,7 +1383,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
retry(attempt -> {
// HTTP request to Vault
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/auth/" + mount + "/revoke-self")
.header("X-Vault-Token", config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Debug.java b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
index ffa8f5a6..32c4f1e5 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Debug.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
@@ -94,7 +94,7 @@ public HealthResponse health(
return retry(attempt -> {
// Build an HTTP request for Vault
- final Rest rest = new Rest()//NOPMD
+ final Rest rest = getRest()//NOPMD
.url(config.getAddress() + "/v1/" + path)
.header("X-Vault-Token", config.getToken())
.header("X-Vault-Namespace", this.nameSpace)
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
index bbff53e4..52e619cc 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -6,7 +6,6 @@
import io.github.jopenlibs.vault.json.JsonObject;
import io.github.jopenlibs.vault.json.JsonValue;
import io.github.jopenlibs.vault.response.LogicalResponse;
-import io.github.jopenlibs.vault.rest.Rest;
import io.github.jopenlibs.vault.rest.RestResponse;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -85,7 +84,7 @@ private LogicalResponse read(final String path, final logicalOperations operatio
throws VaultException {
return retry(attempt -> {
// Make an HTTP request to Vault
- final RestResponse restResponse = new Rest()//NOPMD
+ final RestResponse restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(path,
config.getPrefixPathDepth(), operation))
.header("X-Vault-Token", config.getToken())
@@ -142,7 +141,7 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege
attempt -> {
// Make an HTTP request to Vault
final RestResponse restResponse =
- new Rest() //NOPMD
+ getRest() //NOPMD
.url(config.getAddress() + "/v1/" + adjustPathForReadOrWrite(
path,
config.getPrefixPathDepth(), logicalOperations.readV2))
@@ -275,7 +274,7 @@ private LogicalResponse write(final String path, final Map Sets the base URL to which the HTTP request will be sent. The URL may or may not include
@@ -396,18 +405,9 @@ private String parametersToQueryString() {
* @throws InterruptedException if connection is interrupted
*/
private RestResponse send(HttpRequest req) throws IOException, InterruptedException {
- final var client = HttpClient.newBuilder();
- if (connectTimeoutSeconds != null) {
- client.connectTimeout(Duration.of(connectTimeoutSeconds, ChronoUnit.SECONDS));
- }
-
- if (sslVerification != null && !sslVerification) {
- client.sslContext(DISABLED_SSL_CONTEXT);
- } else if (sslContext != null) {
- client.sslContext(sslContext);
- }
+ final HttpClient client = getClient();
- var response = client.build().send(req, BodyHandlers.ofString());
+ var response = client.send(req, BodyHandlers.ofString());
// Get the resulting status code
final var statusCode = response.statusCode();
@@ -419,6 +419,23 @@ private RestResponse send(HttpRequest req) throws IOException, InterruptedExcept
return new RestResponse(statusCode, mimeType, body);
}
+ private HttpClient getClient() {
+ if (configuredClient != null) {
+ return configuredClient;
+ }
+ final var client = HttpClient.newBuilder();
+ if (connectTimeoutSeconds != null) {
+ client.connectTimeout(Duration.of(connectTimeoutSeconds, ChronoUnit.SECONDS));
+ }
+
+ if (sslVerification != null && !sslVerification) {
+ client.sslContext(DISABLED_SSL_CONTEXT);
+ } else if (sslContext != null) {
+ client.sslContext(sslContext);
+ }
+ return client.build();
+ }
+
/**
* This helper method build an {@link HttpRequest.Builder} object used to send requests to
diff --git a/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java b/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java
new file mode 100644
index 00000000..544da225
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/ConnectionReUsageTest.java
@@ -0,0 +1,129 @@
+package io.github.jopenlibs.vault;
+
+import io.github.jopenlibs.vault.api.Logical;
+import io.github.jopenlibs.vault.response.LogicalResponse;
+import io.github.jopenlibs.vault.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.vault.mock.MockVault;
+import java.net.Socket;
+import java.net.http.HttpClient;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.NetworkTrafficServerConnector;
+import org.eclipse.jetty.server.Server;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ConnectionReUsageTest {
+
+ private static final String TOKEN = "token";
+
+ private final NetworkConnectionListener connectionListener = new NetworkConnectionListener();
+
+ private Server vaultServerMock;
+
+ @Before
+ public void setUp() throws Exception {
+ final MockVault mockVault = new MockVault(200, "{\"data\":{\"key\":\"value\"}}");
+ vaultServerMock = initHttpMockVaultWithListener(mockVault, connectionListener);
+ vaultServerMock.start();
+
+ connectionListener.reset();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (vaultServerMock != null) {
+ VaultTestUtils.shutdownMockVault(vaultServerMock);
+ }
+ }
+
+ @Test
+ public void readShouldReuseConnectionAfterSuccessfulRequestByHttp() throws Exception {
+ int readNum = 10;
+
+ HttpClient httpClient = HttpClient.newBuilder()
+ .connectTimeout(Duration.of(5, ChronoUnit.SECONDS))
+ .build();
+
+ Logical vault = Vault.create(new VaultConfig()
+ .httpClient(httpClient)
+ .address("http://localhost:8999")
+ .token(TOKEN)
+ .readTimeout(1)
+ .engineVersion(1)
+ .build()).logical();
+
+ for (int i = 0; i < readNum; i++) {
+ LogicalResponse resp = vault.read("testing/p1");
+ assertEquals("value", resp.getData().get("key"));
+ }
+
+ int closed = connectionListener.getClosed();
+ int opened = connectionListener.getOpened();
+
+ assertTrue("Too many connections opened: " + opened, opened <= (closed + 1));
+ }
+
+ private static class NetworkConnectionListener implements NetworkTrafficListener {
+
+ private final AtomicInteger opened = new AtomicInteger();
+ private final AtomicInteger closed = new AtomicInteger();
+
+ @Override
+ public void opened(Socket socket) {
+ opened.incrementAndGet();
+ }
+
+ @Override
+ public void closed(Socket socket) {
+ closed.incrementAndGet();
+ }
+
+ @Override
+ public void incoming(Socket socket, ByteBuffer byteBuffer) {
+ }
+
+ @Override
+ public void outgoing(Socket socket, ByteBuffer byteBuffer) {
+ }
+
+ public int getOpened() {
+ return opened.get();
+ }
+
+ public int getClosed() {
+ return closed.get();
+ }
+
+ public void reset() {
+ opened.set(0);
+ closed.set(0);
+ }
+ }
+
+ public Server initHttpMockVaultWithListener(final MockVault mock, NetworkTrafficListener listener) {
+ final Server server = new Server();
+ final HttpConfiguration http = new HttpConfiguration();
+
+ NetworkTrafficServerConnector connector =
+ new NetworkTrafficServerConnector(
+ server,
+ new HttpConnectionFactory(http));
+ connector.setNetworkTrafficListener(listener);
+ connector.setPort(8999);
+ server.setConnectors(new Connector[]{connector});
+
+ server.setHandler(mock);
+ return server;
+ }
+}