Skip to content

Commit 03eb710

Browse files
authored
Add a connectTimeout option to the TSC builder and DeterministicClient (#163)
* Add a connectTimeout option * formatting * Stupid formatting
1 parent bacd750 commit 03eb710

5 files changed

Lines changed: 112 additions & 15 deletions

File tree

src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/DeterministicTenantSecurityClient.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ public DeterministicTenantSecurityClient(String tspDomain, String apiKey) throws
5555
}
5656

5757
/**
58-
* Constructor for DeterministicTenantSecurityClient class that allows for modifying the random
59-
* number generator used for encryption. Sets a default connect and read timeout of 20s.
58+
* Constructor for DeterministicTenantSecurityClient class that allows for configuring the request
59+
* and AES thread pool sizes. Sets a default connect and read timeout of 20s.
6060
*
6161
* @param tspDomain Domain where the Tenant Security Proxy is running.
6262
* @param apiKey Key to use for requests to the Tenant Security Proxy.
@@ -70,8 +70,9 @@ public DeterministicTenantSecurityClient(String tspDomain, String apiKey, int re
7070
}
7171

7272
/**
73-
* Constructor for DeterministicTenantSecurityClient class that allows for modifying the random
74-
* number generator used for encryption.
73+
* Constructor for DeterministicTenantSecurityClient class that allows for configuring the request
74+
* and AES thread pool sizes and a shared timeout. The provided timeout is used for both the
75+
* connect and read timeouts; use the 6-argument constructor to set them independently.
7576
*
7677
* @param tspDomain Domain where the Tenant Security Proxy is running.
7778
* @param apiKey Key to use for requests to the Tenant Security Proxy.
@@ -82,6 +83,23 @@ public DeterministicTenantSecurityClient(String tspDomain, String apiKey, int re
8283
*/
8384
public DeterministicTenantSecurityClient(String tspDomain, String apiKey, int requestThreadSize,
8485
int aesThreadSize, int timeout) throws Exception {
86+
this(tspDomain, apiKey, requestThreadSize, aesThreadSize, timeout, timeout);
87+
}
88+
89+
/**
90+
* Constructor for DeterministicTenantSecurityClient class with independent connect and read
91+
* timeouts.
92+
*
93+
* @param tspDomain Domain where the Tenant Security Proxy is running.
94+
* @param apiKey Key to use for requests to the Tenant Security Proxy.
95+
* @param requestThreadSize Number of threads to use for fixed-size web request thread pool
96+
* @param aesThreadSize Number of threads to use for fixed-size AES operations threadpool
97+
* @param readTimeout Request to TSP read timeout in ms.
98+
* @param connectTimeout Request to TSP connect timeout in ms.
99+
* @throws Exception If the provided domain is invalid.
100+
*/
101+
public DeterministicTenantSecurityClient(String tspDomain, String apiKey, int requestThreadSize,
102+
int aesThreadSize, int readTimeout, int connectTimeout) throws Exception {
85103
// Use the URL class to validate the form of the provided TSP domain URL
86104
new URL(tspDomain);
87105
if (apiKey == null || apiKey.isEmpty()) {
@@ -95,14 +113,18 @@ public DeterministicTenantSecurityClient(String tspDomain, String apiKey, int re
95113
throw new IllegalArgumentException(
96114
"Value provided for AES threadpool size must be greater than 0!");
97115
}
98-
if (timeout < 1) {
99-
throw new IllegalArgumentException("Value provided for timeout must be greater than 0!");
116+
if (readTimeout < 1) {
117+
throw new IllegalArgumentException("Value provided for readTimeout must be greater than 0!");
118+
}
119+
if (connectTimeout < 1) {
120+
throw new IllegalArgumentException(
121+
"Value provided for connectTimeout must be greater than 0!");
100122
}
101123

102124
this.encryptionExecutor = Executors.newFixedThreadPool(aesThreadSize);
103125

104-
this.encryptionService =
105-
new TenantSecurityRequest(tspDomain, apiKey, requestThreadSize, timeout);
126+
this.encryptionService = new TenantSecurityRequest(tspDomain, apiKey, requestThreadSize,
127+
readTimeout, connectTimeout);
106128
}
107129

108130
DeterministicTenantSecurityClient(ExecutorService aesThreadExecutor,

src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityClient.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,16 @@ private TenantSecurityClient(Builder builder) throws Exception {
5959
if (builder.timeout < 1) {
6060
throw new IllegalArgumentException("Value provided for timeout must be greater than 0!");
6161
}
62+
if (builder.connectTimeout != null && builder.connectTimeout < 1) {
63+
throw new IllegalArgumentException(
64+
"Value provided for connectTimeout must be greater than 0!");
65+
}
66+
int effectiveConnectTimeout =
67+
builder.connectTimeout != null ? builder.connectTimeout : builder.timeout;
6268

6369
this.encryptionExecutor = Executors.newFixedThreadPool(builder.aesThreadSize);
6470
this.encryptionService = new TenantSecurityRequest(builder.tspDomain, builder.apiKey,
65-
builder.requestThreadSize, builder.timeout);
71+
builder.requestThreadSize, builder.timeout, effectiveConnectTimeout);
6672
this.deterministicClient =
6773
new DeterministicTenantSecurityClient(this.encryptionExecutor, this.encryptionService);
6874

@@ -113,6 +119,9 @@ public static class Builder {
113119
private int requestThreadSize = DEFAULT_REQUEST_THREADPOOL_SIZE;
114120
private int aesThreadSize = DEFAULT_AES_THREADPOOL_SIZE;
115121
private int timeout = DEFAULT_TIMEOUT_MS;
122+
// When left null, the connect timeout follows `timeout` to preserve pre-existing behavior
123+
// where a single setting controlled both.
124+
private Integer connectTimeout = null;
116125
private boolean allowInsecureHttp = false;
117126
// If this is null when build is called we set it to the default. Don't set it here
118127
// in case the default isn't available on their OS.
@@ -155,16 +164,34 @@ public Builder aesThreadSize(int size) {
155164
}
156165

157166
/**
158-
* Sets the timeout in milliseconds for communicating with the TSP.
167+
* Sets the read timeout in milliseconds for requests to the TSP, and serves as the fallback for
168+
* the connect timeout when {@link #connectTimeoutMs(int)} is not called. In other words, unless
169+
* {@link #connectTimeoutMs(int)} is also set, the value provided here controls both the connect
170+
* and the read timeout (preserving the single-timeout behavior of earlier releases). To
171+
* configure them independently, call {@link #connectTimeoutMs(int)} as well.
159172
*
160-
* @param timeout Timeout in milliseconds for the TSP requests.
173+
* @param timeout Read timeout in milliseconds for TSP requests.
161174
* @return The builder
162175
*/
163176
public Builder timeoutMs(int timeout) {
164177
this.timeout = timeout;
165178
return this;
166179
}
167180

181+
/**
182+
* Sets the connect timeout in milliseconds for requests to the TSP. When not set, the connect
183+
* timeout follows the value provided to {@link #timeoutMs(int)}. Configure this independently
184+
* to, for example, fail fast on unreachable hosts while still allowing a longer read timeout
185+
* for slow responses.
186+
*
187+
* @param connectTimeout Connect timeout in milliseconds for TSP requests.
188+
* @return The builder
189+
*/
190+
public Builder connectTimeoutMs(int connectTimeout) {
191+
this.connectTimeout = connectTimeout;
192+
return this;
193+
}
194+
168195
/**
169196
* Sets the random number generator. This should be set with care as the generator must be
170197
* cryptographically secure. Defaults to "NativePRNGNonBlocking"

src/main/java/com/ironcorelabs/tenantsecurity/kms/v1/TenantSecurityRequest.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,18 @@ private static String stripTrailingSlash(String s) {
5757
private final GenericUrl deriveKeyEndpoint;
5858
private final GenericUrl reportOperationsEndpoint;
5959
private final HttpRequestFactory requestFactory;
60-
private final int timeout;
60+
private final int readTimeout;
61+
private final int connectTimeout;
6162

6263
// TSC version that will be sent to the TSP.
6364
static final String sdkVersion = "8.1.0-SNAPSHOT";
6465

6566
TenantSecurityRequest(String tspDomain, String apiKey, int requestThreadSize, int timeout) {
67+
this(tspDomain, apiKey, requestThreadSize, timeout, timeout);
68+
}
69+
70+
TenantSecurityRequest(String tspDomain, String apiKey, int requestThreadSize, int readTimeout,
71+
int connectTimeout) {
6672
HttpHeaders headers = new HttpHeaders();
6773
// Instead of calling `put` which causes issues with older versions of the google http library
6874
// call the set for each of these.
@@ -84,7 +90,8 @@ private static String stripTrailingSlash(String s) {
8490

8591
this.webRequestExecutor = Executors.newFixedThreadPool(requestThreadSize);
8692
this.requestFactory = provideHttpRequestFactory(requestThreadSize, requestThreadSize);
87-
this.timeout = timeout;
93+
this.readTimeout = readTimeout;
94+
this.connectTimeout = connectTimeout;
8895
}
8996

9097
public void close() throws IOException {
@@ -101,8 +108,8 @@ private HttpRequest getApiRequest(Map<String, Object> postData, GenericUrl endpo
101108
// Clone the headers on use. Otherwise Google will keep appending their custom
102109
// user agent string and it will grow big enough to cause header overflow
103110
// errors.
104-
.setHeaders(this.httpHeaders.clone()).setReadTimeout(this.timeout)
105-
.setConnectTimeout(this.timeout)
111+
.setHeaders(this.httpHeaders.clone()).setReadTimeout(this.readTimeout)
112+
.setConnectTimeout(this.connectTimeout)
106113
// We want to parse out error codes, so don't throw when we get a non-200
107114
// response code
108115
.setThrowExceptionOnExecuteError(false);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.ironcorelabs.tenantsecurity.kms.v1;
2+
3+
4+
import org.testng.annotations.Test;
5+
6+
@Test(groups = {"unit"})
7+
public class DeterministicClientTest {
8+
@Test(expectedExceptions = IllegalArgumentException.class)
9+
public void invalidReadTimeoutOnSixArgConstructor() throws Exception {
10+
new DeterministicTenantSecurityClient("https://localhost", "apiKey", 1, 1, 0, 1000).close();
11+
}
12+
13+
@Test(expectedExceptions = IllegalArgumentException.class)
14+
public void invalidConnectTimeoutOnSixArgConstructor() throws Exception {
15+
new DeterministicTenantSecurityClient("https://localhost", "apiKey", 1, 1, 1000, 0).close();
16+
}
17+
18+
// Sanity check that the 6-arg constructor builds successfully with distinct read and connect
19+
// timeouts.
20+
public void independentReadAndConnectTimeoutsOnSixArgConstructor() throws Exception {
21+
new DeterministicTenantSecurityClient("https://localhost", "apiKey", 1, 1, 30000, 2000).close();
22+
}
23+
}

src/test/java/com/ironcorelabs/tenantsecurity/kms/v1/KMSClientTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,22 @@ public void invalidCryptoThreadpoolSize() throws Exception {
4141
new TenantSecurityClient.Builder("https://localhost", "apiKey").aesThreadSize(0).build()
4242
.close();
4343
}
44+
45+
@Test(expectedExceptions = IllegalArgumentException.class)
46+
public void invalidConnectTimeoutZero() throws Exception {
47+
new TenantSecurityClient.Builder("https://localhost", "apiKey").connectTimeoutMs(0).build()
48+
.close();
49+
}
50+
51+
@Test(expectedExceptions = IllegalArgumentException.class)
52+
public void invalidConnectTimeoutNegative() throws Exception {
53+
new TenantSecurityClient.Builder("https://localhost", "apiKey").connectTimeoutMs(-1).build()
54+
.close();
55+
}
56+
57+
// Sanity check that an independently-configured connect timeout builds successfully.
58+
public void independentConnectAndReadTimeouts() throws Exception {
59+
new TenantSecurityClient.Builder("https://localhost", "apiKey").timeoutMs(30000)
60+
.connectTimeoutMs(2000).build().close();
61+
}
4462
}

0 commit comments

Comments
 (0)