Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5130220
Merge pull request #546 from splitio/development
chillaq Apr 17, 2025
193f3c4
Merge pull request #571 from splitio/development
chillaq May 29, 2025
2bc5767
Updated SplitClientConfig
chillaq Jun 24, 2025
3cca5ea
Added proxy scheme
chillaq Jun 25, 2025
0267513
Update factory
chillaq Jun 25, 2025
436dc2a
Added proxy tests
chillaq Jun 25, 2025
b42fddc
updated test
chillaq Jun 26, 2025
74a4533
Merge pull request #574 from splitio/FME-4283-proxy-auth-config
chillaq Jun 30, 2025
80b8f35
Merge pull request #575 from splitio/FME-4285-proxy-auth-factory
chillaq Jun 30, 2025
d2bdc02
Added sslcontext to SSE
chillaq Jun 30, 2025
c4c2eea
Merge pull request #576 from splitio/proxy-auth-sse
chillaq Jun 30, 2025
0489c0d
Added auto refresh proxy token
chillaq Jun 30, 2025
4a86a3d
disable test
chillaq Jun 30, 2025
a5a92f8
disabled test
chillaq Jun 30, 2025
32f5a11
disabled test
chillaq Jun 30, 2025
853bd7b
Merge pull request #577 from splitio/proxy-auth-auto-refresh-token
chillaq Jun 30, 2025
ce3aec5
renamed interface
chillaq Jun 30, 2025
f62acd9
Merge branch 'development' into feature/proxy-auth
chillaq Jun 30, 2025
4662ee7
Fixed config property and test
chillaq Jul 1, 2025
61fe715
Rename config field
chillaq Jul 1, 2025
eef3227
Merge pull request #579 from splitio/proxy-auth-fix
chillaq Jul 1, 2025
e53c5d0
polishing
chillaq Jul 1, 2025
21d43be
polish
chillaq Jul 1, 2025
d9e2bce
polish
chillaq Jul 1, 2025
2113b7c
polish
chillaq Jul 1, 2025
4f89fd9
fix tracker warning
chillaq Jul 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<parent>
<groupId>io.split.client</groupId>
<artifactId>java-client-parent</artifactId>
<version>4.16.0</version>
<version>4.17.0-rc2</version>
</parent>
<version>4.16.0</version>
<version>4.17.0-rc2</version>
<artifactId>java-client</artifactId>
<packaging>jar</packaging>
<name>Java Client</name>
Expand All @@ -24,7 +24,7 @@
<version>0.8.0</version>
<extensions>true</extensions>
<configuration>
<ignorePublishedComponents>true</ignorePublishedComponents>
<ignorePublishedComponents>false</ignorePublishedComponents>
<publishingServerId>central</publishingServerId>
<autoPublish>false</autoPublish>
<waitUntil>published</waitUntil>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.split.client;

import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.BearerToken;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.core5.http.protocol.HttpContext;

class HttpClientDynamicCredentials implements org.apache.hc.client5.http.auth.CredentialsProvider {

private final ProxyCredentialsProvider _proxyCredentialsProvider;

public HttpClientDynamicCredentials (ProxyCredentialsProvider proxyCredentialsProvider) {
_proxyCredentialsProvider = proxyCredentialsProvider;
}

@Override
public Credentials getCredentials(AuthScope authScope, HttpContext context) {

// This Provider is invoked every time a request is made.
// This should invoke a user-custom provider responsible for:
return new BearerToken(_proxyCredentialsProvider.getJwtToken());
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.split.client;

public interface ProxyCredentialsProvider
{
/**
* Get the additional headers needed for all http operations
* @return HashMap of addition headers
*/
String getJwtToken();
}
97 changes: 95 additions & 2 deletions client/src/main/java/io/split/client/SplitClientConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.split.client;

import io.split.client.dtos.ProxyMTLSAuth;
import io.split.client.impressions.ImpressionListener;
import io.split.client.impressions.ImpressionsManager;
import io.split.client.utils.FileTypeEnum;
Expand Down Expand Up @@ -34,6 +35,14 @@ public class SplitClientConfig {
public static final String STREAMING_ENDPOINT = "https://streaming.split.io/sse";
public static final String TELEMETRY_ENDPOINT = "https://telemetry.split.io/api/v1";

public static class HttpScheme {
private HttpScheme() {
throw new IllegalStateException("Utility class");
}
public static final String HTTP = "http";
public static final String HTTPS = "https";
}

private final String _endpoint;
private final String _eventsEndpoint;

Expand Down Expand Up @@ -85,6 +94,8 @@ public class SplitClientConfig {
private final HttpHost _proxy;
private final String _proxyUsername;
private final String _proxyPassword;
private final ProxyCredentialsProvider _proxyCredentialsProvider;
private final ProxyMTLSAuth _proxyMtlsAuth;

// To be set during startup
public static String splitSdkVersion;
Expand Down Expand Up @@ -118,6 +129,8 @@ private SplitClientConfig(String endpoint,
HttpHost proxy,
String proxyUsername,
String proxyPassword,
ProxyCredentialsProvider proxyCredentialsProvider,
ProxyMTLSAuth proxyMtlsAuth,
int eventsQueueSize,
long eventSendIntervalInMillis,
int maxStringLength,
Expand Down Expand Up @@ -171,6 +184,8 @@ private SplitClientConfig(String endpoint,
_proxy = proxy;
_proxyUsername = proxyUsername;
_proxyPassword = proxyPassword;
_proxyCredentialsProvider = proxyCredentialsProvider;
_proxyMtlsAuth = proxyMtlsAuth;
_eventsQueueSize = eventsQueueSize;
_eventSendIntervalInMillis = eventSendIntervalInMillis;
_maxStringLength = maxStringLength;
Expand Down Expand Up @@ -302,6 +317,14 @@ public String proxyPassword() {
return _proxyPassword;
}

public ProxyCredentialsProvider proxyCredentialsProvider() {
return _proxyCredentialsProvider;
}

public ProxyMTLSAuth proxyMTLSAuth() {
return _proxyMtlsAuth;
}

public long eventSendIntervalInMillis() {
return _eventSendIntervalInMillis;
}
Expand Down Expand Up @@ -417,8 +440,8 @@ public boolean isSdkEndpointOverridden() {
}

public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; }
public static final class Builder {

public static final class Builder {
private String _endpoint = SDK_ENDPOINT;
private boolean _endpointSet = false;
private String _eventsEndpoint = EVENTS_ENDPOINT;
Expand All @@ -440,8 +463,11 @@ public static final class Builder {
private int _waitBeforeShutdown = 5000;
private String _proxyHost = "localhost";
private int _proxyPort = -1;
private String _proxyScheme = HttpScheme.HTTP;
private String _proxyUsername;
private String _proxyPassword;
private ProxyCredentialsProvider _proxyCredentialsProvider;
private ProxyMTLSAuth _proxyMtlsAuth;
private int _eventsQueueSize = 500;
private long _eventSendIntervalInMillis = 30 * (long)1000;
private int _maxStringLength = 250;
Expand Down Expand Up @@ -754,6 +780,17 @@ public Builder proxyPort(int proxyPort) {
return this;
}

/**
* The http scheme of the proxy. Default is http.
*
* @param proxyScheme protocol for the proxy
* @return this builder
*/
public Builder proxyScheme(String proxyScheme) {
_proxyScheme = proxyScheme;
return this;
}

/**
* Set the username for authentication against the proxy (if proxy settings are enabled). (Optional).
*
Expand All @@ -776,6 +813,28 @@ public Builder proxyPassword(String proxyPassword) {
return this;
}

/**
* Set the token for authentication against the proxy (if proxy settings are enabled). (Optional).
*
* @param proxyCredentialsProvider
* @return this builder
*/
public Builder proxyCredentialsProvider(ProxyCredentialsProvider proxyCredentialsProvider) {
_proxyCredentialsProvider = proxyCredentialsProvider;
return this;
}

/**
* Set the mtls authentication against the proxy (if proxy settings are enabled). (Optional).
*
* @param proxyMtlsAuth
* @return this builder
*/
public Builder proxyMtlsAuth(ProxyMTLSAuth proxyMtlsAuth) {
_proxyMtlsAuth = proxyMtlsAuth;
return this;
}

/**
* Disables running destroy() on shutdown by default.
*
Expand All @@ -788,7 +847,7 @@ public Builder disableDestroyOnShutDown() {

HttpHost proxy() {
if (_proxyPort != -1) {
return new HttpHost(_proxyHost, _proxyPort);
return new HttpHost(_proxyScheme, _proxyHost, _proxyPort);
}
// Default is no proxy.
return null;
Expand Down Expand Up @@ -1096,6 +1155,36 @@ private void verifyAlternativeClient() {
}
}

private void verifyProxy() {
if (_proxyPort == -1) {
return;
}

if (!(_proxyScheme.equals(HttpScheme.HTTP) || _proxyScheme.equals(HttpScheme.HTTPS))) {
throw new IllegalArgumentException("Proxy scheme must be either http or https.");
}

if (_proxyUsername == null && _proxyCredentialsProvider == null && _proxyMtlsAuth == null) {
return;
}

if (_proxyUsername != null && _proxyCredentialsProvider != null) {
throw new IllegalArgumentException("Proxy user and Proxy token params are updated, set only one param.");
}

if (_proxyUsername != null && _proxyMtlsAuth != null) {
throw new IllegalArgumentException("Proxy user and Proxy mTLS params are updated, set only one param.");
}

if (_proxyCredentialsProvider != null && _proxyMtlsAuth != null) {
throw new IllegalArgumentException("Proxy token and Proxy mTLS params are updated, set only one param.");
}

if (_proxyMtlsAuth != null && (_proxyMtlsAuth.getP12File() == null || _proxyMtlsAuth.getP12FilePassKey() == null)) {
throw new IllegalArgumentException("Proxy mTLS must have p12 file path and name, and pass phrase.");
}
}

public SplitClientConfig build() {

verifyRates();
Expand All @@ -1108,6 +1197,8 @@ public SplitClientConfig build() {

verifyAlternativeClient();

verifyProxy();

if (_numThreadsForSegmentFetch <= 0) {
throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
}
Expand All @@ -1133,6 +1224,8 @@ public SplitClientConfig build() {
proxy(),
_proxyUsername,
_proxyPassword,
_proxyCredentialsProvider,
_proxyMtlsAuth,
_eventsQueueSize,
_eventSendIntervalInMillis,
_maxStringLength,
Expand Down
48 changes: 44 additions & 4 deletions client/src/main/java/io/split/client/SplitFactoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,14 @@
import org.slf4j.LoggerFactory;
import pluggable.CustomStorageWrapper;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.HashSet;
Expand Down Expand Up @@ -517,9 +520,12 @@ public boolean isDestroyed() {

protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
SDKMetadata sdkMetadata, RequestDecorator requestDecorator)
throws URISyntaxException {
throws URISyntaxException, IOException {

SSLContext sslContext = buildSSLContext(config);

SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
.setSslContext(SSLContexts.createSystemDefault())
.setSslContext(sslContext)
.setTlsVersions(TLS.V_1_1, TLS.V_1_2)
.build();

Expand Down Expand Up @@ -556,13 +562,15 @@ protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClie
}

private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitClientConfig config,
SDKMetadata sdkMetadata) {
SDKMetadata sdkMetadata) throws IOException {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofMilliseconds(SSE_CONNECT_TIMEOUT))
.build();

SSLContext sslContext = buildSSLContext(config);

SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
.setSslContext(SSLContexts.createSystemDefault())
.setSslContext(sslContext)
.setTlsVersions(TLS.V_1_1, TLS.V_1_2)
.build();

Expand All @@ -589,6 +597,33 @@ private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitCli
return httpClientbuilder.build();
}

private static SSLContext buildSSLContext(SplitClientConfig config) throws IOException, NullPointerException {
SSLContext sslContext;
if (config.proxyMTLSAuth() != null) {
_log.debug("Proxy setup using mTLS");
InputStream keystoreStream = null;
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keystoreStream = java.nio.file.Files.newInputStream(Paths.get(config.proxyMTLSAuth().getP12File()));
keyStore.load(keystoreStream, config.proxyMTLSAuth().getP12FilePassKey().toCharArray());
sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, config.proxyMTLSAuth().getP12FilePassKey().toCharArray())
.build();
} catch (Exception e) {
_log.error("Exception caught while processing p12 file for Proxy mTLS auth: ", e);
_log.warn("Ignoring p12 mTLS config and switching to default context");
sslContext = SSLContexts.createSystemDefault();
} finally {
if (keystoreStream != null) {
keystoreStream.close();
}
}
} else {
sslContext = SSLContexts.createSystemDefault();
}
return sslContext;
}

private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, SplitClientConfig config) {
_log.info("Initializing Split SDK with proxy settings");
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(config.proxy());
Expand All @@ -604,6 +639,11 @@ private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder,
httpClientbuilder.setDefaultCredentialsProvider(credsProvider);
}

if (config.proxyCredentialsProvider() != null) {
_log.debug("Proxy setup using token");
httpClientbuilder.setDefaultCredentialsProvider(new HttpClientDynamicCredentials(config.proxyCredentialsProvider()));
}

return httpClientbuilder;
}

Expand Down
38 changes: 38 additions & 0 deletions client/src/main/java/io/split/client/dtos/ProxyMTLSAuth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.split.client.dtos;

public class ProxyMTLSAuth {
private final String _proxyP12File;
private final String _proxyP12FilePassKey;

private ProxyMTLSAuth(String proxyP12File, String proxyP12FilePassKey) {
_proxyP12File = proxyP12File;
_proxyP12FilePassKey = proxyP12FilePassKey;
}

public String getP12File() { return _proxyP12File; }

public String getP12FilePassKey() { return _proxyP12FilePassKey; }

public static ProxyMTLSAuth.Builder builder() {
return new ProxyMTLSAuth.Builder();
}

public static class Builder {
private String _p12File;
private String _p12FilePassKey;

public ProxyMTLSAuth.Builder proxyP12File(String p12File) {
_p12File = p12File;
return this;
}

public ProxyMTLSAuth.Builder proxyP12FilePassKey(String p12FilePassKey) {
_p12FilePassKey = p12FilePassKey;
return this;
}

public ProxyMTLSAuth build() {
return new ProxyMTLSAuth(_p12File, _p12FilePassKey);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private void sendUniqueKeys(){
}
try {
if (uniqueKeysTracker.size() == 0) {
_log.warn("The Unique Keys Tracker is empty");
_log.debug("The Unique Keys Tracker is empty");
return;
}
HashMap<String, HashSet<String>> uniqueKeysHashMap = popAll();
Expand Down
Loading