Skip to content

Commit 42d6869

Browse files
committed
New proxy config integration
1 parent 4674e27 commit 42d6869

6 files changed

Lines changed: 146 additions & 39 deletions

File tree

src/main/java/io/split/android/client/SplitClientConfig.java

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static io.split.android.client.utils.Utils.checkNotNull;
55

66
import androidx.annotation.NonNull;
7+
import androidx.annotation.Nullable;
78

89
import java.net.URI;
910
import java.util.concurrent.TimeUnit;
@@ -17,6 +18,7 @@
1718
import io.split.android.client.network.CertificatePinningConfiguration;
1819
import io.split.android.client.network.DevelopmentSslConfig;
1920
import io.split.android.client.network.HttpProxy;
21+
import io.split.android.client.network.ProxyConfiguration;
2022
import io.split.android.client.network.SplitAuthenticator;
2123
import io.split.android.client.service.ServiceConstants;
2224
import io.split.android.client.service.impressions.ImpressionsMode;
@@ -132,6 +134,8 @@ public class SplitClientConfig {
132134
private final long mImpressionsDedupeTimeInterval;
133135
@NonNull
134136
private final RolloutCacheConfiguration mRolloutCacheConfiguration;
137+
@Nullable
138+
private final ProxyConfiguration mProxyConfiguration;
135139

136140
public static Builder builder() {
137141
return new Builder();
@@ -187,7 +191,8 @@ private SplitClientConfig(String endpoint,
187191
long observerCacheExpirationPeriod,
188192
CertificatePinningConfiguration certificatePinningConfiguration,
189193
long impressionsDedupeTimeInterval,
190-
RolloutCacheConfiguration rolloutCacheConfiguration) {
194+
@NonNull RolloutCacheConfiguration rolloutCacheConfiguration,
195+
@Nullable ProxyConfiguration proxyConfiguration) {
191196
mEndpoint = endpoint;
192197
mEventsEndpoint = eventsEndpoint;
193198
mTelemetryEndpoint = telemetryEndpoint;
@@ -246,6 +251,7 @@ private SplitClientConfig(String endpoint,
246251
mCertificatePinningConfiguration = certificatePinningConfiguration;
247252
mImpressionsDedupeTimeInterval = impressionsDedupeTimeInterval;
248253
mRolloutCacheConfiguration = rolloutCacheConfiguration;
254+
mProxyConfiguration = proxyConfiguration;
249255
}
250256

251257
public String trafficType() {
@@ -436,7 +442,9 @@ public boolean persistentAttributesEnabled() {
436442
return mIsPersistentAttributesEnabled;
437443
}
438444

439-
public int offlineRefreshRate() { return mOfflineRefreshRate; }
445+
public int offlineRefreshRate() {
446+
return mOfflineRefreshRate;
447+
}
440448

441449
public boolean shouldRecordTelemetry() {
442450
return mShouldRecordTelemetry;
@@ -446,7 +454,9 @@ public long telemetryRefreshRate() {
446454
return mTelemetryRefreshRate;
447455
}
448456

449-
public boolean syncEnabled() { return mSyncEnabled; }
457+
public boolean syncEnabled() {
458+
return mSyncEnabled;
459+
}
450460

451461
public int mtkPerPush() {
452462
return mMtkPerPush;
@@ -476,7 +486,9 @@ public int sseDisconnectionDelay() {
476486
return mSSEDisconnectionDelayInSecs;
477487
}
478488

479-
private void enableTelemetry() { mShouldRecordTelemetry = true; }
489+
private void enableTelemetry() {
490+
mShouldRecordTelemetry = true;
491+
}
480492

481493
public long observerCacheExpirationPeriod() {
482494
return Math.max(mImpressionsDedupeTimeInterval, mObserverCacheExpirationPeriod);
@@ -572,6 +584,8 @@ public static final class Builder {
572584

573585
private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build();
574586

587+
private ProxyConfiguration mProxyConfiguration = null;
588+
575589
public Builder() {
576590
mServiceEndpoints = ServiceEndpoints.builder().build();
577591
}
@@ -806,7 +820,9 @@ public Builder ready(int milliseconds) {
806820
*
807821
* @param proxyHost proxy URI
808822
* @return this builder
823+
* @deprecated use {@link #proxyConfiguration(ProxyConfiguration)}
809824
*/
825+
@Deprecated
810826
public Builder proxyHost(String proxyHost) {
811827
if (proxyHost != null && proxyHost.endsWith("/")) {
812828
mProxyHost = proxyHost.substring(0, proxyHost.length() - 1);
@@ -823,6 +839,7 @@ public Builder proxyHost(String proxyHost) {
823839
* @param proxyAuthenticator
824840
* @return this builder
825841
*/
842+
@Deprecated
826843
public Builder proxyAuthenticator(SplitAuthenticator proxyAuthenticator) {
827844
mProxyAuthenticator = proxyAuthenticator;
828845
return this;
@@ -1030,6 +1047,7 @@ public Builder offlineRefreshRate(int offlineRefreshRate) {
10301047
* <p>
10311048
* This is an ADVANCED parameter
10321049
* </p>
1050+
*
10331051
* @param telemetryRefreshRate Rate in seconds for telemetry refresh.
10341052
* @return This builder
10351053
* @default 3600 seconds
@@ -1101,10 +1119,9 @@ public Builder certificatePinningConfiguration(CertificatePinningConfiguration c
11011119
/**
11021120
* This configuration is used to control the size of the impressions deduplication window.
11031121
*
1122+
* @param impressionsDedupeTimeInterval The time interval in milliseconds.
11041123
* @Experimental This method is experimental and may change or be removed in future versions.
11051124
* To be used upon Split team recommendation.
1106-
*
1107-
* @param impressionsDedupeTimeInterval The time interval in milliseconds.
11081125
*/
11091126
@Deprecated
11101127
public Builder impressionsDedupeTimeInterval(long impressionsDedupeTimeInterval) {
@@ -1128,6 +1145,17 @@ public Builder rolloutCacheConfiguration(@NonNull RolloutCacheConfiguration roll
11281145
return this;
11291146
}
11301147

1148+
/**
1149+
* Sets the proxy configuration
1150+
*
1151+
* @param proxyConfiguration
1152+
* @return this builder
1153+
*/
1154+
public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
1155+
mProxyConfiguration = proxyConfiguration;
1156+
return this;
1157+
}
1158+
11311159
public SplitClientConfig build() {
11321160
Logger.instance().setLevel(mLogLevel);
11331161

@@ -1207,7 +1235,7 @@ public SplitClientConfig build() {
12071235
mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL;
12081236
}
12091237

1210-
HttpProxy proxy = parseProxyHost(mProxyHost);
1238+
HttpProxy proxy = parseProxyHost(mProxyHost, mProxyConfiguration);
12111239

12121240
return new SplitClientConfig(
12131241
mServiceEndpoints.getSdkEndpoint(),
@@ -1260,28 +1288,49 @@ public SplitClientConfig build() {
12601288
mObserverCacheExpirationPeriod,
12611289
mCertificatePinningConfiguration,
12621290
mImpressionsDedupeTimeInterval,
1263-
mRolloutCacheConfiguration);
1291+
mRolloutCacheConfiguration,
1292+
mProxyConfiguration);
12641293
}
12651294

1266-
private HttpProxy parseProxyHost(String proxyUri) {
1295+
private HttpProxy parseProxyHost(String proxyUri, ProxyConfiguration proxyConfiguration) {
1296+
// Use legacy proxy behavior if proxyConfiguration is null
1297+
if (proxyConfiguration == null) {
1298+
return legacyProxyBehavior(proxyUri);
1299+
}
1300+
1301+
// Initialize internal config with null url. This will be verified when building the factory.
1302+
HttpProxy.Builder builder = HttpProxy.newBuilder(null, -1);
1303+
if (proxyConfiguration.getUrl() != null) {
1304+
builder = HttpProxy.newBuilder(proxyConfiguration.getUrl().getHost(), proxyConfiguration.getUrl().getPort())
1305+
.mtlsAuth(proxyConfiguration.getClientCert(), proxyConfiguration.getClientPk())
1306+
.proxyCacert(proxyConfiguration.getCaCert())
1307+
.credentialsProvider(proxyConfiguration.getCredentialsProvider());
1308+
}
1309+
return builder.build();
1310+
}
1311+
1312+
@Nullable
1313+
private HttpProxy legacyProxyBehavior(String proxyUri) {
12671314
if (!Utils.isNullOrEmpty(proxyUri)) {
12681315
try {
12691316
String username = null;
12701317
String password = null;
12711318
URI uri = URI.create(proxyUri);
12721319
int port = uri.getPort() != -1 ? uri.getPort() : PROXY_PORT_DEFAULT;
12731320
String userInfo = uri.getUserInfo();
1274-
if(!Utils.isNullOrEmpty(userInfo)) {
1321+
if (!Utils.isNullOrEmpty(userInfo)) {
12751322
String[] userInfoComponents = userInfo.split(":");
1276-
if(userInfoComponents.length > 1) {
1323+
if (userInfoComponents.length > 1) {
12771324
username = userInfoComponents[0];
12781325
password = userInfoComponents[1];
12791326
}
12801327
}
12811328
String host = String.format("%s%s", uri.getHost(), uri.getPath());
1282-
return HttpProxy.newBuilder(host, port)
1283-
.basicAuth(username, password)
1284-
.build();
1329+
if (username != null && password != null) {
1330+
return HttpProxy.newBuilder(host, port).basicAuth(username, password).build();
1331+
} else {
1332+
return HttpProxy.newBuilder(host, port).build();
1333+
}
12851334
} catch (IllegalArgumentException e) {
12861335
Logger.e("Proxy URI not valid: " + e.getLocalizedMessage());
12871336
throw new IllegalArgumentException();

src/main/java/io/split/android/client/SplitFactoryBuilder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,9 @@ private static void checkPreconditions(@NonNull String sdkKey, @NonNull Key key,
9797
if (context == null) {
9898
throw new SplitInstantiationException("Could not instantiate SplitFactory. Context cannot be null");
9999
}
100+
101+
if (config.proxy() != null && config.proxy().getHost() == null) {
102+
throw new SplitInstantiationException("Could not instantiate SplitFactory. When configured, proxy host cannot be null");
103+
}
100104
}
101105
}

src/main/java/io/split/android/client/network/HttpProxy.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import androidx.annotation.NonNull;
66
import androidx.annotation.Nullable;
7+
78
import java.io.InputStream;
89

910
public class HttpProxy {
@@ -15,6 +16,7 @@ public class HttpProxy {
1516
private final @Nullable InputStream mClientCertStream;
1617
private final @Nullable InputStream mClientKeyStream;
1718
private final @Nullable InputStream mCaCertStream;
19+
private final @Nullable ProxyCredentialsProvider mCredentialsProvider;
1820

1921
private HttpProxy(Builder builder) {
2022
mHost = builder.mHost;
@@ -24,9 +26,10 @@ private HttpProxy(Builder builder) {
2426
mClientCertStream = builder.mClientCertStream;
2527
mClientKeyStream = builder.mClientKeyStream;
2628
mCaCertStream = builder.mCaCertStream;
29+
mCredentialsProvider = builder.mCredentialsProvider;
2730
}
2831

29-
public @NonNull String getHost() {
32+
public @Nullable String getHost() {
3033
return mHost;
3134
}
3235

@@ -54,7 +57,11 @@ public int getPort() {
5457
return mCaCertStream;
5558
}
5659

57-
public static Builder newBuilder(@NonNull String host, int port) {
60+
public @Nullable ProxyCredentialsProvider getCredentialsProvider() {
61+
return mCredentialsProvider;
62+
}
63+
64+
public static Builder newBuilder(@Nullable String host, int port) {
5865
return new Builder(host, port);
5966
}
6067

@@ -66,8 +73,10 @@ public static class Builder {
6673
private @Nullable InputStream mClientCertStream;
6774
private @Nullable InputStream mClientKeyStream;
6875
private @Nullable InputStream mCaCertStream;
76+
@Nullable
77+
private ProxyCredentialsProvider mCredentialsProvider;
6978

70-
private Builder(@NonNull String host, int port) {
79+
private Builder(@Nullable String host, int port) {
7180
checkNotNull(host);
7281
mHost = host;
7382
mPort = port;
@@ -84,10 +93,14 @@ public Builder proxyCacert(@NonNull InputStream caCertStream) {
8493
return this;
8594
}
8695

87-
public Builder mtlsAuth(@NonNull InputStream certStream, @NonNull InputStream keyStream, @NonNull InputStream caCertStream) {
88-
mClientCertStream = certStream;
96+
public Builder mtlsAuth(@NonNull InputStream clientCertStream, @NonNull InputStream keyStream) {
97+
mClientCertStream = clientCertStream;
8998
mClientKeyStream = keyStream;
90-
mCaCertStream = caCertStream;
99+
return this;
100+
}
101+
102+
public Builder credentialsProvider(@NonNull ProxyCredentialsProvider credentialsProvider) {
103+
mCredentialsProvider = credentialsProvider;
91104
return this;
92105
}
93106

src/test/java/io/split/android/client/SplitFactoryHelperTest.kt

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package io.split.android.client
22

33
import android.content.Context
44
import io.split.android.client.SplitFactoryHelper.Initializer.Listener
5+
import io.split.android.client.api.Key
56
import io.split.android.client.events.EventsManagerCoordinator
67
import io.split.android.client.events.SplitInternalEvent
8+
import io.split.android.client.exceptions.SplitInstantiationException
79
import io.split.android.client.lifecycle.SplitLifecycleManager
8-
import io.split.android.client.service.CleanUpDatabaseTask
10+
import io.split.android.client.network.ProxyConfiguration
911
import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor
1012
import io.split.android.client.service.executor.SplitTaskExecutionInfo
1113
import io.split.android.client.service.executor.SplitTaskExecutionListener
@@ -14,6 +16,8 @@ import io.split.android.client.service.executor.SplitTaskType
1416
import io.split.android.client.service.synchronizer.RolloutCacheManager
1517
import io.split.android.client.service.synchronizer.SyncManager
1618
import junit.framework.TestCase.assertEquals
19+
import junit.framework.TestCase.assertFalse
20+
import junit.framework.TestCase.assertTrue
1721
import org.junit.After
1822
import org.junit.Before
1923
import org.junit.Test
@@ -25,7 +29,6 @@ import org.mockito.Mockito.verify
2529
import org.mockito.Mockito.`when`
2630
import org.mockito.MockitoAnnotations
2731
import java.io.File
28-
import java.lang.IllegalArgumentException
2932
import java.util.concurrent.locks.ReentrantLock
3033

3134
class SplitFactoryHelperTest {
@@ -184,4 +187,30 @@ class SplitFactoryHelperTest {
184187
verify(lifecycleManager).register(syncManager)
185188
verify(initLock).unlock()
186189
}
190+
191+
@Test
192+
fun `initializing with proxy config with null url throws`() {
193+
var exceptionThrown = false
194+
try {
195+
SplitFactoryBuilder.build("sdk_key", Key("user"), SplitClientConfig.builder().proxyConfiguration(
196+
ProxyConfiguration.builder().build()).build(), context)
197+
} catch (splitInstantiationException: SplitInstantiationException) {
198+
exceptionThrown = splitInstantiationException.message!!.contains("When configured, proxy host cannot be null")
199+
}
200+
201+
assertTrue(exceptionThrown)
202+
}
203+
204+
@Test
205+
fun `initializing with proxy config with valid url does not throw`() {
206+
var exceptionThrown = false
207+
try {
208+
SplitFactoryBuilder.build("sdk_key", Key("user"), SplitClientConfig.builder().proxyConfiguration(
209+
ProxyConfiguration.builder().url("http://localhost:8080").build()).build(), context)
210+
} catch (splitInstantiationException: SplitInstantiationException) {
211+
exceptionThrown = splitInstantiationException.message!!.contains("When configured, proxy host cannot be null")
212+
}
213+
214+
assertFalse(exceptionThrown)
215+
}
187216
}

src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,9 @@ public MockResponse dispatch(RecordedRequest request) {
390390
HttpProxy proxy = HttpProxy.newBuilder("localhost", assignedProxyPort)
391391
.mtlsAuth(
392392
Files.newInputStream(clientCertFile.toPath()),
393-
Files.newInputStream(clientKeyFile.toPath()),
394-
Files.newInputStream(proxyCaFile.toPath())
393+
Files.newInputStream(clientKeyFile.toPath())
395394
)
395+
.proxyCacert(Files.newInputStream(proxyCaFile.toPath()))
396396
.build();
397397

398398
// 5. Build client (let builder/factory handle trust)

0 commit comments

Comments
 (0)