diff --git a/http/README.md b/http/README.md index 199f22805..8ece8d648 100644 --- a/http/README.md +++ b/http/README.md @@ -1,5 +1,58 @@ # HTTP module -This module provides the HTTP client implementation used internally by the Split SDK. +Internal HTTP client for the Split SDK. Not exposed to SDK consumers. -Includes request/response lifecycle, certificate pinning runtime, proxy tunnelling, and TLS configuration. Hidden from SDK consumers. +## Building an `HttpClient` + +Use `HttpClientImpl.Builder` to create an instance: + +```java +HttpClient client = new HttpClientImpl.Builder() + .setConnectionTimeout(15_000) + .setReadTimeout(15_000) + .setTlsUpdater(tlsUpdater) // optional – TlsUpdater SPI + .setProxy(httpProxy) // optional – proxy config from :http-domain + .setProxyAuthenticator(authenticator) // optional – SplitAuthenticator from :http-domain + .setCertificatePinningConfiguration(pinConfig) // optional – cert pins from :http-domain + .setDevelopmentSslConfig(devSslConfig) // optional – dev/test SSL overrides + .build(); +``` + +## Making requests + +```java +// Simple GET +HttpRequest req = client.request(uri, HttpMethod.GET); +HttpResponse resp = req.execute(); + +// POST with body and extra headers +HttpRequest post = client.request(uri, HttpMethod.POST, jsonBody, extraHeaders); +HttpResponse resp = post.execute(); + +// SSE streaming +HttpStreamRequest stream = client.streamRequest(uri); +HttpStreamResponse streamResp = stream.execute(); +``` + +## Global headers + +```java +client.setHeader("Authorization", "Bearer " + apiKey); +client.addHeaders(commonHeaders); + +// Streaming-specific headers (only applied to streamRequest calls) +client.setStreamingHeader("SplitSDKClientKey", clientKey); +``` + +## TLS on older devices + +Implement the `TlsUpdater` SPI and pass it to the builder. The client calls `couldBeOld()` to decide whether to force TLS 1.2 via `Tls12OnlySocketFactory`. + +## URI building + +```java +URI uri = new URIBuilder("https://sdk.split.io/api") + .addPath("splitChanges") + .addParameter("since", "-1") + .build(); +``` diff --git a/http/build.gradle b/http/build.gradle index 85ef7d707..f613652b7 100644 --- a/http/build.gradle +++ b/http/build.gradle @@ -16,7 +16,7 @@ android { dependencies { implementation libs.annotation implementation project(':logger') - implementation project(':http-domain') + api project(':http-domain') testImplementation libs.junit4 testImplementation libs.mockitoCore diff --git a/main/src/main/java/io/split/android/client/network/Base64Encoder.java b/http/src/main/java/io/split/android/client/network/Base64Encoder.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/Base64Encoder.java rename to http/src/main/java/io/split/android/client/network/Base64Encoder.java diff --git a/main/src/main/java/io/split/android/client/network/BaseHttpResponse.java b/http/src/main/java/io/split/android/client/network/BaseHttpResponse.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/BaseHttpResponse.java rename to http/src/main/java/io/split/android/client/network/BaseHttpResponse.java diff --git a/main/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java b/http/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java rename to http/src/main/java/io/split/android/client/network/BaseHttpResponseImpl.java diff --git a/main/src/main/java/io/split/android/client/network/CertificateChecker.java b/http/src/main/java/io/split/android/client/network/CertificateChecker.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/CertificateChecker.java rename to http/src/main/java/io/split/android/client/network/CertificateChecker.java diff --git a/main/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java b/http/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java rename to http/src/main/java/io/split/android/client/network/CertificateCheckerImpl.java diff --git a/main/src/main/java/io/split/android/client/network/ChainCleaner.java b/http/src/main/java/io/split/android/client/network/ChainCleaner.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ChainCleaner.java rename to http/src/main/java/io/split/android/client/network/ChainCleaner.java diff --git a/main/src/main/java/io/split/android/client/network/ChainCleanerImpl.java b/http/src/main/java/io/split/android/client/network/ChainCleanerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ChainCleanerImpl.java rename to http/src/main/java/io/split/android/client/network/ChainCleanerImpl.java diff --git a/http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java b/http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java new file mode 100644 index 000000000..4106c7784 --- /dev/null +++ b/http/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java @@ -0,0 +1,22 @@ +package io.split.android.client.network; + +import android.util.Base64; + +class DefaultBase64Encoder implements Base64Encoder { + + @Override + public String encode(String value) { + if (value == null) { + return null; + } + return Base64.encodeToString(value.getBytes(), Base64.NO_WRAP); + } + + @Override + public String encode(byte[] bytes) { + if (bytes == null) { + return null; + } + return Base64.encodeToString(bytes, Base64.NO_WRAP); + } +} diff --git a/main/src/main/java/io/split/android/client/network/HttpClient.java b/http/src/main/java/io/split/android/client/network/HttpClient.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpClient.java rename to http/src/main/java/io/split/android/client/network/HttpClient.java diff --git a/main/src/main/java/io/split/android/client/network/HttpClientImpl.java b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java similarity index 94% rename from main/src/main/java/io/split/android/client/network/HttpClientImpl.java rename to http/src/main/java/io/split/android/client/network/HttpClientImpl.java index f41271796..d846462b7 100644 --- a/main/src/main/java/io/split/android/client/network/HttpClientImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpClientImpl.java @@ -1,7 +1,5 @@ package io.split.android.client.network; -import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -20,8 +18,6 @@ import javax.net.ssl.SSLSocketFactory; -import io.split.android.client.utils.Base64Util; -import io.split.android.client.utils.Utils; import io.split.android.client.utils.logger.Logger; public class HttpClientImpl implements HttpClient { @@ -180,7 +176,7 @@ private SplitUrlConnectionAuthenticator initializeProxyAuthenticator(HttpProxy p return null; } else if (proxyAuthenticator != null) { return new SplitUrlConnectionAuthenticator(proxyAuthenticator); - } else if (!Utils.isNullOrEmpty(proxy.getUsername())) { + } else if (proxy.getUsername() != null && !proxy.getUsername().isEmpty()) { return createBasicAuthenticator(proxy.getUsername(), proxy.getPassword()); } @@ -188,18 +184,7 @@ private SplitUrlConnectionAuthenticator initializeProxyAuthenticator(HttpProxy p } private static SplitUrlConnectionAuthenticator createBasicAuthenticator(String username, String password) { - return new SplitUrlConnectionAuthenticator(new SplitBasicAuthenticator(username, password, new Base64Encoder() { - - @Override - public String encode(String value) { - return Base64Util.encode(value); - } - - @Override - public String encode(byte[] bytes) { - return Base64Util.encode(bytes); - } - })); + return new SplitUrlConnectionAuthenticator(new SplitBasicAuthenticator(username, password, new DefaultBase64Encoder())); } public static class Builder { @@ -211,14 +196,15 @@ public static class Builder { private long mConnectionTimeout = -1; private DevelopmentSslConfig mDevelopmentSslConfig = null; private SSLSocketFactory mSslSocketFactory = null; - private Context mHostAppContext; + @Nullable + private TlsUpdater mTlsUpdater; private UrlSanitizer mUrlSanitizer; private CertificatePinningConfiguration mCertificatePinningConfiguration; private CertificateChecker mCertificateChecker; private Base64Decoder mBase64Decoder = new DefaultBase64Decoder(); - public Builder setContext(Context context) { - mHostAppContext = context; + public Builder setTlsUpdater(@Nullable TlsUpdater tlsUpdater) { + mTlsUpdater = tlsUpdater; return this; } @@ -279,13 +265,13 @@ Builder setBase64Decoder(Base64Decoder base64Decoder) { public HttpClient build() { if (mDevelopmentSslConfig == null) { - if (LegacyTlsUpdater.couldBeOld()) { - LegacyTlsUpdater.update(mHostAppContext); + if (mTlsUpdater != null && mTlsUpdater.couldBeOld()) { + mTlsUpdater.update(); } if (mProxy != null) { mSslSocketFactory = createSslSocketFactoryFromProxy(mProxy); - } else if (LegacyTlsUpdater.couldBeOld()) { + } else if (mTlsUpdater != null && mTlsUpdater.couldBeOld()) { try { mSslSocketFactory = new Tls12OnlySocketFactory(); } catch (NoSuchAlgorithmException | KeyManagementException e) { diff --git a/main/src/main/java/io/split/android/client/network/HttpException.java b/http/src/main/java/io/split/android/client/network/HttpException.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpException.java rename to http/src/main/java/io/split/android/client/network/HttpException.java diff --git a/main/src/main/java/io/split/android/client/network/HttpMethod.java b/http/src/main/java/io/split/android/client/network/HttpMethod.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpMethod.java rename to http/src/main/java/io/split/android/client/network/HttpMethod.java diff --git a/main/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java b/http/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java rename to http/src/main/java/io/split/android/client/network/HttpOverTunnelExecutor.java diff --git a/main/src/main/java/io/split/android/client/network/HttpRequest.java b/http/src/main/java/io/split/android/client/network/HttpRequest.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpRequest.java rename to http/src/main/java/io/split/android/client/network/HttpRequest.java diff --git a/main/src/main/java/io/split/android/client/network/HttpRequestHelper.java b/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java similarity index 97% rename from main/src/main/java/io/split/android/client/network/HttpRequestHelper.java rename to http/src/main/java/io/split/android/client/network/HttpRequestHelper.java index 4688f00b7..14e5a5b06 100644 --- a/main/src/main/java/io/split/android/client/network/HttpRequestHelper.java +++ b/http/src/main/java/io/split/android/client/network/HttpRequestHelper.java @@ -1,6 +1,5 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.getAsInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -100,6 +99,13 @@ static void applyTimeouts(long readTimeout, long connectionTimeout, HttpURLConne } } + private static int getAsInt(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) value; + } + static void applySslConfig(SSLSocketFactory sslSocketFactory, DevelopmentSslConfig developmentSslConfig, HttpURLConnection connection) { if (sslSocketFactory != null) { if (connection instanceof HttpsURLConnection) { diff --git a/main/src/main/java/io/split/android/client/network/HttpRequestImpl.java b/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java similarity index 94% rename from main/src/main/java/io/split/android/client/network/HttpRequestImpl.java rename to http/src/main/java/io/split/android/client/network/HttpRequestImpl.java index 1f2a0c402..669dd8598 100644 --- a/main/src/main/java/io/split/android/client/network/HttpRequestImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpRequestImpl.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import static io.split.android.client.network.HttpRequestHelper.applySslConfig; import static io.split.android.client.network.HttpRequestHelper.applyTimeouts; @@ -29,7 +29,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.utils.logger.Logger; public class HttpRequestImpl implements HttpRequest { @@ -37,6 +36,12 @@ public class HttpRequestImpl implements HttpRequest { public static final String CONTENT_TYPE = "Content-Type"; public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=utf-8"; + /** + * Non-retryable status code for SSL errors. + * Mirrors HttpStatus.INTERNAL_NON_RETRYABLE from :main. + */ + static final int NON_RETRYABLE_STATUS_CODE = 9009; + private final URI mUri; private final String mBody; private final HttpMethod mHttpMethod; @@ -73,11 +78,11 @@ public class HttpRequestImpl implements HttpRequest { @Nullable SSLSocketFactory sslSocketFactory, @NonNull UrlSanitizer urlSanitizer, @Nullable CertificateChecker certificateChecker) { - mUri = checkNotNull(uri); - mHttpMethod = checkNotNull(httpMethod); + mUri = requireNonNull(uri); + mHttpMethod = requireNonNull(httpMethod); mBody = body; - mUrlSanitizer = checkNotNull(urlSanitizer); - mHeaders = new HashMap<>(checkNotNull(headers)); + mUrlSanitizer = requireNonNull(urlSanitizer); + mHeaders = new HashMap<>(requireNonNull(headers)); mProxy = proxy; mHttpProxy = httpProxy; mProxyAuthenticator = proxyAuthenticator; @@ -119,7 +124,7 @@ private HttpResponse getRequest(AtomicBoolean wasRetried) throws HttpException { } catch (ProtocolException e) { throw new HttpException("Http method not allowed: " + e.getLocalizedMessage()); } catch (SSLPeerUnverifiedException e) { - throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), HttpStatus.INTERNAL_NON_RETRYABLE.getCode()); + throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), NON_RETRYABLE_STATUS_CODE); } catch (IOException e) { throw new HttpException("Something happened while retrieving data: " + e.getLocalizedMessage()); } finally { @@ -146,7 +151,7 @@ private HttpResponse postRequest(AtomicBoolean wasRetried) throws HttpException response = handleProxyAuthentication(response, false, wasRetried); } } catch (SSLPeerUnverifiedException e) { - throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), HttpStatus.INTERNAL_NON_RETRYABLE.getCode()); + throw new HttpException("SSL Peer Unverified: " + e.getLocalizedMessage(), NON_RETRYABLE_STATUS_CODE); } catch (IOException e) { throw new HttpException("Something happened while posting data: " + e.getLocalizedMessage()); } finally { diff --git a/main/src/main/java/io/split/android/client/network/HttpResponse.java b/http/src/main/java/io/split/android/client/network/HttpResponse.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpResponse.java rename to http/src/main/java/io/split/android/client/network/HttpResponse.java diff --git a/main/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java b/http/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java rename to http/src/main/java/io/split/android/client/network/HttpResponseConnectionAdapter.java diff --git a/main/src/main/java/io/split/android/client/network/HttpResponseImpl.java b/http/src/main/java/io/split/android/client/network/HttpResponseImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpResponseImpl.java rename to http/src/main/java/io/split/android/client/network/HttpResponseImpl.java diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamRequest.java b/http/src/main/java/io/split/android/client/network/HttpStreamRequest.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpStreamRequest.java rename to http/src/main/java/io/split/android/client/network/HttpStreamRequest.java diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java b/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java similarity index 96% rename from main/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java rename to http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java index d6f48b8d9..c8573e235 100644 --- a/main/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java +++ b/http/src/main/java/io/split/android/client/network/HttpStreamRequestImpl.java @@ -2,7 +2,7 @@ import static io.split.android.client.network.HttpRequestHelper.checkPins; import static io.split.android.client.network.HttpRequestHelper.createConnection; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import static io.split.android.client.network.HttpRequestHelper.applySslConfig; import static io.split.android.client.network.HttpRequestHelper.applyTimeouts; @@ -28,7 +28,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocketFactory; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.utils.logger.Logger; public class HttpStreamRequestImpl implements HttpStreamRequest { @@ -72,11 +71,11 @@ public class HttpStreamRequestImpl implements HttpStreamRequest { @Nullable HttpProxy httpProxy, @Nullable ProxyCredentialsProvider proxyCredentialsProvider, @Nullable ProxyCacertConnectionHandler proxyCacertConnectionHandler) { - mUri = checkNotNull(uri); + mUri = requireNonNull(uri); mHttpMethod = HttpMethod.GET; mProxy = proxy; - mUrlSanitizer = checkNotNull(urlSanitizer); - mHeaders = new HashMap<>(checkNotNull(headers)); + mUrlSanitizer = requireNonNull(urlSanitizer); + mHeaders = new HashMap<>(requireNonNull(headers)); mProxyAuthenticator = proxyAuthenticator; mConnectionTimeout = connectionTimeout; mDevelopmentSslConfig = developmentSslConfig; @@ -141,7 +140,7 @@ private HttpStreamResponse getRequest() throws HttpException, IOException { throw new HttpException("Http method not allowed: " + e.getLocalizedMessage()); } catch (SSLPeerUnverifiedException e) { disconnect(); - throw new HttpException("SSL peer not verified: " + e.getLocalizedMessage(), HttpStatus.INTERNAL_NON_RETRYABLE.getCode()); + throw new HttpException("SSL peer not verified: " + e.getLocalizedMessage(), HttpRequestImpl.NON_RETRYABLE_STATUS_CODE); } catch (SocketException e) { disconnect(); // Let socket-related IOExceptions pass through unwrapped for consistent error handling diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamResponse.java b/http/src/main/java/io/split/android/client/network/HttpStreamResponse.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpStreamResponse.java rename to http/src/main/java/io/split/android/client/network/HttpStreamResponse.java diff --git a/main/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java b/http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java rename to http/src/main/java/io/split/android/client/network/HttpStreamResponseImpl.java diff --git a/main/src/main/java/io/split/android/client/network/PercentEscaper.java b/http/src/main/java/io/split/android/client/network/PercentEscaper.java similarity index 97% rename from main/src/main/java/io/split/android/client/network/PercentEscaper.java rename to http/src/main/java/io/split/android/client/network/PercentEscaper.java index b61bed710..9f99ceb8e 100644 --- a/main/src/main/java/io/split/android/client/network/PercentEscaper.java +++ b/http/src/main/java/io/split/android/client/network/PercentEscaper.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; /** * Based on Guava PercentEscaper @@ -37,7 +37,7 @@ final class PercentEscaper extends UnicodeEscaper { * @throws IllegalArgumentException if any of the parameters were invalid */ public PercentEscaper(String safeChars, boolean plusForSpace) { - checkNotNull(safeChars); // eager for GWT. + requireNonNull(safeChars); // eager for GWT. // Avoid any misunderstandings about the behavior of this escaper if (safeChars.matches(".*[0-9A-Za-z].*")) { throw new IllegalArgumentException( @@ -78,7 +78,7 @@ private static boolean[] createSafeOctets(String safeChars) { */ @Override protected int nextEscapeIndex(CharSequence csq, int index, int end) { - checkNotNull(csq); + requireNonNull(csq); for (; index < end; index++) { char c = csq.charAt(index); if (c >= safeOctets.length || !safeOctets[c]) { @@ -94,7 +94,7 @@ protected int nextEscapeIndex(CharSequence csq, int index, int end) { */ @Override public String escape(String s) { - checkNotNull(s); + requireNonNull(s); int slen = s.length(); for (int index = 0; index < slen; index++) { char c = s.charAt(index); diff --git a/main/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java b/http/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java rename to http/src/main/java/io/split/android/client/network/ProxyCacertConnectionHandler.java diff --git a/main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java b/http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java rename to http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProvider.java diff --git a/main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java b/http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java similarity index 99% rename from main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java rename to http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java index 49a84c134..8978258cf 100644 --- a/main/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java +++ b/http/src/main/java/io/split/android/client/network/ProxySslSocketFactoryProviderImpl.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -43,7 +43,7 @@ class ProxySslSocketFactoryProviderImpl implements ProxySslSocketFactoryProvider } ProxySslSocketFactoryProviderImpl(@NonNull Base64Decoder base64Decoder) { - mBase64Decoder = checkNotNull(base64Decoder); + mBase64Decoder = requireNonNull(base64Decoder); } @Override diff --git a/main/src/main/java/io/split/android/client/network/RawHttpResponseParser.java b/http/src/main/java/io/split/android/client/network/RawHttpResponseParser.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/RawHttpResponseParser.java rename to http/src/main/java/io/split/android/client/network/RawHttpResponseParser.java diff --git a/main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java b/http/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java rename to http/src/main/java/io/split/android/client/network/SplitAuthenticatedRequest.java diff --git a/main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java b/http/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java rename to http/src/main/java/io/split/android/client/network/SplitBasicAuthenticator.java diff --git a/main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java b/http/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java rename to http/src/main/java/io/split/android/client/network/SplitUrlConnectionAuthenticator.java diff --git a/main/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java b/http/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java rename to http/src/main/java/io/split/android/client/network/SslProxyTunnelEstablisher.java diff --git a/main/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java b/http/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java rename to http/src/main/java/io/split/android/client/network/Tls12OnlySocketFactory.java diff --git a/http/src/main/java/io/split/android/client/network/TlsUpdater.java b/http/src/main/java/io/split/android/client/network/TlsUpdater.java new file mode 100644 index 000000000..4fff431f4 --- /dev/null +++ b/http/src/main/java/io/split/android/client/network/TlsUpdater.java @@ -0,0 +1,14 @@ +package io.split.android.client.network; + +public interface TlsUpdater { + + /** + * Return true if the device may need a TLS update. + */ + boolean couldBeOld(); + + /** + * Perform the TLS update. + */ + void update(); +} diff --git a/main/src/main/java/io/split/android/client/network/TrustManagerProvider.java b/http/src/main/java/io/split/android/client/network/TrustManagerProvider.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/TrustManagerProvider.java rename to http/src/main/java/io/split/android/client/network/TrustManagerProvider.java diff --git a/main/src/main/java/io/split/android/client/network/URIBuilder.java b/http/src/main/java/io/split/android/client/network/URIBuilder.java similarity index 76% rename from main/src/main/java/io/split/android/client/network/URIBuilder.java rename to http/src/main/java/io/split/android/client/network/URIBuilder.java index e5aacc0e5..3611aaf27 100644 --- a/main/src/main/java/io/split/android/client/network/URIBuilder.java +++ b/http/src/main/java/io/split/android/client/network/URIBuilder.java @@ -1,25 +1,23 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; import androidx.annotation.NonNull; -import androidx.core.util.Pair; - import java.net.URI; import java.net.URISyntaxException; +import java.util.AbstractMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; -import io.split.android.client.utils.Utils; - public class URIBuilder { private final URI mRootURI; - private final Set> mParams; + private final Set> mParams; private String mPath; private String mQueryString; public URIBuilder(@NonNull URI rootURI, String path) { - mRootURI = checkNotNull(rootURI); + mRootURI = requireNonNull(rootURI); String rootPath = mRootURI.getRawPath(); if (path != null && rootPath != null) { mPath = String.format("%s/%s", rootPath, path); @@ -40,13 +38,13 @@ public URIBuilder(@NonNull URI rootURI) { public URIBuilder addParameter(@NonNull String param, @NonNull String value) { if (param != null && value != null) { - mParams.add(new Pair<>(param, value)); + mParams.add(new AbstractMap.SimpleEntry<>(param, value)); } return this; } public URIBuilder defaultQueryString(@NonNull String queryString) { - if (!Utils.isNullOrEmpty(queryString)) { + if (queryString != null && !queryString.isEmpty()) { mQueryString = queryString; } return this; @@ -57,14 +55,14 @@ public URI build() throws URISyntaxException { String params = null; if (mParams.size() > 0) { StringBuilder query = new StringBuilder(); - for (Pair param : mParams) { - query.append(param.first).append("=").append(param.second).append("&"); + for (Map.Entry param : mParams) { + query.append(param.getKey()).append("=").append(param.getValue()).append("&"); } params = query.substring(0, query.length() - 1); } - if (!Utils.isNullOrEmpty(mQueryString)) { - if (!Utils.isNullOrEmpty(params)) { + if (mQueryString != null && !mQueryString.isEmpty()) { + if (params != null && !params.isEmpty()) { if (!"&".equals(mQueryString.substring(0, 1))) { params = params + "&"; } diff --git a/main/src/main/java/io/split/android/client/network/UnicodeEscaper.java b/http/src/main/java/io/split/android/client/network/UnicodeEscaper.java similarity index 98% rename from main/src/main/java/io/split/android/client/network/UnicodeEscaper.java rename to http/src/main/java/io/split/android/client/network/UnicodeEscaper.java index 4ed19ab54..7f3f6fd67 100644 --- a/main/src/main/java/io/split/android/client/network/UnicodeEscaper.java +++ b/http/src/main/java/io/split/android/client/network/UnicodeEscaper.java @@ -1,6 +1,6 @@ package io.split.android.client.network; -import static io.split.android.client.utils.Utils.checkNotNull; +import static java.util.Objects.requireNonNull; /** * Based on Guava UnicodeEscaper @@ -14,7 +14,7 @@ protected UnicodeEscaper() {} protected abstract char[] escape(int cp); public String escape(String string) { - checkNotNull(string); + requireNonNull(string); int end = string.length(); int index = nextEscapeIndex(string, 0, end); return index == end ? string : escapeSlow(string, index); @@ -136,7 +136,7 @@ protected final String escapeSlow(String s, int index) { * surrogate character at the end of the sequence */ protected static int codePointAt(CharSequence seq, int index, int end) { - checkNotNull(seq); + requireNonNull(seq); if (index < end) { char c1 = seq.charAt(index++); if (c1 < Character.MIN_HIGH_SURROGATE || c1 > Character.MAX_LOW_SURROGATE) { diff --git a/main/src/main/java/io/split/android/client/network/UrlEscapers.java b/http/src/main/java/io/split/android/client/network/UrlEscapers.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/UrlEscapers.java rename to http/src/main/java/io/split/android/client/network/UrlEscapers.java diff --git a/main/src/main/java/io/split/android/client/network/UrlSanitizer.java b/http/src/main/java/io/split/android/client/network/UrlSanitizer.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/UrlSanitizer.java rename to http/src/main/java/io/split/android/client/network/UrlSanitizer.java diff --git a/main/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java b/http/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java rename to http/src/main/java/io/split/android/client/network/UrlSanitizerImpl.java diff --git a/main/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java b/http/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java rename to http/src/test/java/io/split/android/client/network/CertificateCheckerImplTest.java diff --git a/main/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java b/http/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java rename to http/src/test/java/io/split/android/client/network/ChainCleanerImplTest.java diff --git a/main/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java b/http/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java similarity index 57% rename from main/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java rename to http/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java index 738300ce7..ddbbc5078 100644 --- a/main/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java +++ b/http/src/test/java/io/split/android/client/network/DefaultBase64EncoderTest.java @@ -2,6 +2,8 @@ import static org.mockito.Mockito.mockStatic; +import android.util.Base64; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -9,39 +11,37 @@ import java.nio.charset.StandardCharsets; -import io.split.android.client.utils.Base64Util; - public class DefaultBase64EncoderTest { - + private DefaultBase64Encoder encoder; - private MockedStatic mockedBase64Util; - + private MockedStatic mockedBase64; + @Before public void setUp() { encoder = new DefaultBase64Encoder(); - mockedBase64Util = mockStatic(Base64Util.class); + mockedBase64 = mockStatic(Base64.class); } - + @After public void tearDown() { - mockedBase64Util.close(); + mockedBase64.close(); } - + @Test - public void encodeStringUsesBase64Util() { + public void encodeStringUsesAndroidBase64() { String input = "test string"; - + encoder.encode(input); - - mockedBase64Util.verify(() -> Base64Util.encode(input)); + + mockedBase64.verify(() -> Base64.encodeToString(input.getBytes(), Base64.NO_WRAP)); } - + @Test - public void encodeByteArrayUsesBase64Util() { + public void encodeByteArrayUsesAndroidBase64() { byte[] input = "test bytes".getBytes(StandardCharsets.UTF_8); - + encoder.encode(input); - - mockedBase64Util.verify(() -> Base64Util.encode(input)); + + mockedBase64.verify(() -> Base64.encodeToString(input, Base64.NO_WRAP)); } } diff --git a/main/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java b/http/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java rename to http/src/test/java/io/split/android/client/network/HttpClientTunnellingProxyTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java b/http/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java rename to http/src/test/java/io/split/android/client/network/HttpOverTunnelExecutorTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java b/http/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java rename to http/src/test/java/io/split/android/client/network/HttpRequestHelperTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java b/http/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java rename to http/src/test/java/io/split/android/client/network/HttpResponseConnectionAdapterTest.java diff --git a/main/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java b/http/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java rename to http/src/test/java/io/split/android/client/network/HttpStreamResponseTest.java diff --git a/main/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java b/http/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java rename to http/src/test/java/io/split/android/client/network/ProxySslSocketFactoryProviderImplTest.java diff --git a/main/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java b/http/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java rename to http/src/test/java/io/split/android/client/network/RawHttpResponseParserTest.java diff --git a/main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java b/http/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java rename to http/src/test/java/io/split/android/client/network/SplitAuthenticatorTest.java diff --git a/main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java b/http/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java rename to http/src/test/java/io/split/android/client/network/SplitBasicAuthenticatorTest.java diff --git a/main/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java b/http/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java rename to http/src/test/java/io/split/android/client/network/SplitUrlConnectionAuthenticatorTest.java diff --git a/main/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java b/http/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java rename to http/src/test/java/io/split/android/client/network/SslProxyTunnelEstablisherTest.java diff --git a/main/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java b/http/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java rename to http/src/test/java/io/split/android/client/network/TrustManagerProviderTest.java diff --git a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java index 8bb12d71f..0016b6a76 100644 --- a/main/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/main/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -33,6 +33,7 @@ import io.split.android.client.lifecycle.SplitLifecycleManagerImpl; import io.split.android.client.network.HttpClient; import io.split.android.client.network.HttpClientImpl; +import io.split.android.client.network.LegacyTlsUpdaterAdapter; import io.split.android.client.service.CleanUpDatabaseTask; import io.split.android.client.service.SplitApiFacade; import io.split.android.client.service.executor.SplitSingleThreadTaskExecutor; @@ -385,7 +386,7 @@ private static HttpClient getHttpClient(@NonNull String apiToken, .setConnectionTimeout(config.connectionTimeout()) .setReadTimeout(config.readTimeout()) .setDevelopmentSslConfig(config.developmentSslConfig()) - .setContext(context) + .setTlsUpdater(new LegacyTlsUpdaterAdapter(context)) .setProxyAuthenticator(config.authenticator()); if (config.proxy() != null) { builder.setProxy(config.proxy()); diff --git a/main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java b/main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java deleted file mode 100644 index e1333ca80..000000000 --- a/main/src/main/java/io/split/android/client/network/DefaultBase64Encoder.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.split.android.client.network; - -import io.split.android.client.utils.Base64Util; - -class DefaultBase64Encoder implements Base64Encoder { - - @Override - public String encode(String value) { - return Base64Util.encode(value); - } - - @Override - public String encode(byte[] bytes) { - return Base64Util.encode(bytes); - } -} diff --git a/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java b/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java new file mode 100644 index 000000000..162fcee9b --- /dev/null +++ b/main/src/main/java/io/split/android/client/network/LegacyTlsUpdaterAdapter.java @@ -0,0 +1,29 @@ +package io.split.android.client.network; + +import android.content.Context; + +import androidx.annotation.Nullable; + +/** + * Adapter that bridges the :http module's {@link TlsUpdater} interface with the + * :main module's {@link LegacyTlsUpdater} class. + */ +public class LegacyTlsUpdaterAdapter implements TlsUpdater { + + @Nullable + private final Context mContext; + + public LegacyTlsUpdaterAdapter(@Nullable Context context) { + mContext = context; + } + + @Override + public boolean couldBeOld() { + return LegacyTlsUpdater.couldBeOld(); + } + + @Override + public void update() { + LegacyTlsUpdater.update(mContext); + } +} diff --git a/main/src/main/java/io/split/android/client/utils/Utils.java b/main/src/main/java/io/split/android/client/utils/Utils.java index 8341d776c..ff8e7d4eb 100644 --- a/main/src/main/java/io/split/android/client/utils/Utils.java +++ b/main/src/main/java/io/split/android/client/utils/Utils.java @@ -55,14 +55,6 @@ public static void checkArgument(boolean expression) { } } - public static int getAsInt(long value) { - if (value > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } else { - return (int) value; - } - } - public static List> partition(List list, int size) { if (list == null) { return new ArrayList<>(); diff --git a/main/src/test/java/io/split/android/client/network/HttpClientTest.java b/main/src/test/java/io/split/android/client/network/HttpClientTest.java index a2f2c8c86..6a4610127 100644 --- a/main/src/test/java/io/split/android/client/network/HttpClientTest.java +++ b/main/src/test/java/io/split/android/client/network/HttpClientTest.java @@ -10,8 +10,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.content.Context; - import androidx.annotation.NonNull; import com.google.gson.reflect.TypeToken; @@ -281,7 +279,6 @@ public MockResponse dispatch(RecordedRequest request) { mProxyServer.start(); HttpClient client = new HttpClientImpl.Builder() - .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) .setProxy(HttpProxy.newBuilder(mProxyServer.getHostName(), mProxyServer.getPort()).buildLegacy()) .build(); @@ -316,7 +313,6 @@ public MockResponse dispatch(RecordedRequest request) { mProxyServer.start(); HttpClient client = new HttpClientImpl.Builder() - .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) .setProxyAuthenticator(new SplitAuthenticator() { @Override @@ -371,7 +367,6 @@ public MockResponse dispatch(RecordedRequest request) { mProxyServer.start(); HttpClient client = new HttpClientImpl.Builder() - .setContext(mock(Context.class)) .setUrlSanitizer(mUrlSanitizerMock) .setProxyAuthenticator(new SplitAuthenticator() { @Override @@ -407,36 +402,30 @@ public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) @Test public void buildUsesTls12FactoryWhenLegacyAndNoProxy() throws Exception { - Context context = mock(Context.class); - - try (MockedStatic legacyMock = Mockito.mockStatic(LegacyTlsUpdater.class)) { - legacyMock.when(LegacyTlsUpdater::couldBeOld).thenReturn(true); + TlsUpdater tlsUpdater = mock(TlsUpdater.class); + when(tlsUpdater.couldBeOld()).thenReturn(true); - HttpClient legacyClient = new HttpClientImpl.Builder() - .setContext(context) - .setUrlSanitizer(mUrlSanitizerMock) - .build(); + HttpClient legacyClient = new HttpClientImpl.Builder() + .setTlsUpdater(tlsUpdater) + .setUrlSanitizer(mUrlSanitizerMock) + .build(); - legacyMock.verify(() -> LegacyTlsUpdater.update(context)); - assertTrue(((HttpClientImpl) legacyClient).getSslSocketFactory() instanceof Tls12OnlySocketFactory); - } + Mockito.verify(tlsUpdater).update(); + assertTrue(((HttpClientImpl) legacyClient).getSslSocketFactory() instanceof Tls12OnlySocketFactory); } @Test public void buildUsesDefaultSslWhenNotLegacyAndNoProxy() throws Exception { - Context context = mock(Context.class); + TlsUpdater tlsUpdater = mock(TlsUpdater.class); + when(tlsUpdater.couldBeOld()).thenReturn(false); - try (MockedStatic legacyMock = Mockito.mockStatic(LegacyTlsUpdater.class)) { - legacyMock.when(LegacyTlsUpdater::couldBeOld).thenReturn(false); - - HttpClient modernClient = new HttpClientImpl.Builder() - .setContext(context) - .setUrlSanitizer(mUrlSanitizerMock) - .build(); + HttpClient modernClient = new HttpClientImpl.Builder() + .setTlsUpdater(tlsUpdater) + .setUrlSanitizer(mUrlSanitizerMock) + .build(); - legacyMock.verify(() -> LegacyTlsUpdater.update(context), Mockito.never()); - assertNull(((HttpClientImpl) modernClient).getSslSocketFactory()); - } + Mockito.verify(tlsUpdater, Mockito.never()).update(); + assertNull(((HttpClientImpl) modernClient).getSslSocketFactory()); }