diff --git a/CHANGES.txt b/CHANGES.txt
index 072fab1f9..9c10df67f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,6 @@
+4.13.0 (Sep 13, 2024)
+- Added support for Kerberos Proxy authentication.
+
4.12.1 (Jun 10, 2024)
- Fixed deadlock for virtual thread in Push Manager and SSE Client.
diff --git a/client/pom.xml b/client/pom.xml
index 6bd936d6c..2c1892ca2 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,7 +5,7 @@
io.split.client
java-client-parent
- 4.13.0-rc1
+ 4.13.0
java-client
jar
@@ -64,6 +64,7 @@
io.split.schemas:*
io.codigo.grammar:*
org.apache.httpcomponents.*
+ org.apache.hc.*
com.google.*
org.yaml:snakeyaml:*
@@ -238,5 +239,17 @@
4.0.3
test
+
+ org.powermock
+ powermock-module-junit4
+ 1.7.4
+ test
+
+
+ org.powermock
+ powermock-api-mockito
+ 1.7.4
+ test
+
diff --git a/client/src/main/java/io/split/client/RequestDecorator.java b/client/src/main/java/io/split/client/RequestDecorator.java
index 1572463ef..33059e617 100644
--- a/client/src/main/java/io/split/client/RequestDecorator.java
+++ b/client/src/main/java/io/split/client/RequestDecorator.java
@@ -2,16 +2,12 @@
import io.split.client.dtos.RequestContext;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.Header;
import java.util.HashSet;
-import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
-import java.util.ArrayList;
import java.util.Set;
-import java.util.List;
+import java.util.stream.Collectors;
public final class RequestDecorator {
CustomHeaderDecorator _headerDecorator;
@@ -36,42 +32,16 @@ public RequestDecorator(CustomHeaderDecorator headerDecorator) {
: headerDecorator;
}
- public HttpRequest decorateHeaders(HttpRequest request) {
+ public RequestContext decorateHeaders(RequestContext request) {
try {
- Map> headers = _headerDecorator
- .getHeaderOverrides(new RequestContext(convertToMap(request.getHeaders())));
- for (Map.Entry> entry : headers.entrySet()) {
- if (isHeaderAllowed(entry.getKey())) {
- List values = entry.getValue();
- for (int i = 0; i < values.size(); i++) {
- if (i == 0) {
- request.setHeader(entry.getKey(), values.get(i));
- } else {
- request.addHeader(entry.getKey(), values.get(i));
- }
- }
- }
- }
+ return new RequestContext(_headerDecorator.getHeaderOverrides(request)
+ .entrySet()
+ .stream()
+ .filter(e -> !forbiddenHeaders.contains(e.getKey().toLowerCase()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
} catch (Exception e) {
throw new IllegalArgumentException(
String.format("Problem adding custom headers to request decorator: %s", e), e);
}
-
- return request;
- }
-
- private boolean isHeaderAllowed(String headerName) {
- return !forbiddenHeaders.contains(headerName.toLowerCase());
- }
-
- private Map> convertToMap(Header[] to_convert) {
- Map> to_return = new HashMap>();
- for (Integer i = 0; i < to_convert.length; i++) {
- if (!to_return.containsKey(to_convert[i].getName())) {
- to_return.put(to_convert[i].getName(), new ArrayList());
- }
- to_return.get(to_convert[i].getName()).add(to_convert[i].getValue());
- }
- return to_return;
}
}
diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java
index ec4e49cf3..8787c1069 100644
--- a/client/src/main/java/io/split/client/SplitClientConfig.java
+++ b/client/src/main/java/io/split/client/SplitClientConfig.java
@@ -4,9 +4,9 @@
import io.split.client.impressions.ImpressionsManager;
import io.split.client.utils.FileTypeEnum;
import io.split.integrations.IntegrationsConfig;
+import io.split.service.CustomHttpModule;
import io.split.storages.enums.OperationMode;
import io.split.storages.enums.StorageMode;
-import io.split.service.HttpAuthScheme;
import org.apache.hc.core5.http.HttpHost;
import pluggable.CustomStorageWrapper;
@@ -92,8 +92,7 @@ public class SplitClientConfig {
private final HashSet _flagSetsFilter;
private final int _invalidSets;
private final CustomHeaderDecorator _customHeaderDecorator;
- private final HttpAuthScheme _authScheme;
-
+ private final CustomHttpModule _alternativeHTTPModule;
public static Builder builder() {
return new Builder();
@@ -151,7 +150,7 @@ private SplitClientConfig(String endpoint,
HashSet flagSetsFilter,
int invalidSets,
CustomHeaderDecorator customHeaderDecorator,
- HttpAuthScheme authScheme) {
+ CustomHttpModule alternativeHTTPModule) {
_endpoint = endpoint;
_eventsEndpoint = eventsEndpoint;
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -204,7 +203,7 @@ private SplitClientConfig(String endpoint,
_flagSetsFilter = flagSetsFilter;
_invalidSets = invalidSets;
_customHeaderDecorator = customHeaderDecorator;
- _authScheme = authScheme;
+ _alternativeHTTPModule = alternativeHTTPModule;
Properties props = new Properties();
try {
@@ -412,10 +411,8 @@ public int getInvalidSets() {
public CustomHeaderDecorator customHeaderDecorator() {
return _customHeaderDecorator;
}
- public HttpAuthScheme authScheme() {
- return _authScheme;
- }
+ public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; }
public static final class Builder {
private String _endpoint = SDK_ENDPOINT;
@@ -473,7 +470,7 @@ public static final class Builder {
private HashSet _flagSetsFilter = new HashSet<>();
private int _invalidSetsCount = 0;
private CustomHeaderDecorator _customHeaderDecorator = null;
- private HttpAuthScheme _authScheme = null;
+ private CustomHttpModule _alternativeHTTPModule = null;
public Builder() {
}
@@ -969,13 +966,13 @@ public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator
}
/**
- * Authentication Scheme
+ * Alternative Http Client
*
- * @param authScheme
+ * @param alternativeHTTPModule
* @return this builder
*/
- public Builder authScheme(HttpAuthScheme authScheme) {
- _authScheme = authScheme;
+ public Builder alternativeHTTPModule(CustomHttpModule alternativeHTTPModule) {
+ _alternativeHTTPModule = alternativeHTTPModule;
return this;
}
@@ -990,7 +987,7 @@ public Builder threadFactory(ThreadFactory threadFactory) {
return this;
}
- public SplitClientConfig build() {
+ private void verifyRates() {
if (_featuresRefreshRate < 5 ) {
throw new IllegalArgumentException("featuresRefreshRate must be >= 5: " + _featuresRefreshRate);
}
@@ -999,15 +996,6 @@ public SplitClientConfig build() {
throw new IllegalArgumentException("segmentsRefreshRate must be >= 30: " + _segmentsRefreshRate);
}
- switch (_impressionsMode) {
- case OPTIMIZED:
- _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 300 : Math.max(60, _impressionsRefreshRate);
- break;
- case DEBUG:
- _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 60 : _impressionsRefreshRate;
- break;
- }
-
if (_eventSendIntervalInMillis < 1000) {
throw new IllegalArgumentException("_eventSendIntervalInMillis must be >= 1000: " + _eventSendIntervalInMillis);
}
@@ -1015,19 +1003,12 @@ public SplitClientConfig build() {
if (_metricsRefreshRate < 30) {
throw new IllegalArgumentException("metricsRefreshRate must be >= 30: " + _metricsRefreshRate);
}
-
- if (_impressionsQueueSize <=0 ) {
- throw new IllegalArgumentException("impressionsQueueSize must be > 0: " + _impressionsQueueSize);
- }
-
- if (_connectionTimeout <= 0) {
- throw new IllegalArgumentException("connectionTimeOutInMs must be > 0: " + _connectionTimeout);
- }
-
- if (_readTimeout <= 0) {
- throw new IllegalArgumentException("readTimeout must be > 0: " + _readTimeout);
+ if(_telemetryRefreshRate < 60) {
+ throw new IllegalStateException("_telemetryRefreshRate must be >= 60");
}
+ }
+ private void verifyEndPoints() {
if (_endpoint == null) {
throw new IllegalArgumentException("endpoint must not be null");
}
@@ -1040,18 +1021,6 @@ public SplitClientConfig build() {
throw new IllegalArgumentException("If endpoint is set, you must also set the events endpoint");
}
- if (_numThreadsForSegmentFetch <= 0) {
- throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
- }
-
- if (_authRetryBackoffBase <= 0) {
- throw new IllegalArgumentException("authRetryBackoffBase: must be >= 1");
- }
-
- if (_streamingReconnectBackoffBase <= 0) {
- throw new IllegalArgumentException("streamingReconnectBackoffBase: must be >= 1");
- }
-
if (_authServiceURL == null) {
throw new IllegalArgumentException("authServiceURL must not be null");
}
@@ -1063,22 +1032,26 @@ public SplitClientConfig build() {
if (_telemetryURl == null) {
throw new IllegalArgumentException("telemetryURl must not be null");
}
+ }
- if (_onDemandFetchRetryDelayMs <= 0) {
- throw new IllegalStateException("streamingRetryDelay must be > 0");
+ private void verifyAllModes() {
+ switch (_impressionsMode) {
+ case OPTIMIZED:
+ _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 300 : Math.max(60, _impressionsRefreshRate);
+ break;
+ case DEBUG:
+ _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 60 : _impressionsRefreshRate;
+ break;
+ case NONE:
+ break;
}
- if(_onDemandFetchMaxRetries <= 0) {
- throw new IllegalStateException("_onDemandFetchMaxRetries must be > 0");
+ if (_impressionsQueueSize <=0 ) {
+ throw new IllegalArgumentException("impressionsQueueSize must be > 0: " + _impressionsQueueSize);
}
-
if(_storageMode == null) {
_storageMode = StorageMode.MEMORY;
}
-
- if(_telemetryRefreshRate < 60) {
- throw new IllegalStateException("_telemetryRefreshRate must be >= 60");
- }
if(OperationMode.CONSUMER.equals(_operationMode)){
if(_customStorageWrapper == null) {
@@ -1086,8 +1059,56 @@ public SplitClientConfig build() {
}
_storageMode = StorageMode.PLUGGABLE;
}
+ }
+
+ private void verifyNetworkParams() {
+ if (_connectionTimeout <= 0) {
+ throw new IllegalArgumentException("connectionTimeOutInMs must be > 0: " + _connectionTimeout);
+ }
+
+ if (_readTimeout <= 0) {
+ throw new IllegalArgumentException("readTimeout must be > 0: " + _readTimeout);
+ }
+ if (_authRetryBackoffBase <= 0) {
+ throw new IllegalArgumentException("authRetryBackoffBase: must be >= 1");
+ }
+
+ if (_streamingReconnectBackoffBase <= 0) {
+ throw new IllegalArgumentException("streamingReconnectBackoffBase: must be >= 1");
+ }
+
+ if (_onDemandFetchRetryDelayMs <= 0) {
+ throw new IllegalStateException("streamingRetryDelay must be > 0");
+ }
+
+ if(_onDemandFetchMaxRetries <= 0) {
+ throw new IllegalStateException("_onDemandFetchMaxRetries must be > 0");
+ }
+ }
+
+ private void verifyAlternativeClient() {
+ if (_alternativeHTTPModule != null && _streamingEnabled) {
+ throw new IllegalArgumentException("Streaming feature is not supported with Alternative HTTP Client");
+ }
+ }
+
+ public SplitClientConfig build() {
+
+ verifyRates();
+
+ verifyAllModes();
+
+ verifyEndPoints();
+
+ verifyNetworkParams();
+
+ verifyAlternativeClient();
+
+ if (_numThreadsForSegmentFetch <= 0) {
+ throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
+ }
- return new SplitClientConfig(
+ return new SplitClientConfig(
_endpoint,
_eventsEndpoint,
_featuresRefreshRate,
@@ -1140,7 +1161,7 @@ public SplitClientConfig build() {
_flagSetsFilter,
_invalidSetsCount,
_customHeaderDecorator,
- _authScheme);
+ _alternativeHTTPModule);
}
}
}
\ No newline at end of file
diff --git a/client/src/main/java/io/split/client/SplitFactoryBuilder.java b/client/src/main/java/io/split/client/SplitFactoryBuilder.java
index c2271ec4f..2b48fb0d3 100644
--- a/client/src/main/java/io/split/client/SplitFactoryBuilder.java
+++ b/client/src/main/java/io/split/client/SplitFactoryBuilder.java
@@ -2,6 +2,7 @@
import io.split.inputValidation.ApiKeyValidator;
import io.split.grammar.Treatments;
+import io.split.service.SplitHttpClient;
import io.split.storages.enums.StorageMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java
index 68eeba672..3102d3e17 100644
--- a/client/src/main/java/io/split/client/SplitFactoryImpl.java
+++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java
@@ -57,10 +57,9 @@
import io.split.engine.segments.SegmentChangeFetcher;
import io.split.engine.segments.SegmentSynchronizationTaskImp;
import io.split.integrations.IntegrationsConfig;
-import io.split.service.HttpAuthScheme;
-import io.split.service.SplitHttpClient;
import io.split.service.SplitHttpClientImpl;
-import io.split.service.SplitHttpClientKerberosImpl;
+import io.split.service.SplitHttpClient;
+
import io.split.storages.SegmentCache;
import io.split.storages.SegmentCacheConsumer;
import io.split.storages.SegmentCacheProducer;
@@ -85,6 +84,7 @@
import io.split.telemetry.synchronizer.TelemetryInMemorySubmitter;
import io.split.telemetry.synchronizer.TelemetrySyncTask;
import io.split.telemetry.synchronizer.TelemetrySynchronizer;
+
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
@@ -104,7 +104,6 @@
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
-import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pluggable.CustomStorageWrapper;
@@ -113,16 +112,16 @@
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
import static io.split.client.utils.SplitExecutorFactory.buildExecutorService;
public class SplitFactoryImpl implements SplitFactory {
- private static final Logger _log = LoggerFactory.getLogger(SplitFactory.class);
+ private static final org.slf4j.Logger _log = LoggerFactory.getLogger(SplitFactoryImpl.class);
private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or "
+
"inputStream doesn't add it to the config.";
@@ -157,15 +156,16 @@ public class SplitFactoryImpl implements SplitFactory {
private final SplitSynchronizationTask _splitSynchronizationTask;
private final EventsTask _eventsTask;
private final SyncManager _syncManager;
- private final SplitHttpClient _splitHttpClient;
+ private SplitHttpClient _splitHttpClient;
private final UserStorageWrapper _userStorageWrapper;
private final ImpressionsSender _impressionsSender;
private final URI _rootTarget;
private final URI _eventsRootTarget;
private final UniqueKeysTracker _uniqueKeysTracker;
+ private RequestDecorator _requestDecorator;
// Constructor for standalone mode
- public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException {
+ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException, IOException {
_userStorageWrapper = null;
_operationMode = config.operationMode();
_startTime = System.currentTimeMillis();
@@ -188,9 +188,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
// SDKReadinessGates
_gates = new SDKReadinessGates();
+ _requestDecorator = new RequestDecorator(config.customHeaderDecorator());
// HttpClient
- RequestDecorator requestDecorator = new RequestDecorator(config.customHeaderDecorator());
- _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, requestDecorator);
+ if (config.alternativeHTTPModule() == null) {
+ _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, _requestDecorator);
+ } else {
+ _splitHttpClient = config.alternativeHTTPModule().createClient(apiToken, _sdkMetadata, _requestDecorator);
+ }
// Roots
_rootTarget = URI.create(config.endpoint());
@@ -236,7 +240,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
EventsSender eventsSender = EventsSender.create(_splitHttpClient, _eventsRootTarget, _telemetryStorageProducer);
_eventsTask = EventsTask.create(config.eventSendIntervalInMillis(), eventsStorage, eventsSender,
config.getThreadFactory());
- _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory());
+ _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
+ config.getThreadFactory());
// Evaluator
_evaluator = new EvaluatorImp(splitCache, segmentCache);
@@ -259,7 +264,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
// SyncManager
SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp,
_impressionsManager, _eventsTask, _telemetrySyncTask, _uniqueKeysTracker);
- SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata), requestDecorator);
+ SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata),
+ _requestDecorator);
_syncManager = SyncManagerImp.build(splitTasks, _splitFetcher, splitCache, splitAPI,
segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser,
@@ -330,8 +336,10 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor
_evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer);
_impressionsSender = PluggableImpressionSender.create(customStorageWrapper);
_uniqueKeysTracker = createUniqueKeysTracker(config);
- _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, userCustomImpressionAdapterProducer);
- _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory());
+ _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer,
+ userCustomImpressionAdapterProducer);
+ _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
+ config.getThreadFactory());
SplitTasks splitTasks = SplitTasks.build(null, null,
_impressionsManager, null, _telemetrySyncTask, _uniqueKeysTracker);
@@ -493,7 +501,7 @@ public boolean isDestroyed() {
return isTerminated;
}
- private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
+ protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
SDKMetadata sdkMetadata, RequestDecorator requestDecorator)
throws URISyntaxException {
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
@@ -527,13 +535,6 @@ private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClient
httpClientbuilder = setupProxy(httpClientbuilder, config);
}
- if (config.authScheme() == HttpAuthScheme.KERBEROS) {
- return SplitHttpClientKerberosImpl.create(
- requestDecorator,
- apiToken,
- sdkMetadata);
-
- }
return SplitHttpClientImpl.create(httpClientbuilder.build(),
requestDecorator,
apiToken,
diff --git a/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java b/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java
index a5474cf5b..259ed0794 100644
--- a/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java
+++ b/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java
@@ -1,7 +1,7 @@
package io.split.client.dtos;
-import java.util.Map;
-import org.apache.hc.core5.http.Header;
+import java.util.List;
+
/**
* A structure for returning http call results information
*/
@@ -11,15 +11,42 @@ public class SplitHttpResponse {
private final String _body;
private final Header[] _responseHeaders;
+ public static class Header {
+ private String _name;
+ private List _values;
+
+ public Header(String name, List values) {
+ _name = name;
+ _values = values;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public List getValues() {
+ return _values;
+ }
+ };
+
public SplitHttpResponse(Integer statusCode, String statusMessage, String body, Header[] headers) {
_statusCode = statusCode;
_statusMessage = statusMessage;
_body = body;
_responseHeaders = headers;
}
+
+ public SplitHttpResponse(Integer statusCode, String statusMessage, String body, List headers) {
+ _statusCode = statusCode;
+ _statusMessage = statusMessage;
+ _body = body;
+ _responseHeaders = headers.toArray(new Header[0]);
+ }
+
public Integer statusCode() {
return _statusCode;
}
+
public String statusMessage() {
return _statusMessage;
}
diff --git a/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java b/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java
index 06df64cc4..35c0f57f2 100644
--- a/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java
+++ b/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java
@@ -4,6 +4,7 @@
import io.split.client.dtos.ImpressionCount;
import io.split.client.dtos.SplitHttpResponse;
import io.split.client.dtos.TestImpressions;
+import io.split.client.utils.Json;
import io.split.client.utils.Utils;
import io.split.service.SplitHttpClient;
@@ -11,7 +12,6 @@
import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum;
import io.split.telemetry.domain.enums.ResourceEnum;
import io.split.telemetry.storage.TelemetryRuntimeProducer;
-import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,10 +67,12 @@ private HttpImpressionsSender(SplitHttpClient client, URI impressionBulkTarget,
public void postImpressionsBulk(List impressions) {
long initTime = System.currentTimeMillis();
try {
- HttpEntity entity = Utils.toJsonEntity(impressions);
- Map> additionalHeaders = Collections.singletonMap(IMPRESSIONS_MODE_HEADER,
- Collections.singletonList(_mode.toString()));
- SplitHttpResponse response = _client.post(_impressionBulkTarget, entity, additionalHeaders);
+ Map> additionalHeaders = new HashMap<>();
+ additionalHeaders.put(IMPRESSIONS_MODE_HEADER, Collections.singletonList(_mode.toString()));
+ additionalHeaders.put("Content-Type", Collections.singletonList("application/json"));
+
+ SplitHttpResponse response = _client.post(_impressionBulkTarget, Json.toJson(impressions),
+ additionalHeaders);
if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
_telemetryRuntimeProducer.recordSyncError(ResourceEnum.IMPRESSION_SYNC, response.statusCode());
@@ -95,8 +97,12 @@ public void postCounters(HashMap raw) {
}
try {
+
+ Map> additionalHeaders = new HashMap<>();
+ additionalHeaders.put("Content-Type", Collections.singletonList("application/json"));
+
SplitHttpResponse response = _client.post(_impressionCountTarget,
- Utils.toJsonEntity(ImpressionCount.fromImpressionCounterData(raw)),
+ Json.toJson(ImpressionCount.fromImpressionCounterData(raw)),
null);
if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
diff --git a/client/src/main/java/io/split/client/utils/ApacheRequestDecorator.java b/client/src/main/java/io/split/client/utils/ApacheRequestDecorator.java
new file mode 100644
index 000000000..c64d9d46c
--- /dev/null
+++ b/client/src/main/java/io/split/client/utils/ApacheRequestDecorator.java
@@ -0,0 +1,43 @@
+package io.split.client.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpRequest;
+
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.RequestContext;
+
+public class ApacheRequestDecorator {
+
+ public static HttpRequest decorate(HttpRequest request, RequestDecorator decorator) {
+
+ RequestContext ctx = new RequestContext(convertToMap(request.getHeaders()));
+ for (Map.Entry> entry : decorator.decorateHeaders(ctx).headers().entrySet()) {
+ List values = entry.getValue();
+ for (int i = 0; i < values.size(); i++) {
+ if (i == 0) {
+ request.setHeader(entry.getKey(), values.get(i));
+ } else {
+ request.addHeader(entry.getKey(), values.get(i));
+ }
+ }
+ }
+
+ return request;
+ }
+
+ private static Map> convertToMap(Header[] to_convert) {
+ Map> to_return = new HashMap>();
+ for (Integer i = 0; i < to_convert.length; i++) {
+ if (!to_return.containsKey(to_convert[i].getName())) {
+ to_return.put(to_convert[i].getName(), new ArrayList());
+ }
+ to_return.get(to_convert[i].getName()).add(to_convert[i].getValue());
+ }
+ return to_return;
+ }
+}
diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java
index 3c15481fd..653249308 100644
--- a/client/src/main/java/io/split/engine/common/PushManagerImp.java
+++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java
@@ -84,6 +84,7 @@ public static PushManagerImp build(Synchronizer synchronizer,
telemetryRuntimeProducer, flagSetsFilter);
Worker segmentWorker = new SegmentsWorkerImp(synchronizer);
PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(statusMessages, telemetryRuntimeProducer);
+
return new PushManagerImp(new AuthApiClientImp(authUrl, splitAPI.getHttpClient(), telemetryRuntimeProducer),
EventSourceClientImp.build(streamingUrl, featureFlagsWorker, segmentWorker, pushStatusTracker, splitAPI.getSseHttpClient(),
telemetryRuntimeProducer, threadFactory, splitAPI.getRequestDecorator()),
diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java
index 37cc6dac9..aac6f5566 100644
--- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java
+++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java
@@ -2,6 +2,7 @@
import com.google.common.base.Strings;
import io.split.client.RequestDecorator;
+import io.split.client.utils.ApacheRequestDecorator;
import io.split.telemetry.domain.StreamingEvent;
import io.split.telemetry.domain.enums.StreamEventsEnum;
import io.split.telemetry.storage.TelemetryRuntimeProducer;
@@ -64,11 +65,11 @@ private enum ConnectionState {
private final TelemetryRuntimeProducer _telemetryRuntimeProducer;
public SSEClient(Function eventCallback,
- Function statusCallback,
- CloseableHttpClient client,
- TelemetryRuntimeProducer telemetryRuntimeProducer,
- ThreadFactory threadFactory,
- RequestDecorator requestDecorator) {
+ Function statusCallback,
+ CloseableHttpClient client,
+ TelemetryRuntimeProducer telemetryRuntimeProducer,
+ ThreadFactory threadFactory,
+ RequestDecorator requestDecorator) {
_eventCallback = eventCallback;
_statusCallback = statusCallback;
_client = client;
@@ -96,7 +97,7 @@ public boolean open(URI uri) {
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- if(e.getMessage() == null){
+ if (e.getMessage() == null) {
_log.info("The thread was interrupted while opening SSEClient");
return false;
}
@@ -152,31 +153,41 @@ private void connectAndLoop(URI uri, CountDownLatch signal) {
_log.debug(exc.getMessage());
if (SOCKET_CLOSED_MESSAGE.equals(exc.getMessage())) { // Connection closed by us
_statusCallback.apply(StatusMessage.FORCED_STOP);
- _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
- StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+ _telemetryRuntimeProducer.recordStreamingEvents(
+ new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+ StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(),
+ System.currentTimeMillis()));
return;
}
// Connection closed by server
_statusCallback.apply(StatusMessage.RETRYABLE_ERROR);
- _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
- StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+ _telemetryRuntimeProducer
+ .recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+ StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(),
+ System.currentTimeMillis()));
return;
} catch (IOException exc) { // Other type of connection error
- if(!_forcedStop.get()) {
+ if (!_forcedStop.get()) {
_log.debug(String.format("SSE connection ended abruptly: %s. Retying", exc.getMessage()));
- _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
- StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+ _telemetryRuntimeProducer.recordStreamingEvents(
+ new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+ StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(),
+ System.currentTimeMillis()));
_statusCallback.apply(StatusMessage.RETRYABLE_ERROR);
return;
}
- _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
- StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+ _telemetryRuntimeProducer
+ .recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+ StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(),
+ System.currentTimeMillis()));
}
}
} catch (Exception e) { // Any other error non related to the connection disables streaming altogether
- _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
- StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+ _telemetryRuntimeProducer
+ .recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+ StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(),
+ System.currentTimeMillis()));
_log.warn(e.getMessage(), e);
_statusCallback.apply(StatusMessage.NONRETRYABLE_ERROR);
} finally {
@@ -194,12 +205,13 @@ private void connectAndLoop(URI uri, CountDownLatch signal) {
private boolean establishConnection(URI uri, CountDownLatch signal) {
HttpGet request = new HttpGet(uri);
- request = (HttpGet) _requestDecorator.decorateHeaders(request);
+ request = (HttpGet) ApacheRequestDecorator.decorate(request, _requestDecorator);
_ongoingRequest.set(request);
try {
_ongoingResponse.set(_client.execute(_ongoingRequest.get()));
if (_ongoingResponse.get().getCode() != 200) {
- _log.error(String.format("Establishing connection, code error: %s. The url is %s", _ongoingResponse.get().getCode(), uri.toURL()));
+ _log.error(String.format("Establishing connection, code error: %s. The url is %s",
+ _ongoingResponse.get().getCode(), uri.toURL()));
return false;
}
_state.set(ConnectionState.OPEN);
@@ -236,4 +248,4 @@ private void handleMessage(String message) {
RawEvent e = RawEvent.fromString(message);
_eventCallback.apply(e);
}
-}
\ No newline at end of file
+}
diff --git a/client/src/main/java/io/split/service/CustomHttpModule.java b/client/src/main/java/io/split/service/CustomHttpModule.java
new file mode 100644
index 000000000..001648fb3
--- /dev/null
+++ b/client/src/main/java/io/split/service/CustomHttpModule.java
@@ -0,0 +1,11 @@
+package io.split.service;
+
+import io.split.client.RequestDecorator;
+import io.split.client.utils.SDKMetadata;
+
+import java.io.IOException;
+
+public interface CustomHttpModule {
+ public SplitHttpClient createClient(String apiToken, SDKMetadata sdkMetadata, RequestDecorator decorator)
+ throws IOException;
+}
diff --git a/client/src/main/java/io/split/service/HttpAuthScheme.java b/client/src/main/java/io/split/service/HttpAuthScheme.java
deleted file mode 100644
index 1753f7369..000000000
--- a/client/src/main/java/io/split/service/HttpAuthScheme.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.split.service;
-
-public enum HttpAuthScheme {
- KERBEROS
-}
diff --git a/client/src/main/java/io/split/service/HttpPostImp.java b/client/src/main/java/io/split/service/HttpPostImp.java
index e5baa001b..b33bf2103 100644
--- a/client/src/main/java/io/split/service/HttpPostImp.java
+++ b/client/src/main/java/io/split/service/HttpPostImp.java
@@ -1,15 +1,18 @@
package io.split.service;
import io.split.client.dtos.SplitHttpResponse;
-import io.split.client.utils.Utils;
+import io.split.client.utils.Json;
import io.split.telemetry.domain.enums.HttpParamsWrapper;
import io.split.telemetry.storage.TelemetryRuntimeProducer;
-import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -25,16 +28,19 @@ public HttpPostImp(SplitHttpClient client, TelemetryRuntimeProducer telemetryRun
public void post(URI uri, Object object, String posted, HttpParamsWrapper httpParamsWrapper) {
long initTime = System.currentTimeMillis();
- HttpEntity entity = Utils.toJsonEntity(object);
try {
- SplitHttpResponse response = _client.post(uri, entity, null);
+ Map> headers = new HashMap<>();
+ headers.put("Content-Type", Collections.singletonList("application/json"));
+ SplitHttpResponse response = _client.post(uri, Json.toJson(object), headers);
if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
_telemetryRuntimeProducer.recordSyncError(httpParamsWrapper.getResourceEnum(), response.statusCode());
return;
}
- _telemetryRuntimeProducer.recordSyncLatency(httpParamsWrapper.getHttpLatenciesEnum(), System.currentTimeMillis() - initTime);
- _telemetryRuntimeProducer.recordSuccessfulSync(httpParamsWrapper.getLastSynchronizationRecordsEnum(), System.currentTimeMillis());
+ _telemetryRuntimeProducer.recordSyncLatency(httpParamsWrapper.getHttpLatenciesEnum(),
+ System.currentTimeMillis() - initTime);
+ _telemetryRuntimeProducer.recordSuccessfulSync(httpParamsWrapper.getLastSynchronizationRecordsEnum(),
+ System.currentTimeMillis());
} catch (Throwable t) {
_logger.warn("Exception when posting " + posted + object, t);
}
diff --git a/client/src/main/java/io/split/service/SplitHttpClient.java b/client/src/main/java/io/split/service/SplitHttpClient.java
index 1c88bcd4e..899fcf56b 100644
--- a/client/src/main/java/io/split/service/SplitHttpClient.java
+++ b/client/src/main/java/io/split/service/SplitHttpClient.java
@@ -3,8 +3,6 @@
import io.split.engine.common.FetchOptions;
import io.split.client.dtos.SplitHttpResponse;
-import org.apache.hc.core5.http.HttpEntity;
-
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
@@ -30,6 +28,6 @@ public interface SplitHttpClient extends Closeable {
* @return The response structure SplitHttpResponse
*/
public SplitHttpResponse post(URI uri,
- HttpEntity entity,
+ String entity,
Map> additionalHeaders) throws IOException;
}
diff --git a/client/src/main/java/io/split/service/SplitHttpClientImpl.java b/client/src/main/java/io/split/service/SplitHttpClientImpl.java
index 64ca3a55c..7f0674411 100644
--- a/client/src/main/java/io/split/service/SplitHttpClientImpl.java
+++ b/client/src/main/java/io/split/service/SplitHttpClientImpl.java
@@ -1,6 +1,7 @@
package io.split.service;
import io.split.client.RequestDecorator;
+import io.split.client.utils.ApacheRequestDecorator;
import io.split.client.utils.SDKMetadata;
import io.split.client.utils.Utils;
import io.split.engine.common.FetchOptions;
@@ -9,9 +10,10 @@
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
-import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.HttpEntities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
@@ -19,8 +21,11 @@
import java.net.URISyntaxException;
import org.apache.hc.core5.http.HttpRequest;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
public final class SplitHttpClientImpl implements SplitHttpClient {
@@ -72,7 +77,7 @@ public SplitHttpResponse get(URI uri, FetchOptions options, Map new SplitHttpResponse.Header(h.getName(), Collections.singletonList(h.getValue())))
+ .collect(Collectors.toList()));
+ // response.getHeaders());
} catch (Exception e) {
throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
} finally {
@@ -98,7 +107,7 @@ public SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders)
+ public SplitHttpResponse post(URI uri, String body, Map> additionalHeaders)
throws IOException {
CloseableHttpResponse response = null;
@@ -112,8 +121,8 @@ public SplitHttpResponse post(URI uri, HttpEntity entity, Map new SplitHttpResponse.Header(h.getName(), Collections.singletonList(h.getValue())))
+ .collect(Collectors.toList()));
} catch (Exception e) {
throw new IOException(String.format("Problem in http post operation: %s", e), e);
} finally {
diff --git a/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java b/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java
deleted file mode 100644
index 82dd9a35b..000000000
--- a/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java
+++ /dev/null
@@ -1,211 +0,0 @@
-package io.split.service;
-
-import io.split.client.RequestDecorator;
-import io.split.client.dtos.SplitHttpResponse;
-import io.split.client.utils.SDKMetadata;
-import io.split.engine.common.FetchOptions;
-
-import org.apache.hc.client5.http.classic.methods.HttpGet;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpEntity;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.io.entity.EntityUtils;
-import org.apache.hc.core5.http.message.BasicHeader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.InputStreamReader;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.HttpURLConnection;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-public class SplitHttpClientKerberosImpl implements SplitHttpClient {
-
- private static final Logger _log = LoggerFactory.getLogger(SplitHttpClientKerberosImpl.class);
- private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control";
- private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache";
- private static final String HEADER_API_KEY = "Authorization";
- private static final String HEADER_CLIENT_KEY = "SplitSDKClientKey";
- private static final String HEADER_CLIENT_MACHINE_NAME = "SplitSDKMachineName";
- private static final String HEADER_CLIENT_MACHINE_IP = "SplitSDKMachineIP";
- private static final String HEADER_CLIENT_VERSION = "SplitSDKVersion";
-
- private final RequestDecorator _requestDecorator;
- private final String _apikey;
- private final SDKMetadata _metadata;
-
- public static SplitHttpClientKerberosImpl create(RequestDecorator requestDecorator,
- String apikey,
- SDKMetadata metadata) {
- return new SplitHttpClientKerberosImpl(requestDecorator, apikey, metadata);
- }
-
- SplitHttpClientKerberosImpl(RequestDecorator requestDecorator,
- String apikey,
- SDKMetadata metadata) {
- _requestDecorator = requestDecorator;
- _apikey = apikey;
- _metadata = metadata;
- }
-
- public synchronized SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders) {
- HttpURLConnection getHttpURLConnection = null;
- try {
- getHttpURLConnection = (HttpURLConnection) uri.toURL().openConnection();
- return doGet(getHttpURLConnection, options, additionalHeaders);
- } catch (Exception e) {
- throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
- } finally {
- try {
- if (getHttpURLConnection != null) {
- getHttpURLConnection.disconnect();
- }
- } catch (Exception e) {
- _log.error(String.format("Could not close HTTP URL Connection: %s", e), e);
- }
- }
- }
- public SplitHttpResponse doGet(HttpURLConnection getHttpURLConnection, FetchOptions options, Map> additionalHeaders) {
- try {
- getHttpURLConnection.setRequestMethod("GET");
- setBasicHeaders(getHttpURLConnection);
- setAdditionalAndDecoratedHeaders(getHttpURLConnection, additionalHeaders);
-
- if (options.cacheControlHeadersEnabled()) {
- getHttpURLConnection.setRequestProperty(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE);
- }
-
- _log.debug(String.format("Request Headers: %s", getHttpURLConnection.getRequestProperties()));
-
- int responseCode = getHttpURLConnection.getResponseCode();
-
- if (_log.isDebugEnabled()) {
- _log.debug(String.format("[%s] %s. Status code: %s",
- getHttpURLConnection.getRequestMethod(),
- getHttpURLConnection.getURL().toString(),
- responseCode));
- }
-
- String statusMessage = "";
- if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
- _log.warn(String.format("Response status was: %s. Reason: %s", responseCode,
- getHttpURLConnection.getResponseMessage()));
- statusMessage = getHttpURLConnection.getResponseMessage();
- }
-
- InputStreamReader inputStreamReader = new InputStreamReader(getHttpURLConnection.getInputStream());
- BufferedReader br = new BufferedReader(inputStreamReader);
- String strCurrentLine;
- StringBuilder bld = new StringBuilder();
- while ((strCurrentLine = br.readLine()) != null) {
- bld.append(strCurrentLine);
- }
- String responseBody = bld.toString();
- inputStreamReader.close();
- return new SplitHttpResponse(responseCode,
- statusMessage,
- responseBody,
- getResponseHeaders(getHttpURLConnection));
- } catch (Exception e) {
- throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
- }
- }
-
- public synchronized SplitHttpResponse post(URI uri, HttpEntity entity, Map> additionalHeaders) throws IOException {
- HttpURLConnection postHttpURLConnection = null;
- try {
- postHttpURLConnection = (HttpURLConnection) uri.toURL().openConnection();
- return doPost(postHttpURLConnection, entity, additionalHeaders);
- } catch (Exception e) {
- throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e);
- } finally {
- try {
- if (postHttpURLConnection != null) {
- postHttpURLConnection.disconnect();
- }
- } catch (Exception e) {
- _log.error(String.format("Could not close URL Connection: %s", e), e);
- }
- }
- }
-
- public SplitHttpResponse doPost(HttpURLConnection postHttpURLConnection,
- HttpEntity entity,
- Map> additionalHeaders) {
- try {
- postHttpURLConnection.setRequestMethod("POST");
- setBasicHeaders(postHttpURLConnection);
- setAdditionalAndDecoratedHeaders(postHttpURLConnection, additionalHeaders);
-
- postHttpURLConnection.setRequestProperty("Accept-Encoding", "gzip");
- postHttpURLConnection.setRequestProperty("Content-Type", "application/json");
- _log.debug(String.format("Request Headers: %s", postHttpURLConnection.getRequestProperties()));
-
- postHttpURLConnection.setDoOutput(true);
- String postBody = EntityUtils.toString(entity);
- OutputStream os = postHttpURLConnection.getOutputStream();
- os.write(postBody.getBytes(StandardCharsets.UTF_8));
- os.flush();
- os.close();
- _log.debug(String.format("Posting: %s", postBody));
-
- int responseCode = postHttpURLConnection.getResponseCode();
- String statusMessage = "";
- if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
- statusMessage = postHttpURLConnection.getResponseMessage();
- _log.warn(String.format("Response status was: %s. Reason: %s", responseCode,
- statusMessage));
- }
- return new SplitHttpResponse(responseCode, statusMessage, "", getResponseHeaders(postHttpURLConnection));
- } catch (Exception e) {
- throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e);
- }
- }
-
- private void setBasicHeaders(HttpURLConnection urlConnection) {
- urlConnection.setRequestProperty(HEADER_API_KEY, "Bearer " + _apikey);
- urlConnection.setRequestProperty(HEADER_CLIENT_VERSION, _metadata.getSdkVersion());
- urlConnection.setRequestProperty(HEADER_CLIENT_MACHINE_IP, _metadata.getMachineIp());
- urlConnection.setRequestProperty(HEADER_CLIENT_MACHINE_NAME, _metadata.getMachineName());
- urlConnection.setRequestProperty(HEADER_CLIENT_KEY, _apikey.length() > 4
- ? _apikey.substring(_apikey.length() - 4)
- : _apikey);
- }
-
- private void setAdditionalAndDecoratedHeaders(HttpURLConnection urlConnection, Map> additionalHeaders) {
- if (additionalHeaders != null) {
- for (Map.Entry> entry : additionalHeaders.entrySet()) {
- for (String value : entry.getValue()) {
- urlConnection.setRequestProperty(entry.getKey(), value);
- }
- }
- }
- HttpRequest request = new HttpGet("");
- _requestDecorator.decorateHeaders(request);
- for (Header header : request.getHeaders()) {
- urlConnection.setRequestProperty(header.getName(), header.getValue());
- }
- }
-
- private Header[] getResponseHeaders(HttpURLConnection urlConnection) {
- List responseHeaders = new ArrayList<>();
- Map> map = urlConnection.getHeaderFields();
- for (Map.Entry> entry : map.entrySet()) {
- if (entry.getKey() != null) {
- BasicHeader responseHeader = new BasicHeader(entry.getKey(), entry.getValue());
- responseHeaders.add(responseHeader);
- }
- }
- return responseHeaders.toArray(new Header[0]);
- }
- @Override
- public void close() throws IOException {
- // Added for compatibility with HttpSplitClient, no action needed as URLConnection objects are closed.
- }
-}
diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java
index abcc551fe..0a154f7d4 100644
--- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java
+++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java
@@ -2,6 +2,7 @@
import io.split.client.utils.LocalhostUtils;
import io.split.grammar.Treatments;
+import io.split.service.SplitHttpClient;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java
index 86b185419..1b640071c 100644
--- a/client/src/test/java/io/split/client/SplitClientConfigTest.java
+++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java
@@ -6,7 +6,6 @@
import io.split.client.impressions.ImpressionsManager;
import io.split.client.dtos.RequestContext;
import io.split.integrations.IntegrationsConfig;
-import io.split.service.HttpAuthScheme;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
@@ -255,16 +254,4 @@ public Map> getHeaderOverrides(RequestContext context) {
Assert.assertNull(config2.customHeaderDecorator());
}
-
- @Test
- public void checkExpectedAuthScheme() {
- SplitClientConfig cfg = SplitClientConfig.builder()
- .authScheme(HttpAuthScheme.KERBEROS)
- .build();
- Assert.assertEquals(HttpAuthScheme.KERBEROS, cfg.authScheme());
-
- cfg = SplitClientConfig.builder()
- .build();
- Assert.assertEquals(null, cfg.authScheme());
- }
}
\ No newline at end of file
diff --git a/client/src/test/java/io/split/client/SplitFactoryImplTest.java b/client/src/test/java/io/split/client/SplitFactoryImplTest.java
index 57441ced6..f2f7e3efc 100644
--- a/client/src/test/java/io/split/client/SplitFactoryImplTest.java
+++ b/client/src/test/java/io/split/client/SplitFactoryImplTest.java
@@ -2,10 +2,7 @@
import io.split.client.impressions.ImpressionsManager;
import io.split.client.utils.FileTypeEnum;
-import io.split.client.utils.SDKMetadata;
import io.split.integrations.IntegrationsConfig;
-import io.split.service.HttpAuthScheme;
-import io.split.service.SplitHttpClientKerberosImpl;
import io.split.storages.enums.OperationMode;
import io.split.storages.pluggable.domain.UserStorageWrapper;
import io.split.telemetry.storage.TelemetryStorage;
@@ -14,10 +11,11 @@
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
+import static org.mockito.Mockito.when;
import pluggable.CustomStorageWrapper;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -25,7 +23,6 @@
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
-import static io.split.client.SplitClientConfig.splitSdkVersion;
public class SplitFactoryImplTest extends TestCase {
public static final String API_KEY ="29013ionasdasd09u";
@@ -140,7 +137,7 @@ public void testFactoryConsumerInstantiation() throws Exception {
CustomStorageWrapper customStorageWrapper = Mockito.mock(CustomStorageWrapper.class);
UserStorageWrapper userStorageWrapper = Mockito.mock(UserStorageWrapper.class);
TelemetrySynchronizer telemetrySynchronizer = Mockito.mock(TelemetrySynchronizer.class);
- Mockito.when(userStorageWrapper.connect()).thenReturn(true);
+ when(userStorageWrapper.connect()).thenReturn(true);
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.enableDebug()
@@ -178,7 +175,7 @@ public void testFactoryConsumerInstantiation() throws Exception {
public void testFactoryConsumerInstantiationRetryReadiness() throws Exception {
CustomStorageWrapper customStorageWrapper = Mockito.mock(CustomStorageWrapper.class);
UserStorageWrapper userStorageWrapper = Mockito.mock(UserStorageWrapper.class);
- Mockito.when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
+ when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.enableDebug()
.impressionsMode(ImpressionsManager.Mode.DEBUG)
@@ -207,7 +204,7 @@ public void testFactoryConsumerInstantiationRetryReadiness() throws Exception {
public void testFactoryConsumerDestroy() throws NoSuchFieldException, URISyntaxException, IllegalAccessException {
CustomStorageWrapper customStorageWrapper = Mockito.mock(CustomStorageWrapper.class);
UserStorageWrapper userStorageWrapper = Mockito.mock(UserStorageWrapper.class);
- Mockito.when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
+ when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.enableDebug()
.impressionsMode(ImpressionsManager.Mode.DEBUG)
@@ -233,7 +230,7 @@ public void testFactoryConsumerDestroy() throws NoSuchFieldException, URISyntaxE
}
@Test
- public void testLocalhostLegacy() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ public void testLocalhostLegacy() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.setBlockUntilReadyTimeout(10000)
.build();
@@ -246,7 +243,7 @@ public void testLocalhostLegacy() throws URISyntaxException, NoSuchMethodExcepti
}
@Test
- public void testLocalhostYaml() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ public void testLocalhostYaml() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile("src/test/resources/split.yaml")
.setBlockUntilReadyTimeout(10000)
@@ -260,7 +257,7 @@ public void testLocalhostYaml() throws URISyntaxException, NoSuchMethodException
}
@Test
- public void testLocalhosJson() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ public void testLocalhosJson() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile("src/test/resources/split_init.json")
.setBlockUntilReadyTimeout(10000)
@@ -275,7 +272,7 @@ public void testLocalhosJson() throws URISyntaxException, NoSuchMethodException,
@Test
public void testLocalhostYamlInputStream() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
- IllegalAccessException, FileNotFoundException {
+ IllegalAccessException, IOException {
InputStream inputStream = new FileInputStream("src/test/resources/split.yaml");
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile(inputStream, FileTypeEnum.YAML)
@@ -291,7 +288,7 @@ public void testLocalhostYamlInputStream() throws URISyntaxException, NoSuchMeth
@Test
public void testLocalhosJsonInputStream() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
- IllegalAccessException, FileNotFoundException {
+ IllegalAccessException, IOException {
InputStream inputStream = new FileInputStream("src/test/resources/split_init.json");
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile(inputStream, FileTypeEnum.JSON)
@@ -306,7 +303,7 @@ public void testLocalhosJsonInputStream() throws URISyntaxException, NoSuchMetho
}
@Test
- public void testLocalhosJsonInputStreamNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ public void testLocalhosJsonInputStreamNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile(null, FileTypeEnum.JSON)
.setBlockUntilReadyTimeout(10000)
@@ -321,7 +318,7 @@ public void testLocalhosJsonInputStreamNull() throws URISyntaxException, NoSuchM
@Test
public void testLocalhosJsonInputStreamAndFileTypeNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
- IllegalAccessException, FileNotFoundException {
+ IllegalAccessException, IOException {
InputStream inputStream = new FileInputStream("src/test/resources/split_init.json");
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile(inputStream, null)
@@ -337,7 +334,7 @@ public void testLocalhosJsonInputStreamAndFileTypeNull() throws URISyntaxExcepti
@Test
public void testLocalhosJsonInputStreamNullAndFileTypeNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
- IllegalAccessException {
+ IllegalAccessException, IOException {
SplitClientConfig splitClientConfig = SplitClientConfig.builder()
.splitFile(null, null)
.setBlockUntilReadyTimeout(10000)
@@ -349,19 +346,4 @@ public void testLocalhosJsonInputStreamNullAndFileTypeNull() throws URISyntaxExc
Object splitChangeFetcher = method.invoke(splitFactory, splitClientConfig);
Assert.assertTrue(splitChangeFetcher instanceof LegacyLocalhostSplitChangeFetcher);
}
-
- @Test
- public void testFactoryKerberosInstance() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- SplitClientConfig splitClientConfig = SplitClientConfig.builder()
- .setBlockUntilReadyTimeout(10000)
- .authScheme(HttpAuthScheme.KERBEROS)
- .build();
- SplitFactoryImpl splitFactory = new SplitFactoryImpl("asdf", splitClientConfig);
-
- Method method = SplitFactoryImpl.class.getDeclaredMethod("buildSplitHttpClient", String.class,
- SplitClientConfig.class, SDKMetadata.class, RequestDecorator.class);
- method.setAccessible(true);
- Object SplitHttpClient = method.invoke(splitFactory, "asdf", splitClientConfig, new SDKMetadata(splitSdkVersion, "", ""), new RequestDecorator(null));
- Assert.assertTrue(SplitHttpClient instanceof SplitHttpClientKerberosImpl);
- }
}
\ No newline at end of file
diff --git a/client/src/test/java/io/split/client/RequestDecoratorTest.java b/client/src/test/java/io/split/client/utils/ApacheRequestDecoratorTest.java
similarity index 80%
rename from client/src/test/java/io/split/client/RequestDecoratorTest.java
rename to client/src/test/java/io/split/client/utils/ApacheRequestDecoratorTest.java
index 62868eb40..5d5971bb8 100644
--- a/client/src/test/java/io/split/client/RequestDecoratorTest.java
+++ b/client/src/test/java/io/split/client/utils/ApacheRequestDecoratorTest.java
@@ -1,32 +1,31 @@
-package io.split.client;
+package io.split.client.utils;
+import io.split.client.CustomHeaderDecorator;
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.RequestContext;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.ProtocolException;
import org.junit.Assert;
import org.junit.Test;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-
-import io.split.client.dtos.RequestContext;
import java.util.List;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.Map;
-public class RequestDecoratorTest {
+public class ApacheRequestDecoratorTest {
@Test
public void testNoOp() {
- RequestDecorator decorator = new RequestDecorator(null);
+ ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
+ RequestDecorator requestDecorator = new RequestDecorator(null);
HttpGet request = new HttpGet("http://anyhost");
- request = (HttpGet) decorator.decorateHeaders(request);
+
+ request = (HttpGet) apacheRequestDecorator.decorate(request, requestDecorator);
Assert.assertEquals(0, request.getHeaders().length);
request.addHeader("myheader", "value");
- request = (HttpGet) decorator.decorateHeaders(request);
+ request = (HttpGet) apacheRequestDecorator.decorate(request, requestDecorator);
Assert.assertEquals(1, request.getHeaders().length);
}
@@ -45,9 +44,11 @@ public Map> getHeaderOverrides(RequestContext context) {
}
MyCustomHeaders myHeaders = new MyCustomHeaders();
RequestDecorator decorator = new RequestDecorator(myHeaders);
+ ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
+
HttpGet request = new HttpGet("http://anyhost");
request.addHeader("first", "myfirstheader");
- request = (HttpGet) decorator.decorateHeaders(request);
+ request = (HttpGet) apacheRequestDecorator.decorate(request, decorator);
Assert.assertEquals(4, request.getHeaders().length);
Assert.assertEquals("1", request.getHeader("first").getValue());
@@ -59,7 +60,7 @@ public Map> getHeaderOverrides(RequestContext context) {
HttpPost request2 = new HttpPost("http://anyhost");
request2.addHeader("myheader", "value");
- request2 = (HttpPost) decorator.decorateHeaders(request2);
+ request2 = (HttpPost) apacheRequestDecorator.decorate(request2, decorator);
Assert.assertEquals(5, request2.getHeaders().length);
}
@@ -88,8 +89,9 @@ public Map> getHeaderOverrides(RequestContext context) {
}
MyCustomHeaders myHeaders = new MyCustomHeaders();
RequestDecorator decorator = new RequestDecorator(myHeaders);
+ ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
HttpGet request = new HttpGet("http://anyhost");
- request = (HttpGet) decorator.decorateHeaders(request);
+ request = (HttpGet) apacheRequestDecorator.decorate(request, decorator);
Assert.assertEquals(1, request.getHeaders().length);
Assert.assertEquals(null, request.getHeader("SplitSDKVersion"));
}
@@ -105,7 +107,8 @@ public Map> getHeaderOverrides(RequestContext context) {
}
MyCustomHeaders myHeaders = new MyCustomHeaders();
RequestDecorator decorator = new RequestDecorator(myHeaders);
+ ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
HttpGet request = new HttpGet("http://anyhost");
- request = (HttpGet) decorator.decorateHeaders(request);
+ request = (HttpGet) apacheRequestDecorator.decorate(request, decorator);
}
}
\ No newline at end of file
diff --git a/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java b/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java
deleted file mode 100644
index 4f814c31d..000000000
--- a/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package io.split.service;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-import io.split.TestHelper;
-import io.split.client.RequestDecorator;
-import io.split.client.dtos.*;
-import io.split.client.impressions.Impression;
-import io.split.client.utils.Json;
-import io.split.client.utils.SDKMetadata;
-import io.split.client.utils.Utils;
-import io.split.engine.common.FetchOptions;
-
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.core5.http.*;
-import org.apache.hc.core5.http.io.entity.EntityUtils;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.io.*;
-import java.lang.reflect.InvocationTargetException;
-import java.net.*;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-
-public class HttpSplitClientKerberosTest {
-
- @Test
- public void testGetWithSpecialCharacters() throws URISyntaxException, IOException {
- Map> responseHeaders = new HashMap>();
- responseHeaders.put((HttpHeaders.VIA), Arrays.asList("HTTP/1.1 s_proxy_rio1"));
-
- HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- when(mockHttpURLConnection.getHeaderFields()).thenReturn(responseHeaders);
-
- RequestDecorator decorator = new RequestDecorator(null);
-
- Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
- ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource(
- new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8));
- when(mockHttpURLConnection.getInputStream()).thenReturn(stubInputStream);
-
- SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata());
-
- Map> additionalHeaders = Collections.singletonMap("AdditionalHeader",
- Collections.singletonList("add"));
-
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doGet(mockHttpURLConnection,
- new FetchOptions.Builder().cacheControlHeaders(true).build(), additionalHeaders);
- SplitChange change = Json.fromJson(splitHttpResponse.body(), SplitChange.class);
-
- Header[] headers = splitHttpResponse.responseHeaders();
- assertThat(headers[0].getName(), is(equalTo("Via")));
- assertThat(headers[0].getValue(), is(equalTo("[HTTP/1.1 s_proxy_rio1]")));
- Assert.assertNotNull(change);
- Assert.assertEquals(1, change.splits.size());
- Assert.assertNotNull(change.splits.get(0));
-
- Split split = change.splits.get(0);
- Map configs = split.configurations;
- Assert.assertEquals(2, configs.size());
- Assert.assertEquals("{\"test\": \"blue\",\"grüne Straße\": 13}", configs.get("on"));
- Assert.assertEquals("{\"test\": \"blue\",\"size\": 15}", configs.get("off"));
- Assert.assertEquals(2, split.sets.size());
- }
-
- @Test
- public void testGetParameters() throws URISyntaxException, MalformedURLException {
- URI uri = new URI("https://api.split.io/splitChanges?since=1234567");
- FetchOptions options = new FetchOptions.Builder().cacheControlHeaders(true).build();
- SplitHttpClientKerberosImpl splitHtpClientKerberos = Mockito.mock(SplitHttpClientKerberosImpl.class);
- when(splitHtpClientKerberos.get(uri, options, null)).thenCallRealMethod();
-
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.get(uri, options, null);
-
- ArgumentCaptor connectionCaptor = ArgumentCaptor.forClass(HttpURLConnection.class);
- ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(FetchOptions.class);
- ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(HashMap.class);
- verify(splitHtpClientKerberos).doGet(connectionCaptor.capture(), optionsCaptor.capture(), headersCaptor.capture());
-
- assertThat(connectionCaptor.getValue().getRequestMethod(), is(equalTo("GET")));
- assertThat(connectionCaptor.getValue().getURL().toString(), is(equalTo(new URL("https://api.split.io/splitChanges?since=1234567").toString())));
-
- assertThat(optionsCaptor.getValue().cacheControlHeadersEnabled(), is(equalTo(true)));
- }
-
- @Test
- public void testGetError() throws URISyntaxException, IOException {
- HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- RequestDecorator decorator = new RequestDecorator(null);
-
- Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR);
- ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource(
- new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8));
- when(mockHttpURLConnection.getInputStream()).thenReturn(stubInputStream);
-
- SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata());
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doGet(mockHttpURLConnection,
- new FetchOptions.Builder().cacheControlHeaders(true).build(), null);
- Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, (long) splitHttpResponse.statusCode());
- }
-
- @Test(expected = IllegalStateException.class)
- public void testException() throws URISyntaxException, InvocationTargetException, NoSuchMethodException,
- IllegalAccessException, IOException {
- HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- RequestDecorator decorator = null;
-
- Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR);
- ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource(
- new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8));
- when(mockHttpURLConnection.getInputStream()).thenReturn(stubInputStream);
-
- SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata());
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doGet(mockHttpURLConnection,
- new FetchOptions.Builder().cacheControlHeaders(true).build(), null);
- }
-
- @Test
- public void testPost() throws URISyntaxException, IOException, ParseException {
- HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- RequestDecorator decorator = new RequestDecorator(null);
- Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
-
- SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata());
-
- // Send impressions
- List toSend = Arrays.asList(new TestImpressions("t1", Arrays.asList(
- KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null)),
- KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null)),
- KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null)))),
- new TestImpressions("t2", Arrays.asList(
- KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null)),
- KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null)),
- KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null)))));
-
- Map> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode",
- Collections.singletonList("OPTIMIZED"));
- when(mockHttpURLConnection.getHeaderFields()).thenReturn(additionalHeaders);
-
- ByteArrayOutputStream mockOs = Mockito.mock( ByteArrayOutputStream.class);
- when(mockHttpURLConnection.getOutputStream()).thenReturn(mockOs);
-
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doPost(mockHttpURLConnection, Utils.toJsonEntity(toSend),
- additionalHeaders);
-
- // Capture outgoing request and validate it
- ArgumentCaptor captor = ArgumentCaptor.forClass(byte[].class);
- verify(mockOs).write(captor.capture());
- String postBody = EntityUtils.toString(Utils.toJsonEntity(toSend));
- assertThat(captor.getValue(), is(equalTo(postBody.getBytes(StandardCharsets.UTF_8))));
-
- Header[] headers = splitHttpResponse.responseHeaders();
- assertThat(headers[0].getName(), is(equalTo("SplitSDKImpressionsMode")));
- assertThat(headers[0].getValue(), is(equalTo("[OPTIMIZED]")));
-
- Assert.assertEquals(200, (long) splitHttpResponse.statusCode());
- }
-
- @Test
- public void testPotParameters() throws URISyntaxException, IOException {
- URI uri = new URI("https://kubernetesturl.com/split/api/testImpressions/bulk");
- SplitHttpClientKerberosImpl splitHtpClientKerberos = Mockito.mock(SplitHttpClientKerberosImpl.class);
- when(splitHtpClientKerberos.post(uri, null, null)).thenCallRealMethod();
-
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.post(uri, null, null);
-
- ArgumentCaptor connectionCaptor = ArgumentCaptor.forClass(HttpURLConnection.class);
- ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
- ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(HashMap.class);
- verify(splitHtpClientKerberos).doPost(connectionCaptor.capture(), entityCaptor.capture(), headersCaptor.capture());
-
- assertThat(connectionCaptor.getValue().getURL().toString(), is(equalTo(new URL("https://kubernetesturl.com/split/api/testImpressions/bulk").toString())));
- }
-
- @Test
- public void testPosttError() throws URISyntaxException, IOException {
- HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- RequestDecorator decorator = new RequestDecorator(null);
- ByteArrayOutputStream mockOs = Mockito.mock( ByteArrayOutputStream.class);
- Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR);
- when(mockHttpURLConnection.getOutputStream()).thenReturn(mockOs);
-
- SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata());
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doPost(mockHttpURLConnection,
- Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
-
- Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, (long) splitHttpResponse.statusCode());
- }
-
- @Test(expected = IllegalStateException.class)
- public void testPosttException() throws URISyntaxException, IOException {
- HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class);
- RequestDecorator decorator = null;
- Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
-
- SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata());
- SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doPost(mockHttpURLConnection,
- Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
- }
-
- private SDKMetadata metadata() {
- return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP");
- }
-
-}
diff --git a/client/src/test/java/io/split/service/HttpSplitClientTest.java b/client/src/test/java/io/split/service/HttpSplitClientTest.java
index 946775f39..4d18a080d 100644
--- a/client/src/test/java/io/split/service/HttpSplitClientTest.java
+++ b/client/src/test/java/io/split/service/HttpSplitClientTest.java
@@ -16,7 +16,7 @@
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.Header;
+//import org.apache.hc.core5.http.Header;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -57,9 +57,9 @@ public void testGetWithSpecialCharacters() throws URISyntaxException, Invocation
HttpUriRequest request = captor.getValue();
assertThat(request.getFirstHeader("AdditionalHeader").getValue(), is(equalTo("add")));
- Header[] headers = splitHttpResponse.responseHeaders();
+ SplitHttpResponse.Header[] headers = splitHttpResponse.responseHeaders();
assertThat(headers[0].getName(), is(equalTo("Via")));
- assertThat(headers[0].getValue(), is(equalTo("HTTP/1.1 m_proxy_rio1")));
+ assertThat(headers[0].getValues().get(0), is(equalTo("HTTP/1.1 m_proxy_rio1")));
Assert.assertNotNull(change);
Assert.assertEquals(1, change.splits.size());
Assert.assertNotNull(change.splits.get(0));
@@ -122,7 +122,7 @@ public void testPost() throws URISyntaxException, IOException, IllegalAccessExce
Map> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode",
Collections.singletonList("OPTIMIZED"));
- SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget, Utils.toJsonEntity(toSend),
+ SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget, Json.toJson(toSend),
additionalHeaders);
// Capture outgoing request and validate it
@@ -152,7 +152,7 @@ public void testPosttNoExceptionOnHttpErrorCode() throws URISyntaxException, Inv
SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, decorator, "qwerty", metadata());
SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget,
- Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
+ Json.toJson(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
Assert.assertEquals(500, (long) splitHttpResponse.statusCode());
}
@@ -165,7 +165,7 @@ public void testPosttException() throws URISyntaxException, InvocationTargetExce
HttpStatus.SC_INTERNAL_SERVER_ERROR);
SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, null, "qwerty", metadata());
- splitHtpClient.post(rootTarget, Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
+ splitHtpClient.post(rootTarget, Json.toJson(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
}
private SDKMetadata metadata() {
diff --git a/client/src/test/resources/org/powermock/extensions/configuration.properties b/client/src/test/resources/org/powermock/extensions/configuration.properties
new file mode 100644
index 000000000..a8ebaeba3
--- /dev/null
+++ b/client/src/test/resources/org/powermock/extensions/configuration.properties
@@ -0,0 +1 @@
+powermock.global-ignore=jdk.internal.reflect.*,javax.net.ssl.*
\ No newline at end of file
diff --git a/okhttp-modules/pom.xml b/okhttp-modules/pom.xml
new file mode 100644
index 000000000..529869307
--- /dev/null
+++ b/okhttp-modules/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+ java-client-parent
+ io.split.client
+ 4.13.0
+
+ 4.0.0
+ 4.13.0
+ okhttp-modules
+ jar
+ http-modules
+ Alternative Http Modules
+
+
+
+ release
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.3
+ true
+
+ false
+
+
+
+
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ 4.12.0
+
+
+ io.split.client
+ java-client
+ 4.13.0
+ compile
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.0.3
+
+
+
+ junit
+ junit
+ test
+
+
+ org.mockito
+ mockito-core
+ 1.10.19
+ test
+
+
+ org.powermock
+ powermock-module-junit4
+ 1.7.4
+ test
+
+
+ org.powermock
+ powermock-api-mockito
+ 1.7.4
+ test
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ 4.8.0
+ test
+
+
+
+
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/HTTPKerberosAuthInterceptor.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/HTTPKerberosAuthInterceptor.java
new file mode 100644
index 000000000..26bd23ea5
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/HTTPKerberosAuthInterceptor.java
@@ -0,0 +1,273 @@
+package io.split.httpmodules.okhttp;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Date;
+import java.util.Set;
+import java.util.Base64;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.Principal;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.kerberos.KerberosTicket;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.Authenticator;
+import okhttp3.Route;
+
+/**
+ *
+ * An HTTP Request interceptor that modifies the request headers to enable
+ * Kerberos authentication. It appends the Kerberos authentication token to the
+ * 'Authorization' request header for Kerberos authentication
+ *
+ * Copyright 2024 MarkLogic Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+public class HTTPKerberosAuthInterceptor implements Authenticator {
+ String host;
+ Map krbOptions;
+ LoginContext loginContext;
+ public HTTPKerberosAuthInterceptor(String host, Map krbOptions) throws IOException {
+ this.host = host;
+ this.krbOptions = krbOptions;
+ try {
+ buildSubjectCredentials();
+ } catch (LoginException e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Class to create Kerberos Configuration object which specifies the Kerberos
+ * Login Module to be used for authentication.
+ *
+ */
+ protected static class KerberosLoginConfiguration extends Configuration {
+ Map krbOptions = null;
+
+ public KerberosLoginConfiguration() {}
+
+ KerberosLoginConfiguration(Map krbOptions) {
+
+ this.krbOptions = krbOptions;
+ }
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ if (krbOptions == null) {
+ throw new IllegalStateException("Cannot create AppConfigurationEntry without Kerberos Options");
+ }
+ return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, krbOptions) };
+ }
+ }
+
+ /**
+ * This method checks the validity of the TGT in the cache and build the
+ * Subject inside the LoginContext using Krb5LoginModule and the TGT cached by
+ * the Kerberos client. It assumes that a valid TGT is already present in the
+ * kerberos client's cache.
+ *
+ * @throws LoginException
+ */
+ protected void buildSubjectCredentials() throws LoginException {
+ Subject subject = new Subject();
+ /**
+ * We are not getting the TGT from KDC here. The actual TGT is got from the
+ * KDC using kinit or equivalent but we use the cached TGT in order to build
+ * the LoginContext and populate the TGT inside the Subject using
+ * Krb5LoginModule
+ */
+
+ LoginContext lc = getLoginContext(subject);
+ lc.login();
+ loginContext = lc;
+ }
+
+ protected LoginContext getLoginContext(Subject subject) throws LoginException {
+ return new LoginContext("Krb5LoginContext", subject, null,
+ (krbOptions != null) ? new KerberosLoginConfiguration(krbOptions) : new KerberosLoginConfiguration());
+ }
+ /**
+ * This method is responsible for getting the client principal name from the
+ * subject's principal set
+ *
+ * @return String the Kerberos principal name populated in the subject
+ * @throws IllegalStateException if there is more than 0 or more than 1
+ * principal is present
+ */
+ protected String getClientPrincipalName() {
+ final Set principalSet = getContextSubject().getPrincipals();
+ if (principalSet.size() != 1)
+ throw new IllegalStateException(
+ "Only one principal is expected. Found 0 or more than one principals :" + principalSet);
+ return principalSet.iterator().next().getName();
+ }
+
+ protected Subject getContextSubject() {
+ Subject subject = loginContext.getSubject();
+ if (subject == null)
+ throw new IllegalStateException("Kerberos login context without subject");
+ return subject;
+ }
+
+ protected CreateAuthorizationHeaderAction getAuthorizationHeaderAction(String clientPrincipal,
+ String serverPrincipalName) {
+ return new CreateAuthorizationHeaderAction(clientPrincipal,
+ serverPrincipalName);
+ }
+
+ /**
+ * This method builds the Authorization header for Kerberos. It
+ * generates a request token based on the service ticket, client principal name and
+ * time-stamp
+ *
+ * @param serverPrincipalName
+ * the name registered with the KDC of the service for which we
+ * need to authenticate
+ * @return the HTTP Authorization header token
+ */
+ protected String buildAuthorizationHeader(String serverPrincipalName) throws LoginException, PrivilegedActionException {
+ /*
+ * Get the principal from the Subject's private credentials and populate the
+ * client and server principal name for the GSS API
+ */
+ final String clientPrincipal = getClientPrincipalName();
+ final CreateAuthorizationHeaderAction action = getAuthorizationHeaderAction(clientPrincipal,
+ serverPrincipalName);
+
+ /*
+ * Check if the TGT in the Subject's private credentials are valid. If
+ * valid, then we use the TGT in the Subject's private credentials. If not,
+ * we build the Subject's private credentials again from valid TGT in the
+ * Kerberos client cache.
+ */
+ Set