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 nameV } } // 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)) .body(jsonObjectToWriteFromEngineVersion(operation, requestJson).toString() @@ -368,7 +367,7 @@ private LogicalResponse delete(final String path, final Logical.logicalOperation 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/" + adjustPathForDelete(path, config.getPrefixPathDepth(), operation)) .header("X-Vault-Token", config.getToken()) @@ -418,7 +417,7 @@ public LogicalResponse delete(final String path, final int[] versions) throws Va return retry(attempt -> { // Make an HTTP request to Vault JsonObject versionsToDelete = new JsonObject().add("versions", versions); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path, config.getPrefixPathDepth())) .header("X-Vault-Token", config.getToken()) @@ -478,7 +477,7 @@ public LogicalResponse unDelete(final String path, final int[] versions) throws return retry(attempt -> { // Make an HTTP request to Vault JsonObject versionsToUnDelete = new JsonObject().add("versions", versions); - final RestResponse restResponse = new Rest() //NOPMD + final RestResponse restResponse = getRest() //NOPMD .url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path, config.getPrefixPathDepth())) .header("X-Vault-Token", config.getToken()) @@ -525,7 +524,7 @@ public LogicalResponse destroy(final String path, final int[] versions) throws V return retry(attempt -> { // Make an HTTP request to Vault JsonObject versionsToDestroy = new JsonObject().add("versions", versions); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path, config.getPrefixPathDepth())) .header("X-Vault-Token", config.getToken()) @@ -562,7 +561,7 @@ public LogicalResponse upgrade(final String kvPath) throws VaultException { // Make an HTTP request to Vault JsonObject kvToUpgrade = new JsonObject().add("options", new JsonObject().add("version", 2)); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(config.getAddress() + "/v1/sys/mounts/" + (kvPath.replaceAll("/", "") + "/tune")) .header("X-Vault-Token", config.getToken()) diff --git a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java index c6b9345a..34ecb0a9 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java +++ b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java @@ -2,6 +2,7 @@ import io.github.jopenlibs.vault.VaultConfig; import io.github.jopenlibs.vault.VaultException; +import io.github.jopenlibs.vault.rest.Rest; /** @@ -45,6 +46,10 @@ static T retry(final EndpointOperation op, int retryCount, long retryInte } } + protected Rest getRest() { + return new Rest(config.getHttpClient()); + } + public interface EndpointOperation { /** @@ -64,4 +69,5 @@ private static void sleep(long delay) { e.printStackTrace(); } } + } diff --git a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java index 4c182f68..6f6ee569 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java +++ b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java @@ -6,7 +6,6 @@ import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; import io.github.jopenlibs.vault.response.DatabaseResponse; -import io.github.jopenlibs.vault.rest.Rest; import io.github.jopenlibs.vault.rest.RestResponse; import java.nio.charset.StandardCharsets; import java.util.List; @@ -91,7 +90,7 @@ public DatabaseResponse createOrUpdateRole(final String roleName, return retry(attempt -> { final String requestJson = roleOptionsToJson(options); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) @@ -137,7 +136,7 @@ public DatabaseResponse createOrUpdateRole(final String roleName, */ public DatabaseResponse getRole(final String roleName) throws VaultException { return retry(attempt -> { - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) @@ -190,7 +189,7 @@ public DatabaseResponse revoke(final String serialNumber) throws VaultException } final String requestJson = jsonObject.toString(); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) @@ -235,7 +234,7 @@ public DatabaseResponse revoke(final String serialNumber) throws VaultException */ public DatabaseResponse deleteRole(final String roleName) throws VaultException { return retry(attempt -> { - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) @@ -282,7 +281,7 @@ public DatabaseResponse deleteRole(final String roleName) throws VaultException */ public DatabaseResponse creds(final String roleName) throws VaultException { return retry(attempt -> { - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/creds/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java index 24b288ed..174cba24 100644 --- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java +++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java @@ -6,7 +6,6 @@ import io.github.jopenlibs.vault.json.Json; import io.github.jopenlibs.vault.json.JsonObject; import io.github.jopenlibs.vault.response.PkiResponse; -import io.github.jopenlibs.vault.rest.Rest; import io.github.jopenlibs.vault.rest.RestResponse; import java.nio.charset.StandardCharsets; import java.util.List; @@ -117,7 +116,7 @@ public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions o return retry(attempt -> { final String requestJson = roleOptionsToJson(options); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) @@ -165,7 +164,7 @@ public PkiResponse createOrUpdateRole(final String roleName, final RoleOptions o */ public PkiResponse getRole(final String roleName) throws VaultException { return retry(attempt -> { - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) @@ -217,7 +216,7 @@ public PkiResponse revoke(final String serialNumber) throws VaultException { jsonObject.add("serial_number", serialNumber); } final String requestJson = jsonObject.toString(); - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/revoke", config.getAddress(), this.mountPath)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) @@ -262,7 +261,7 @@ public PkiResponse revoke(final String serialNumber) throws VaultException { */ public PkiResponse deleteRole(final String roleName) throws VaultException { return retry(attempt -> { - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format("%s/v1/%s/roles/%s", config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) @@ -428,7 +427,7 @@ public PkiResponse issue( String endpoint = (csr == null || csr.isEmpty()) ? "%s/v1/%s/issue/%s" : "%s/v1/%s/sign/%s"; - final RestResponse restResponse = new Rest()//NOPMD + final RestResponse restResponse = getRest()//NOPMD .url(String.format(endpoint, config.getAddress(), this.mountPath, roleName)) .header("X-Vault-Token", config.getToken()) .header("X-Vault-Namespace", this.nameSpace) diff --git a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java index efa9fc33..782789b0 100644 --- a/src/main/java/io/github/jopenlibs/vault/rest/Rest.java +++ b/src/main/java/io/github/jopenlibs/vault/rest/Rest.java @@ -107,6 +107,15 @@ public X509Certificate[] getAcceptedIssuers() { private Integer readTimeoutSeconds; private Boolean sslVerification; private SSLContext sslContext; + private final HttpClient configuredClient; + + public Rest(HttpClient configuredClient) { + this.configuredClient = configuredClient; + } + + public Rest() { + this(null); + } /** *

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; + } +}