Skip to content

Commit

Permalink
HttpRequest changes
Browse files Browse the repository at this point in the history
  • Loading branch information
gthea committed Dec 7, 2023
1 parent e4dff34 commit 5b18941
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.split.android.client.network;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;

import io.split.android.client.utils.logger.Logger;

class HttpRequestHelper {

static HttpURLConnection openConnection(@Nullable Proxy proxy,
@Nullable SplitUrlConnectionAuthenticator proxyAuthenticator,
@NonNull URL url,
@NonNull HttpMethod method,
@NonNull Map<String, String> headers,
boolean useProxyAuthentication) throws IOException {
HttpURLConnection connection;
if (proxy != null) {
connection = (HttpURLConnection) url.openConnection(proxy);
if (useProxyAuthentication && proxyAuthenticator != null) {
connection = proxyAuthenticator.authenticate(connection);
}
} else {
connection = (HttpURLConnection) url.openConnection();
}
connection.setRequestMethod(method.name());
addHeaders(connection, headers);

return connection;
}

static void applyTimeouts(long readTimeout, long connectionTimeout, HttpURLConnection connection) {
if (readTimeout > 0) {
connection.setReadTimeout(getAsInt(readTimeout));
}

if (connectionTimeout > 0) {
connection.setConnectTimeout(getAsInt(connectionTimeout));
}
}

static void applySslConfig(SSLSocketFactory sslSocketFactory, DevelopmentSslConfig developmentSslConfig, HttpURLConnection connection) {
if (sslSocketFactory != null) {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
} else {
Logger.e("Failed to set SSL socket factory.");
}
}

if (developmentSslConfig != null) {
try {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setSSLSocketFactory(developmentSslConfig.getSslSocketFactory());
((HttpsURLConnection) connection).setHostnameVerifier(developmentSslConfig.getHostnameVerifier());
} else {
Logger.e("Failed to set SSL socket factory.");
}
} catch (Exception ex) {
Logger.e("Could not set development SSL config: " + ex.getLocalizedMessage());
}
}
}

private static int getAsInt(long value) {
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) value;
}
}

private static void addHeaders(HttpURLConnection request, Map<String, String> headers) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
if (entry == null) {
continue;
}

request.addRequestProperty(entry.getKey(), entry.getValue());
}
}
}
194 changes: 133 additions & 61 deletions src/main/java/io/split/android/client/network/HttpRequestImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,76 @@

import static com.google.common.base.Preconditions.checkNotNull;

import static io.split.android.client.network.HttpRequestHelper.applySslConfig;
import static io.split.android.client.network.HttpRequestHelper.applyTimeouts;
import static io.split.android.client.network.HttpRequestHelper.openConnection;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.net.ssl.SSLSocketFactory;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import io.split.android.client.utils.logger.Logger;

public class HttpRequestImpl implements HttpRequest {

private static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
private final OkHttpClient mOkHttpClient;
public static final String CONTENT_TYPE = "Content-Type";
public static final String APPLICATION_JSON_CHARSET_UTF_8 = "application/json; charset=utf-8";

private final URI mUri;
private final String mBody;
private final HttpMethod mHttpMethod;
private final Map<String, String> mHeaders;
private final UrlSanitizer mUrlSanitizer;

HttpRequestImpl(@NonNull OkHttpClient okHttpClient, @NonNull URI uri,
@NonNull HttpMethod httpMethod,
@Nullable String body, @NonNull Map<String, String> headers) {
this(okHttpClient, uri, httpMethod, body, headers, new UrlSanitizerImpl());
}

HttpRequestImpl(@NonNull OkHttpClient okHttpClient, @NonNull URI uri,
@Nullable
private final Proxy mProxy;
@Nullable
private final SplitUrlConnectionAuthenticator mProxyAuthenticator;
private final long mReadTimeout;
private final long mConnectionTimeout;
@Nullable
private final DevelopmentSslConfig mDevelopmentSslConfig;
@Nullable
private final SSLSocketFactory mSslSocketFactory;
private final AtomicBoolean mWasRetried = new AtomicBoolean(false);

HttpRequestImpl(@NonNull URI uri,
@NonNull HttpMethod httpMethod,
@Nullable String body, @NonNull Map<String, String> headers,
@Nullable String body,
@NonNull Map<String, String> headers,
@Nullable Proxy proxy,
@Nullable SplitUrlConnectionAuthenticator proxyAuthenticator,
long readTimeout,
long connectionTimeout,
@Nullable DevelopmentSslConfig developmentSslConfig,
SSLSocketFactory sslSocketFactory,
@NonNull UrlSanitizer urlSanitizer) {
mOkHttpClient = checkNotNull(okHttpClient);
mUri = checkNotNull(uri);
mHttpMethod = checkNotNull(httpMethod);
mBody = body;
mHeaders = new HashMap<>(checkNotNull(headers));
mUrlSanitizer = checkNotNull(urlSanitizer);
mHeaders = new HashMap<>(checkNotNull(headers));
mProxy = proxy;
mProxyAuthenticator = proxyAuthenticator;
mReadTimeout = readTimeout;
mConnectionTimeout = connectionTimeout;
mDevelopmentSslConfig = developmentSslConfig;
mSslSocketFactory = sslSocketFactory;
}

@Override
Expand All @@ -58,82 +81,131 @@ public HttpResponse execute() throws HttpException {
case GET:
return getRequest();
case POST: {
try {
return postRequest();
} catch (IOException e) {
throw new HttpException("Error serializing request body: " + e.getLocalizedMessage());
}
return postRequest();
}
default:
throw new IllegalArgumentException("Request HTTP Method not valid: " + mHttpMethod.name());
}
}

private HttpResponse getRequest() throws HttpException {
URL url;
HttpResponse response;
HttpURLConnection connection = null;
try {
connection = setUpConnection(false);
response = buildResponse(connection);

url = mUrlSanitizer.getUrl(mUri);

Request.Builder requestBuilder = new Request.Builder()
.url(url);
addHeaders(requestBuilder);
Request okHttpRequest = requestBuilder.build();
Response okHttpResponse = mOkHttpClient.newCall(okHttpRequest).execute();
response = buildResponse(okHttpResponse);

if (response.getHttpStatus() == HttpURLConnection.HTTP_PROXY_AUTH) {
response = handleProxyAuthentication(response, true);
}
} catch (MalformedURLException e) {
throw new HttpException("URL is malformed: " + e.getLocalizedMessage());
} catch (ProtocolException e) {
throw new HttpException("Http method not allowed: " + e.getLocalizedMessage());
} catch (IOException e) {
throw new HttpException("Something happened while retrieving data: " + e.getLocalizedMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
}

return response;
}

private HttpResponse postRequest() throws IOException {

private HttpResponse postRequest() throws HttpException {
if (mBody == null) {
throw new IOException("Json data is null");
throw new HttpException("Json data is null");
}

URL url = mUri.toURL();
RequestBody body = RequestBody.create(JSON, mBody);
Request.Builder builder = new Request.Builder()
.url(url)
.post(body);

addHeaders(builder);
Request httpOkRequest = builder.build();
Response httpOkResponse = mOkHttpClient.newCall(httpOkRequest).execute();
HttpResponse httpResponse = buildResponse(httpOkResponse);
return httpResponse;
HttpURLConnection connection = null;
HttpResponse response;
try {
connection = setUpPostConnection(false);
response = buildResponse(connection);

if (response.getHttpStatus() == HttpURLConnection.HTTP_PROXY_AUTH) {
response = handleProxyAuthentication(response, false);
}
} catch (IOException e) {
throw new HttpException("Something happened while posting data: " + e.getLocalizedMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
}

return response;
}

private void addHeaders(Request.Builder request) {
for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
request.header(entry.getKey(), entry.getValue());
private HttpResponse handleProxyAuthentication(HttpResponse originalResponse, boolean isGet) throws HttpException {
HttpURLConnection connection = null;
if (!mWasRetried.getAndSet(true)) {
try {
Logger.d("Retrying with proxy authentication");
connection = (isGet) ? setUpConnection(true) : setUpPostConnection(true);
return buildResponse(connection);
} catch (IOException ex) {
throw new HttpException("Something happened while retrieving data: " + ex.getLocalizedMessage());
} finally {
if (connection != null) {
connection.disconnect();
}
}
}

return originalResponse;
}

private HttpResponse buildResponse(Response okHttpResponse) throws IOException {
int responseCode = okHttpResponse.code();
if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < 300) {
BufferedReader in = new BufferedReader(new InputStreamReader(
okHttpResponse.body().byteStream()));
private HttpURLConnection setUpPostConnection(boolean useProxyAuthenticator) throws IOException {
HttpURLConnection connection = setUpConnection(useProxyAuthenticator);

connection.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON_CHARSET_UTF_8);

if (!mBody.trim().isEmpty()) {
connection.setDoOutput(true);
try (OutputStream bodyStream = connection.getOutputStream()) {
bodyStream.write(mBody.getBytes());
bodyStream.flush();
}
}

return connection;
}

@NonNull
private HttpURLConnection setUpConnection(boolean authenticate) throws IOException {
URL url = mUrlSanitizer.getUrl(mUri);
if (url == null) {
throw new IOException("Error parsing URL");
}

HttpURLConnection connection = openConnection(mProxy, mProxyAuthenticator, url, mHttpMethod, mHeaders, authenticate);
applyTimeouts(mReadTimeout, mConnectionTimeout, connection);
applySslConfig(mSslSocketFactory, mDevelopmentSslConfig, connection);

return connection;
}

String inputLine;
private static HttpResponse buildResponse(HttpURLConnection connection) throws IOException {
int responseCode = connection.getResponseCode();

if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < 300) {
StringBuilder responseData = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
responseData.append(inputLine);
try (InputStream inputStream = connection.getInputStream()) {
if (inputStream != null) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
responseData.append(inputLine);
}
}
}
}
in.close();

return new HttpResponseImpl(responseCode, (responseData.length() > 0 ? responseData.toString() : null));
}

return new HttpResponseImpl(responseCode);
}

}

0 comments on commit 5b18941

Please sign in to comment.