Skip to content

Commit 82a2ac6

Browse files
committed
feat: improve behavior of HTTP redirects
This commit introduces a new okhttp interceptor that will implement customized behavior for HTTP redirects. Specifically, "safe" headers will be included in a redirected request if the original and redirected hosts are the same, or are both within IBM's "cloud.ibm.com" domain. Signed-off-by: Phil Adams <[email protected]>
1 parent a0c5b26 commit 82a2ac6

File tree

7 files changed

+767
-31
lines changed

7 files changed

+767
-31
lines changed

Diff for: .travis.yml

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ cache:
1616
directories:
1717
- "$HOME/.m2"
1818

19+
addons:
20+
hosts:
21+
- region1.cloud.ibm.com
22+
- region1.notcloud.ibm.com
23+
- region2.cloud.ibm.com
24+
- region2.notcloud.ibm.com
25+
1926
env:
2027
global:
2128
- MVN_ARGS="--settings build/.travis.settings.xml"

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

+55-27
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import com.ibm.cloud.sdk.core.http.HttpConfigOptions.LoggingLevel;
1717
import com.ibm.cloud.sdk.core.http.gzip.GzipRequestInterceptor;
1818
import com.ibm.cloud.sdk.core.service.security.DelegatingSSLSocketFactory;
19+
import com.ibm.cloud.sdk.core.util.EnvironmentUtils;
20+
1921
import okhttp3.Authenticator;
2022
import okhttp3.ConnectionSpec;
2123
import okhttp3.Interceptor;
@@ -179,6 +181,7 @@ private OkHttpClient configureHttpClient() {
179181
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
180182

181183
addCookieJar(builder);
184+
addRedirectInterceptor(builder);
182185

183186
builder.connectTimeout(60, TimeUnit.SECONDS);
184187
builder.writeTimeout(60, TimeUnit.SECONDS);
@@ -197,11 +200,34 @@ private OkHttpClient configureHttpClient() {
197200
*
198201
* @param builder the builder
199202
*/
200-
private void addCookieJar(final OkHttpClient.Builder builder) {
203+
private OkHttpClient.Builder addCookieJar(final OkHttpClient.Builder builder) {
201204
final CookieManager cookieManager = new CookieManager();
202205
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
203206

204207
builder.cookieJar(new ServiceCookieJar(cookieManager));
208+
return builder;
209+
}
210+
211+
/**
212+
* Adds the RedirectInterceptor to the client builder.
213+
* @param builder
214+
*/
215+
private OkHttpClient.Builder addRedirectInterceptor(final OkHttpClient.Builder builder) {
216+
String envvar = EnvironmentUtils.getenv("IBMCLOUD_BYPASS_CUSTOM_REDIRECTS");
217+
if (!Boolean.valueOf(envvar)) {
218+
// Disable the built-in "followRedirects" so that okhttp will ignore redirects.
219+
builder.followRedirects(false).followSslRedirects(false);
220+
221+
// Add our RedirectInterceptor to the client.
222+
builder.addInterceptor(new RedirectInterceptor());
223+
LOG.log(Level.FINE, "Registered the redirect interceptor.");
224+
} else {
225+
LOG.log(Level.FINE, "Bypassed redirect interceptor via options.");
226+
227+
// Set the defaults for these two options.
228+
builder.followRedirects(true).followSslRedirects(true);
229+
}
230+
return builder;
205231
}
206232

207233
/**
@@ -337,30 +363,6 @@ public static void setupTLSProtocol(final OkHttpClient.Builder builder) {
337363
}
338364
}
339365

340-
/**
341-
* Sets a new list of interceptors for the specified {@link OkHttpClient} instance by removing the specified
342-
* interceptor and returns a new instance with the interceptors configured as requested.
343-
*
344-
* @param client the {@link OkHttpClient} instance to remove the interceptors from
345-
* @param interceptorToRemove the class name of the interceptor to remove
346-
* @return the new {@link OkHttpClient} instance with the new list of interceptors
347-
*/
348-
private OkHttpClient reconfigureClientInterceptors(OkHttpClient client, String interceptorToRemove) {
349-
OkHttpClient.Builder builder = client.newBuilder();
350-
351-
if (!builder.interceptors().isEmpty()) {
352-
for (Iterator<Interceptor> iter = builder.interceptors().iterator(); iter.hasNext(); ) {
353-
Interceptor element = iter.next();
354-
if (interceptorToRemove.equals(element.getClass().getSimpleName())) {
355-
LOG.log(Level.FINE, "Removing interceptor " + element.getClass().getName() + " from http client instance.");
356-
iter.remove();
357-
}
358-
}
359-
}
360-
361-
return builder.build();
362-
}
363-
364366
/**
365367
* Sets a new list of interceptors for the specified {@link OkHttpClient} instance by removing any interceptors
366368
* that implement "interfaceToRemove".
@@ -370,7 +372,7 @@ private OkHttpClient reconfigureClientInterceptors(OkHttpClient client, String i
370372
* @return the new {@link OkHttpClient} instance with the updated list of interceptors
371373
*/
372374
private OkHttpClient reconfigureClientInterceptors(OkHttpClient client,
373-
Class<? extends Interceptor> interfaceToRemove) {
375+
Class<? extends Interceptor> interfaceToRemove) {
374376
OkHttpClient.Builder builder = client.newBuilder();
375377

376378
if (!builder.interceptors().isEmpty()) {
@@ -395,6 +397,7 @@ private OkHttpClient reconfigureClientInterceptors(OkHttpClient client,
395397
public OkHttpClient createHttpClient() {
396398
Builder builder = okHttpClient.newBuilder();
397399
addCookieJar(builder);
400+
addRedirectInterceptor(builder);
398401
return builder.build();
399402
}
400403

@@ -419,7 +422,10 @@ public OkHttpClient configureClient(HttpConfigOptions options) {
419422
* @return a new {@link OkHttpClient} instance with the specified options applied
420423
*/
421424
public OkHttpClient configureClient(OkHttpClient client, HttpConfigOptions options) {
425+
Boolean customRedirects = null;
422426
if (options != null) {
427+
customRedirects = options.getCustomRedirects();
428+
423429
if (options.shouldDisableSslVerification()) {
424430
client = disableSslVerification(client);
425431
}
@@ -443,6 +449,7 @@ public OkHttpClient configureClient(OkHttpClient client, HttpConfigOptions optio
443449
options.getAuthenticator());
444450
if (retryInterceptor != null) {
445451
client = client.newBuilder().addInterceptor(retryInterceptor).build();
452+
LOG.log(Level.FINE, "Registered the retry interceptor.");
446453
} else {
447454
LOG.log(Level.WARNING,
448455
"The retry interceptor factory returned a null IRetryInterceptor instance. Retries are disabled.");
@@ -453,14 +460,35 @@ public OkHttpClient configureClient(OkHttpClient client, HttpConfigOptions optio
453460
// Configure the GZIP interceptor.
454461
Boolean enableGzip = options.getGzipCompression();
455462
if (enableGzip != null) {
456-
client = reconfigureClientInterceptors(client, "GzipRequestInterceptor");
463+
client = reconfigureClientInterceptors(client, GzipRequestInterceptor.class);
457464
if (enableGzip.booleanValue()) {
458465
client = client.newBuilder()
459466
.addInterceptor(new GzipRequestInterceptor())
460467
.build();
468+
LOG.log(Level.FINE, "Registered the gzip interceptor.");
461469
}
462470
}
463471
}
472+
473+
// Configure the redirect interceptor.
474+
// We want "custom redirects" to be enabled by default (including a scenario
475+
// where "options" is null), hence the need to handle this feature outside the "if" above.
476+
// We want to register the interceptor if:
477+
// 1. options == null
478+
// 2. options != null and "custom redirects" is explicitly set to Boolean.TRUE
479+
//
480+
// We also want to allow "custom redirects" to be bypassed by setting an environment variable.
481+
// This may be only temporary until we are comfortable with this new function, but I'm
482+
// adding support for this environment variable as a quick fix in case there are "unforeseen
483+
// consequences" after this feature hits the street.
484+
// Example:
485+
// export IBMCLOUD_BYPASS_CUSTOM_REDIRECTS=true
486+
// Setting this environment variable will revert back to using the okhttp builtin support for redirects.
487+
client = reconfigureClientInterceptors(client, RedirectInterceptor.class);
488+
if (customRedirects == null || customRedirects.booleanValue()) {
489+
client = addRedirectInterceptor(client.newBuilder()).build();
490+
}
491+
464492
return client;
465493
}
466494
}

Diff for: src/main/java/com/ibm/cloud/sdk/core/http/HttpConfigOptions.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public enum LoggingLevel {
3939
private Boolean enableRetries;
4040
private int maxRetries;
4141
private int maxRetryInterval;
42+
private Boolean enableCustomRedirects = Boolean.TRUE;
4243
private Proxy proxy;
4344
private Authenticator proxyAuthenticator;
4445
private LoggingLevel loggingLevel;
@@ -66,6 +67,10 @@ public int getMaxRetryInterval() {
6667
return this.maxRetryInterval;
6768
}
6869

70+
public Boolean getCustomRedirects() {
71+
return this.enableCustomRedirects;
72+
}
73+
6974
public Proxy getProxy() {
7075
return this.proxy;
7176
}
@@ -98,6 +103,7 @@ public static class Builder {
98103
private Boolean enableRetries;
99104
private int maxRetries;
100105
private int maxRetryInterval;
106+
private Boolean enableCustomRedirects = Boolean.TRUE;
101107
private Proxy proxy;
102108
private Authenticator proxyAuthenticator;
103109
private LoggingLevel loggingLevel;
@@ -122,10 +128,10 @@ public Builder disableSslVerification(boolean disableSslVerification) {
122128
}
123129

124130
/**
125-
* Sets flag to enable gzip compression of request bodies during HTTP requests. This should ONLY be used if truly
131+
* Sets flag to enable compression of request bodies during HTTP requests. This should ONLY be used if truly
126132
* intended, as many webservers can't handle this.
127133
*
128-
* @param enableGzipCompression whether to disable SSL verification or not
134+
* @param enableGzipCompression whether to perform gzip-compression of request bodies or not
129135
* @return the builder
130136
*/
131137
public Builder enableGzipCompression(Boolean enableGzipCompression) {
@@ -178,6 +184,17 @@ public Builder disableRetries() {
178184
return this;
179185
}
180186

187+
/**
188+
* Sets flag to enable our custom redirect behavior.
189+
*
190+
* @param enableCustomRedirects whether to enable our custom redirect behavior or not
191+
* @return the builder
192+
*/
193+
public Builder enableCustomRedirects(Boolean enableCustomRedirects) {
194+
this.enableCustomRedirects = enableCustomRedirects;
195+
return this;
196+
}
197+
181198
/**
182199
* Sets HTTP proxy to be used by connections with the current client.
183200
*
@@ -218,6 +235,7 @@ private HttpConfigOptions(Builder builder) {
218235
this.enableRetries = builder.enableRetries;
219236
this.maxRetries = builder.maxRetries;
220237
this.maxRetryInterval = builder.maxRetryInterval;
238+
this.enableCustomRedirects = builder.enableCustomRedirects;
221239
this.proxy = builder.proxy;
222240
this.proxyAuthenticator = builder.proxyAuthenticator;
223241
this.loggingLevel = builder.loggingLevel;

0 commit comments

Comments
 (0)