Skip to content

Commit f81a740

Browse files
committed
feat: allow user-specified default OkHttpClient instance
This commit modifies the java core so that users can now specify a fully-configured OkHttpClient instance to be used by service and authenticator instances: 1. The HttpClientSingleton class now has methods setHttpClient() and getHttpClient(). These methods can be used to set and get the default client configuration from which other OkHttpClient instances are created. 2. The TokenRequestBasedAuthenticator base class now has methods setClient() and getClient(). These methods can be used to set and get the specific OkHttpClient instance that should be used by an authenticator when interacting with the token service. These new methods are inherited by each of the request-based authenticator implementation classes: ContainerAuthenticator, CloudPakForDataAuthenticator, CloudPakForDataServiceAuthenticator, CloudPakForDataServiceInstanceAuthenticator, IamAuthenticator, and VpcInstanceAuthenticator.
1 parent 60d896f commit f81a740

File tree

9 files changed

+233
-26
lines changed

9 files changed

+233
-26
lines changed

src/main/java/com/ibm/cloud/sdk/core/http/HttpClientSingleton.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,25 @@ protected HttpClientSingleton() {
153153
}
154154

155155
/**
156-
* Configures the HTTP client.
156+
* Returns the current {@link OkHttpClient} instance held by this singleton.
157+
* This is the client instance that is used as a default configuration from which other client instances are built.
158+
* @return the current OkHttpClient instance
159+
*/
160+
public OkHttpClient getHttpClient() {
161+
return this.okHttpClient;
162+
}
163+
164+
/**
165+
* Sets the current {@link OkHttpClient} instance held by this singleton.
166+
* This is the client instance that is used as a default configuration from which other client instances are built.
167+
* @param client the new OkHttpClient instance to use as a default client configuration
168+
*/
169+
public void setHttpClient(OkHttpClient client) {
170+
this.okHttpClient = client;
171+
}
172+
173+
/**
174+
* Configures a new HTTP client instance.
157175
*
158176
* @return the HTTP client
159177
*/
@@ -290,7 +308,7 @@ private OkHttpClient setLoggingLevel(OkHttpClient client, LoggingLevel loggingLe
290308
*
291309
* @param builder the {@link OkHttpClient} builder.
292310
*/
293-
private void setupTLSProtocol(final OkHttpClient.Builder builder) {
311+
public static void setupTLSProtocol(final OkHttpClient.Builder builder) {
294312
try {
295313
TrustManagerFactory trustManagerFactory =
296314
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

src/main/java/com/ibm/cloud/sdk/core/security/TokenRequestBasedAuthenticator.java

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* (C) Copyright IBM Corp. 2019, 2021.
2+
* (C) Copyright IBM Corp. 2019, 2022.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at
@@ -40,16 +40,20 @@
4040
* <ul>
4141
* <li>disableSSLVerification - a flag that indicates whether or not client-side SSL verification should be disabled.
4242
* <li>headers - a Map of keys/values that will be set as HTTP headers on requests sent to the token service.
43-
* <li>proxy - a java.net.Proxy instance that will be set on the Client object used to interact with the token service.
44-
* <li>proxyAuthenticator - an okhttp3.Authenticator instance to be set on the Client object used to interact wth
45-
* the token service.
43+
* <li>proxy - a java.net.Proxy instance that will be set on the OkHttpClient instance used to
44+
* interact with the token service.
45+
* <li>proxyAuthenticator - an okhttp3.Authenticator instance to be set on the OkHttpClient instance used to
46+
* interact with the token service.
47+
* <li>client - a fully-configured OkHttpClient instance to be used to interact with the token service.
4648
* </ul>
4749
*/
4850
public abstract class TokenRequestBasedAuthenticator<T extends AbstractToken, R extends TokenServerResponse>
4951
extends AuthenticatorBase implements Authenticator {
5052

5153
private static final Logger logger = Logger.getLogger(TokenRequestBasedAuthenticator.class.getName());
5254

55+
protected OkHttpClient client;
56+
5357
// Configuration properties that are common to all subclasses.
5458
private boolean disableSSLVerification;
5559
private Map<String, String> headers;
@@ -67,6 +71,47 @@ private void setTokenData(T tokenData) {
6771
this.tokenData = tokenData;
6872
}
6973

74+
/**
75+
* Sets the OkHttpClient instance to be used when interacting with the token service.
76+
* @param client the OkHttpClient instance to use
77+
*/
78+
public void setClient(OkHttpClient client) {
79+
this.client = client;
80+
}
81+
82+
/**
83+
* Returns the OkHttpClient instance to be used when interacting with the token service.
84+
* @return the client instance or null if a client insance has not yet been set
85+
*/
86+
public OkHttpClient getClient() {
87+
return this.client;
88+
}
89+
90+
/**
91+
* Returns a properly-configured OkHttpClient instance to use when interacting with the token service.
92+
* This function is different from "getClient()" in that it will configure and save
93+
* a client instance if one has not yet been setup for "this".
94+
* @return a non-null, configured OkHttpClient instance
95+
*/
96+
protected synchronized OkHttpClient getConfiguredClient() {
97+
if (this.client == null) {
98+
OkHttpClient defaultClient = HttpClientSingleton.getInstance().getHttpClient();
99+
100+
HttpConfigOptions.Builder clientOptions = new HttpConfigOptions.Builder()
101+
.disableSslVerification(this.disableSSLVerification)
102+
.proxy(this.proxy)
103+
.proxyAuthenticator(this.proxyAuthenticator);
104+
105+
if (logger.isLoggable(Level.FINE)) {
106+
clientOptions.loggingLevel(LoggingLevel.BODY);
107+
}
108+
109+
this.client = HttpClientSingleton.getInstance().configureClient(defaultClient, clientOptions.build());
110+
}
111+
112+
return this.client;
113+
}
114+
70115
/**
71116
* Validates the configuration properties associated with the Authenticator.
72117
* Each concrete subclass must implement this method.
@@ -136,7 +181,7 @@ public Proxy getProxy() {
136181

137182
/**
138183
* Sets a Proxy object on this Authenticator.
139-
* @param proxy the proxy object to be associated with the Client used to interact wth the token service.
184+
* @param proxy the proxy object to be associated with the Client used to interact with the token service.
140185
*/
141186
public void setProxy(Proxy proxy) {
142187
this.proxy = proxy;
@@ -228,7 +273,7 @@ public void run() {
228273
}
229274
}
230275

231-
// Return the access token from our stored tokenData object.
276+
// Return the access token from our stored tokenData object.object
232277
token = tokenData.getAccessToken();
233278

234279
return token;
@@ -256,18 +301,7 @@ protected R invokeRequest(final RequestBuilder requestBuilder, final Class<? ext
256301
// Allocate the response.
257302
final Object[] responseObj = new Object[1];
258303

259-
// Set up the Client we'll use to invoke the request.
260-
final HttpConfigOptions.Builder clientOptions = new HttpConfigOptions.Builder()
261-
.disableSslVerification(this.disableSSLVerification)
262-
.proxy(this.proxy)
263-
.proxyAuthenticator(this.proxyAuthenticator);
264-
265-
// Enable request/response logging.
266-
if (logger.isLoggable(Level.FINE)) {
267-
clientOptions.loggingLevel(LoggingLevel.BODY);
268-
}
269-
270-
final OkHttpClient client = HttpClientSingleton.getInstance().configureClient(clientOptions.build());
304+
final OkHttpClient client = getConfiguredClient();
271305

272306
final Request request = requestBuilder.build();
273307

src/test/java/com/ibm/cloud/sdk/core/security/VpcInstanceAuthenticatorTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.testng.Assert.assertTrue;
2121
import static org.testng.Assert.fail;
2222

23+
import java.util.Arrays;
2324
import java.util.HashMap;
2425
import java.util.Map;
2526
import java.util.concurrent.TimeUnit;
@@ -38,7 +39,9 @@
3839
import com.ibm.cloud.sdk.core.test.BaseServiceUnitTest;
3940
import com.ibm.cloud.sdk.core.util.Clock;
4041

42+
import okhttp3.ConnectionSpec;
4143
import okhttp3.Headers;
44+
import okhttp3.OkHttpClient;
4245
import okhttp3.Request;
4346
import okhttp3.mockwebserver.RecordedRequest;
4447

@@ -404,6 +407,19 @@ public void testAuthenticateNewAndCachedToken() throws Throwable {
404407
.url(url)
405408
.build();
406409

410+
// Create a custom client and set it on the authenticator.
411+
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
412+
.allEnabledCipherSuites()
413+
.build();
414+
OkHttpClient client = new OkHttpClient.Builder()
415+
.connectTimeout(30, TimeUnit.SECONDS)
416+
.writeTimeout(120, TimeUnit.SECONDS)
417+
.readTimeout(120, TimeUnit.SECONDS)
418+
.connectionSpecs(Arrays.asList(spec, ConnectionSpec.CLEARTEXT))
419+
.build();
420+
authenticator.setClient(client);
421+
assertEquals(authenticator.getClient(), client);
422+
407423
Request.Builder requestBuilder = new Request.Builder().url("https://test.com");
408424

409425
// Set mock server responses.
@@ -422,6 +438,9 @@ public void testAuthenticateNewAndCachedToken() throws Throwable {
422438
requestBuilder = new Request.Builder().url("https://test.com");
423439
authenticator.authenticate(requestBuilder);
424440
verifyAuthHeader(requestBuilder, "Bearer " + vpcIamAccessTokenResponse1.getAccessToken());
441+
442+
// Verify that the authenticator is still using the same client instance that we set before.
443+
assertEquals(authenticator.getClient(), client);
425444
}
426445

427446
@Test

src/test/java/com/ibm/cloud/sdk/core/test/http/HttpClientSingletonTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
package com.ibm.cloud.sdk.core.test.http;
1515

1616
import static org.testng.Assert.assertEquals;
17+
import static org.testng.Assert.assertNotEquals;
18+
import static org.testng.Assert.assertNotNull;
1719
import static org.testng.Assert.assertTrue;
1820

1921
import java.io.IOException;
2022
import java.util.ArrayList;
2123
import java.util.Arrays;
2224
import java.util.Iterator;
2325
import java.util.List;
26+
import java.util.concurrent.TimeUnit;
2427

2528
import javax.net.ssl.SSLSocket;
2629

@@ -126,4 +129,45 @@ public void testSetRetryStrategy() {
126129
assertEquals(testRetryInterceptors, 1);
127130
assertEquals(otherRetryInterceptors, 0);
128131
}
132+
133+
@Test
134+
public void testSetClient() {
135+
136+
// Create a custom client and set it on the HttpClientSingleton
137+
// as the default for configuring other clients.
138+
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
139+
.allEnabledCipherSuites()
140+
.build();
141+
OkHttpClient client = new OkHttpClient.Builder()
142+
.connectTimeout(30, TimeUnit.SECONDS)
143+
.writeTimeout(120, TimeUnit.SECONDS)
144+
.readTimeout(120, TimeUnit.SECONDS)
145+
.connectionSpecs(Arrays.asList(spec, ConnectionSpec.CLEARTEXT))
146+
.build();
147+
HttpClientSingleton cs = HttpClientSingleton.getInstance();
148+
assertNotNull(cs.getHttpClient());
149+
150+
// Verify set/get.
151+
cs.setHttpClient(client);
152+
assertEquals(cs.getHttpClient(), client);
153+
154+
// Verify that "client" is used to configure other clients.
155+
// To verify this, we'll just configure a new client with SSL verification disabled,
156+
// and its timeout properties should be the same as what we set above on our original client.
157+
HttpConfigOptions options = new HttpConfigOptions.Builder()
158+
.disableSslVerification(true)
159+
.build();
160+
OkHttpClient client2 = cs.configureClient(options);
161+
162+
// Verify that the "configureClient()" call above set a new default client instance in the singleton.
163+
assertNotEquals(cs.getHttpClient(), client);
164+
165+
// Verify that the new client is a different instance than our original client.
166+
assertNotEquals(client2, client);
167+
168+
// Verify that the new client "inherits" the config from our original client.
169+
assertEquals(client2.connectTimeoutMillis(), 30 * 1000);
170+
assertEquals(client2.writeTimeoutMillis(), 120 * 1000);
171+
assertEquals(client2.readTimeoutMillis(), 120 * 1000);
172+
}
129173
}

src/test/java/com/ibm/cloud/sdk/core/test/security/ContainerAuthenticatorTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.testng.Assert.fail;
2323

2424
import java.nio.file.NoSuchFileException;
25+
import java.util.Arrays;
2526
import java.util.HashMap;
2627
import java.util.Map;
2728
import java.util.concurrent.TimeUnit;
@@ -45,7 +46,9 @@
4546
import com.ibm.cloud.sdk.core.test.BaseServiceUnitTest;
4647
import com.ibm.cloud.sdk.core.util.Clock;
4748

49+
import okhttp3.ConnectionSpec;
4850
import okhttp3.Headers;
51+
import okhttp3.OkHttpClient;
4952
import okhttp3.Request;
5053
import okhttp3.mockwebserver.RecordedRequest;
5154

@@ -249,6 +252,19 @@ public void testAuthenticateNewAndStoredToken() throws Throwable {
249252
.url(url)
250253
.build();
251254

255+
// Create a custom client and set it on the authenticator.
256+
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
257+
.allEnabledCipherSuites()
258+
.build();
259+
OkHttpClient client = new OkHttpClient.Builder()
260+
.connectTimeout(30, TimeUnit.SECONDS)
261+
.writeTimeout(120, TimeUnit.SECONDS)
262+
.readTimeout(120, TimeUnit.SECONDS)
263+
.connectionSpecs(Arrays.asList(spec, ConnectionSpec.CLEARTEXT))
264+
.build();
265+
authenticator.setClient(client);
266+
assertEquals(authenticator.getClient(), client);
267+
252268
Request.Builder requestBuilder = new Request.Builder().url("https://test.com");
253269

254270
// Set mock server response.
@@ -280,6 +296,9 @@ public void testAuthenticateNewAndStoredToken() throws Throwable {
280296
requestBuilder = new Request.Builder().url("https://test.com");
281297
authenticator.authenticate(requestBuilder);
282298
verifyAuthHeader(requestBuilder, "Bearer " + tokenData1.getAccessToken());
299+
300+
// Verify that the authenticator is still using the same client instance that we set before.
301+
assertEquals(authenticator.getClient(), client);
283302
}
284303

285304
@Test

src/test/java/com/ibm/cloud/sdk/core/test/security/Cp4dAuthenticatorTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.testng.Assert.assertTrue;
2222
import static org.testng.Assert.fail;
2323

24+
import java.util.Arrays;
2425
import java.util.HashMap;
2526
import java.util.Map;
2627
import java.util.concurrent.TimeUnit;
@@ -39,7 +40,9 @@
3940
import com.ibm.cloud.sdk.core.test.BaseServiceUnitTest;
4041
import com.ibm.cloud.sdk.core.util.Clock;
4142

43+
import okhttp3.ConnectionSpec;
4244
import okhttp3.Headers;
45+
import okhttp3.OkHttpClient;
4346
import okhttp3.Request;
4447
import okhttp3.mockwebserver.RecordedRequest;
4548

@@ -312,17 +315,32 @@ public void testAuthenticateNewAndStoredToken() {
312315
.password(testPassword)
313316
.disableSSLVerification(true)
314317
.build();
315-
Request.Builder requestBuilder;
318+
319+
// Create a custom client and set it on the authenticator.
320+
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
321+
.allEnabledCipherSuites()
322+
.build();
323+
OkHttpClient client = new OkHttpClient.Builder()
324+
.connectTimeout(30, TimeUnit.SECONDS)
325+
.writeTimeout(120, TimeUnit.SECONDS)
326+
.readTimeout(120, TimeUnit.SECONDS)
327+
.connectionSpecs(Arrays.asList(spec, ConnectionSpec.CLEARTEXT))
328+
.build();
329+
authenticator.setClient(client);
330+
assertEquals(authenticator.getClient(), client);
316331

317332
// Authenticator should request new, valid token.
318-
requestBuilder = new Request.Builder().url("https://test.com");
333+
Request.Builder requestBuilder = new Request.Builder().url("https://test.com");
319334
authenticator.authenticate(requestBuilder);
320335
verifyAuthHeader(requestBuilder, "Bearer " + tokenData.getToken());
321336

322337
// Authenticator should just return the same token this time since we have a valid one stored.
323338
requestBuilder = new Request.Builder().url("https://test.com");
324339
authenticator.authenticate(requestBuilder);
325340
verifyAuthHeader(requestBuilder, "Bearer " + tokenData.getToken());
341+
342+
// Verify that the authenticator is still using the same client instance that we set before.
343+
assertEquals(authenticator.getClient(), client);
326344
}
327345

328346
@Test

0 commit comments

Comments
 (0)