Skip to content

Commit

Permalink
Merge pull request #431 from okta/use_okhttp_networking
Browse files Browse the repository at this point in the history
Use OkHttp for network calls
  • Loading branch information
rajdeepnanua-okta authored Oct 16, 2024
2 parents a2304e0 + 68ae12b commit c593218
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 167 deletions.
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
install-yarn: true
node-version: 'latest'
- install_android_sdk
- run: brew install git-lfs
- run: git lfs install
- run: git lfs pull
- run: yarn install --frozen-lockfile
- run: yarn build
- run: gem install cocoapods
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 2.13.0

### Other
- [#431](https://github.com/okta/okta-react-native/pull/431) Use OkHttp for networking calls in Android

# 2.12.0

### Other
Expand Down
157 changes: 76 additions & 81 deletions android/src/main/java/com/oktareactnative/HttpClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,166 +12,161 @@

package com.oktareactnative;

import android.annotation.SuppressLint;
import static com.okta.oidc.net.ConnectionParameters.USER_AGENT;

import android.net.Uri;
import android.os.Build;

import androidx.annotation.NonNull;

import com.okta.oidc.BuildConfig;
import com.okta.oidc.net.ConnectionParameters;
import com.okta.oidc.net.OktaHttpClient;
import com.okta.oidc.net.request.TLSSocketFactory;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HttpsURLConnection;

import static com.okta.oidc.net.ConnectionParameters.USER_AGENT;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class HttpClientImpl implements OktaHttpClient {
private final String userAgentTemplate;
private final int connectTimeoutMs;
private final int readTimeoutMs;

private HttpURLConnection mUrlConnection;
protected static OkHttpClient sOkHttpClient;
protected volatile Call mCall;
protected Response mResponse;
protected Exception mException;

HttpClientImpl(String userAgentTemplate, int connectTimeoutMs, int readTimeoutMs) {
this.userAgentTemplate = userAgentTemplate;
this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs;
}

/*
* TLS v1.1, v1.2 in Android supports starting from API 16.
* But it enabled by default starting from API 20.
* This method enable these TLS versions on API < 20.
* */
@SuppressLint("RestrictedApi")
private void enableTlsV1_2(HttpURLConnection urlConnection) {
try {
((HttpsURLConnection) urlConnection)
.setSSLSocketFactory(new TLSSocketFactory());
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException("Cannot create SSLContext.", e);
}
}

private String getUserAgent() {
String sdkVersion = "okta-oidc-android/" + BuildConfig.VERSION_NAME;
return userAgentTemplate.replace("$UPSTREAM_SDK", sdkVersion);
}

protected HttpURLConnection openConnection(URL url, ConnectionParameters params)
throws IOException {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (mUrlConnection instanceof HttpsURLConnection &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
enableTlsV1_2(mUrlConnection);
protected Request buildRequest(Uri uri, ConnectionParameters param) {
if (sOkHttpClient == null) {
sOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(connectTimeoutMs, TimeUnit.MILLISECONDS)
.readTimeout(readTimeoutMs, TimeUnit.MILLISECONDS)
.build();
}

conn.setConnectTimeout(connectTimeoutMs);
conn.setReadTimeout(readTimeoutMs);
conn.setInstanceFollowRedirects(false);

Map<String, String> requestProperties = params.requestProperties();
String userAgent = getUserAgent();
requestProperties.put(USER_AGENT, userAgent);
for (String property : requestProperties.keySet()) {
conn.setRequestProperty(property, requestProperties.get(property));
Request.Builder requestBuilder = new Request.Builder().url(uri.toString());
requestBuilder.addHeader(USER_AGENT, getUserAgent());
for (Map.Entry<String, String> headerEntry : param.requestProperties().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}

ConnectionParameters.RequestMethod requestMethod = params.requestMethod();
Map<String, String> postParameters = params.postParameters();
conn.setRequestMethod(requestMethod.name());
if (requestMethod == ConnectionParameters.RequestMethod.GET) {
conn.setDoInput(true);
} else if (requestMethod == ConnectionParameters.RequestMethod.POST) {
conn.setDoOutput(true);
if (postParameters != null && !postParameters.isEmpty()) {
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(params.getEncodedPostParameters());
out.close();
if (param.requestMethod() == ConnectionParameters.RequestMethod.GET) {
requestBuilder = requestBuilder.get();
} else {
Map<String, String> postParameters = param.postParameters();
if (postParameters != null) {
FormBody.Builder formBuilder = new FormBody.Builder();
for (Map.Entry<String, String> postEntry : postParameters.entrySet()) {
String key = postEntry.getKey();
formBuilder.add(key, postEntry.getValue());
}
RequestBody formBody = formBuilder.build();
requestBuilder.post(formBody);
} else {
requestBuilder.post(RequestBody.create(null, ""));
}
}
return conn;
return requestBuilder.build();
}

@Override
public InputStream connect(@NonNull Uri uri, @NonNull ConnectionParameters params)
throws Exception {
Request request = buildRequest(uri, params);
mCall = sOkHttpClient.newCall(request);
final CountDownLatch latch = new CountDownLatch(1);
mCall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
mException = e;
latch.countDown();
}

mUrlConnection = openConnection(new URL(uri.toString()), params);
mUrlConnection.connect();
try {
return mUrlConnection.getInputStream();
} catch (IOException e) {
return mUrlConnection.getErrorStream();
@Override
public void onResponse(Call call, Response response) {
mResponse = response;
latch.countDown();
}
});
latch.await();
if (mException != null) {
throw mException;
}
if (mResponse != null && mResponse.body() != null) {
return mResponse.body().byteStream();
}
return null;
}


@Override
public void cleanUp() {
mUrlConnection = null;
//NO-OP
}

@Override
public void cancel() {
if (mUrlConnection != null) {
mUrlConnection.disconnect();
if (mCall != null) {
mCall.cancel();
}
}

@Override
public Map<String, List<String>> getHeaderFields() {
if (mUrlConnection != null) {
return mUrlConnection.getHeaderFields();
if (mResponse != null) {
return mResponse.headers().toMultimap();
}
return null;
}

@Override
public String getHeader(String header) {
if (mUrlConnection != null) {
return mUrlConnection.getHeaderField(header);
if (mResponse != null) {
return mResponse.header(header);
}
return null;
}

@Override
public int getResponseCode() throws IOException {
if (mUrlConnection != null) {
return mUrlConnection.getResponseCode();
if (mResponse != null) {
return mResponse.code();
}
return -1;
}

@Override
public int getContentLength() {
if (mUrlConnection != null) {
return mUrlConnection.getContentLength();
if (mResponse != null && mResponse.body() != null) {
return (int) mResponse.body().contentLength();
}
return -1;
}

@Override
public String getResponseMessage() throws IOException {
if (mUrlConnection != null) {
return mUrlConnection.getResponseMessage();
if (mResponse != null) {
return mResponse.message();
}
return null;
}

public HttpURLConnection getUrlConnection() {
return mUrlConnection;
}
}
1 change: 1 addition & 0 deletions e2e/android/forceVersions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def forceVersions(ConfigurationContainer configurations) {
force 'junit:junit:4.13.2'
force 'commons-io:commons-io:2.15.1'
force 'commons-codec:commons-codec:1.17.0'
force 'io.netty:netty-common:4.1.93.Final'
}
}
}
Expand Down
1 change: 0 additions & 1 deletion e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"react-native-reanimated": "^3.11.0",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-web": "~0.19.12",
"semver": "^7.6.2"
},
"resolutions": {
Expand Down
Loading

0 comments on commit c593218

Please sign in to comment.