8000 [FSSDK-10501] forward exceptions for ODP fetch segments by jaeopt · Pull Request #483 · optimizely/android-sdk · GitHub
[go: up one dir, main page]

Skip to content

[FSSDK-10501] forward exceptions for ODP fetch segments #483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
working
  • Loading branch information
jaeopt committed Jul 30, 2024
commit 9393421a86578c7f4dbd2d22ed044a779032c863
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ ODPManager getODPManager() {
*
* @param attrs Attributes that will be combined with default attributes.
*
*
* @return A new map of both the default attributes and attributes passed in.
*/
private Map<String, ?> getAllAttributes(@NonNull Map<String, ?> attrs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ package com.optimizely.ab.android.odp

import android.content.Context
import androidx.annotation.VisibleForTesting
import com.optimizely.ab.android.shared.Client
import com.optimizely.ab.android.shared.ClientForODPOnly
import com.optimizely.ab.android.shared.OptlyStorage
import com.optimizely.ab.android.shared.WorkerScheduler
import com.optimizely.ab.odp.ODPApiManager
Expand All @@ -33,7 +33,7 @@ open class DefaultODPApiManager(private val context: Context, timeoutForSegmentF

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
var segmentClient = ODPSegmentClient(
Client(OptlyStorage(context), LoggerFactory.getLogger(Client::class.java)),
ClientForODPOnly(OptlyStorage(context), LoggerFactory.getLogger(ClientForODPOnly::class.java)),
LoggerFactory.getLogger(ODPSegmentClient::class.java)
)
private val logger = LoggerFactory.getLogger(DefaultODPApiManager::class.java)
Expand Down
8000
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
package com.optimizely.ab.android.odp

import androidx.annotation.VisibleForTesting
import com.optimizely.ab.android.shared.Client
import com.optimizely.ab.android.shared.ClientForODPOnly
import com.optimizely.ab.odp.parser.ResponseJsonParser
import com.optimizely.ab.odp.parser.ResponseJsonParserFactory
import org.slf4j.Logger
import java.net.HttpURLConnection
import java.net.URL

@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
open class ODPSegmentClient(private val client: Client, private val logger: Logger) {
open class ODPSegmentClient(private val client: ClientForODPOnly, private val logger: Logger) {

@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
open fun fetchQualifiedSegments(
Expand All @@ -32,7 +32,7 @@ open class ODPSegmentClient(private val client: Client, private val logger: Logg
payload: String
): List<String>? {

val request: Client.Request<String> = Client.Request {
val request: ClientForODPOnly.Request<String> = ClientForODPOnly.Request {
var urlConnection: HttpURLConnection? = null
try {
val url = URL(apiEndpoint)
Expand Down Expand Up @@ -65,7 +65,8 @@ open class ODPSegmentClient(private val client: Client, private val logger: Logg
}
} catch (e: Exception) {
logger.error("Error making ODP segment request", e)
return@Request null
// return@Request null
throw e;
} finally {
if (urlConnection != null) {
try {
Expand Down
A3E2
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/****************************************************************************
* Copyright 2016-2017,2021, Optimizely, Inc. and contributors *
* *
* 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. *
***************************************************************************/

package com.optimizely.ab.android.shared;

import android.os.Build;

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

import org.slf4j.Logger;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

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

/**
* Functionality common to all clients using http connections
*/
public class ClientForODPOnly {

static final int MAX_BACKOFF_TIMEOUT = (int) Math.pow(2, 5);

@NonNull private final OptlyStorage optlyStorage;
@NonNull private final Logger logger;

/**
* Constructs a new Client instance
*
* @param optlyStorage an instance of {@link OptlyStorage}
* @param logger an instance of {@link Logger}
*/
public ClientForODPOnly(@NonNull OptlyStorage optlyStorage, @NonNull Logger logger) {
this.optlyStorage = optlyStorage;
this.logger = logger;
}

/**
* Opens {@link HttpURLConnection} from a {@link URL}
*
* @param url a {@link URL} instance
* @return an open {@link HttpURLConnection}
*/
@Nullable
public HttpURLConnection openConnection(URL url) {
try {
// API 21 (LOLLIPOP)+ supposed to use TLS1.2 as default, but some API-21 devices still fail, so include it here.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
SSLSocketFactory sslSocketFactory = new TLSSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
}

return (HttpURLConnection) url.openConnection();
} catch (Exception e) {
logger.warn("Error making request to {}.", url);
}
return null;
}

/**
* Adds a if-modified-since header to the open {@link URLConnection} if this value is
* stored in {@link OptlyStorage}.
* @param urlConnection an open {@link URLConnection}
*/
public void setIfModifiedSince(@NonNull URLConnection urlConnection) {
if (urlConnection == null || urlConnection.getURL() == null) {
logger.error("Invalid connection");
return;
}

long lastModified = optlyStorage.getLong(urlConnection.getURL().toString(), 0);
if (lastModified > 0) {
urlConnection.setIfModifiedSince(lastModified);
}
}

/**
* Retrieves the last-modified head from a {@link URLConnection} and saves it
* in {@link OptlyStorage}.
* @param urlConnection a {@link URLConnection} instance
*/
public void saveLastModified(@NonNull URLConnection urlConnection) {
if (urlConnection == null || urlConnection.getURL() == null) {
logger.error("Invalid connection");
return;
}

long lastModified = urlConnection.getLastModified();
if (lastModified > 0) {
optlyStorage.saveLong(urlConnection.getURL().toString(), urlConnection.getLastModified());
} else {
logger.warn("CDN response didn't have a last modified header");
}
}

@Nullable
public String readStream(@NonNull URLConnection urlConnection) {
Scanner scanner = null;
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
scanner = new Scanner(in).useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} catch (Exception e) {
logger.warn("Error reading urlConnection stream.", e);
return null;
}
finally {
if (scanner != null) {
// We assume that closing the scanner will close the associated input stream.
try {
scanner.close();
}
catch (Exception e) {
logger.error("Problem with closing the scanner on a input stream" , e);
}
}
}
}

/**
* Executes a request with exponential backoff
* @param request the request executable, would be a lambda on Java 8
* @param timeout the numerical base for the exponential backoff
* @param power the number of retries
* @param <T> the response type of the request
* @return the response
*/
public <T> T execute(Request<T> request, int timeout, int power) {
int baseTimeout = timeout;
int maxTimeout = (int) Math.pow(baseTimeout, power);
T response = null;
while(timeout <= maxTimeout) {
try {
response = request.execute();
} catch (Exception e) {
logger.error("(ClientForODPOnly) Request failed with error: ", e);
throw e;
}

if (response == null || response == Boolean.FALSE) {
// retry is disabled when timeout set to 0
if (timeout == 0) break;

try {
logger.info("Request failed, waiting {} seconds to try again", timeout);
Thread.sleep(TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.SECONDS));
} catch (InterruptedException e) {
logger.warn("Exponential backoff failed", e);
break;
}
timeout = timeout * baseTimeout;
} else {
break;
}
}
return response;
}

/**
* Bundles up a request allowing it's execution to be deferred
* @param <T> The response type of the request
*/
public interface Request<T> {
T execute();
}
}
4 changes: 2 additions & 2 deletions test-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ android {
buildTypes {
debug {
// enable proguard for debug mode (keep both of these to detect issues while testing)
minifyEnabled true
debuggable false
minifyEnabled false
debuggable true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'test-app-proguard-rules.pro'
}
release {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ static public void samplesForDoc_ForcedDecision(Context context) {
}

static public void samplesForDoc_ODP_async(Context context) {
Log.d("Optimizely", "[ODP] samplesForDoc_ODP_async");
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);
optimizelyManager.initialize(context, null, (OptimizelyClient client) -> {
OptimizelyUserContext userContext = client.createUserContext("user_123");
Expand All @@ -873,6 +874,7 @@ static public void samplesForDoc_ODP_async(Context context) {
}

static public void samplesForDoc_ODP_sync(Context context) {
Log.d("Optimizely", "[ODP] samplesForDoc_ODP_sync");
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);

boolean returnInMainThread = false;
Expand All @@ -887,4 +889,26 @@ static public void samplesForDoc_ODP_sync(Context context) {
});
}

static public void samplesForDoc_ODP_network_errors(Context context) {
Log.d("Optimizely", "[ODP] samplesForDoc_ODP_network_errors");
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);

boolean returnInMainThread = false;

optimizelyManager.initialize(context, null, returnInMainThread, (OptimizelyClient client) -> {
optimizelyManager.getOptimizely().getODPManager().updateSettings("https://a.com", "any-key", Set.of("apple"));

OptimizelyUserContext userContext = client.createUserContext("user_123");
try {
userContext.fetchQualifiedSegments();
} catch (Exception e) {
Log.d("Optimizely", "[ODP] exception from SDK = " + e.getMessage());
}

Log.d("Optimizely", "[ODP] segments = " + userContext.getQualifiedSegments());
OptimizelyDecision optDecision = userContext.decide("odp-flag-1");
Log.d("Optimizely", "[ODP] decision = " + optDecision.toString());
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,19 @@ class SplashScreenActivity : AppCompatActivity() {
val eventRescheduler = EventRescheduler()
applicationContext.registerReceiver(eventRescheduler, IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))

if (INITIALIZE_ASYNCHRONOUSLY) {
optimizelyManager!!.initialize(this, R.raw.datafile) { _ ->
addNotificationListeners()
startVariation()
}
} else {
optimizelyManager!!.initialize(this, R.raw.datafile)
addNotificationListeners()
startVariation()
}

// if (INITIALIZE_ASYNCHRONOUSLY) {
// optimizelyManager!!.initialize(this, R.raw.datafile) { _ ->
// addNotificationListeners()
// startVariation()
// }
// } else {
// optimizelyManager!!.initialize(this, R.raw.datafile)
// addNotificationListeners()
// startVariation()
// }

// APISamplesInJava.samplesForDoc_ODP_sync(this)
APISamplesInJava.samplesForDoc_ODP_network_errors(this)
}

private fun addNotificationListeners() {
Expand Down
1 change: 1 addition & 0 deletions test-app/src/main/resources/android-logger.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ logger.com.optimizely.ab.internal=DEBUG:Optly.internal
logger.com.optimizely.ab.android.sdk=DEBUG:Optly.androidSdk
logger.com.optimizely.ab.android.datafile_handler=DEBUG:Optly.datafileHandler
logger.com.optimizely.ab.android.event_handler=DEBUG:Optly.eventHandler
logger.com.optimizely.ab.android.odp=DEBUG:Optly.odp
logger.com.optimizely.ab.android.shared=DEBUG:Optly.shared
logger.com.optimizely.ab.android.user-profile=DEBUG:Optly.userProfile

Expand Down
0