
com.launchdarkly.client.Util Maven / Gradle / Ivy
package com.launchdarkly.client;
import com.google.common.base.Function;
import com.google.gson.JsonPrimitive;
import com.launchdarkly.client.interfaces.HttpAuthentication;
import com.launchdarkly.client.interfaces.HttpConfiguration;
import com.launchdarkly.client.value.LDValue;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static com.google.common.collect.Iterables.transform;
import okhttp3.Authenticator;
import okhttp3.ConnectionPool;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
class Util {
/**
* Converts either a unix epoch millis number or RFC3339/ISO8601 timestamp as {@link JsonPrimitive} to a {@link DateTime} object.
* @param maybeDate wraps either a number or a string that may contain a valid timestamp.
* @return null if input is not a valid format.
*/
static DateTime jsonPrimitiveToDateTime(LDValue maybeDate) {
if (maybeDate.isNumber()) {
return new DateTime((long)maybeDate.doubleValue());
} else if (maybeDate.isString()) {
try {
return new DateTime(maybeDate.stringValue(), DateTimeZone.UTC);
} catch (Throwable t) {
return null;
}
} else {
return null;
}
}
static Headers.Builder getHeadersBuilderFor(String sdkKey, HttpConfiguration config) {
Headers.Builder builder = new Headers.Builder()
.add("Authorization", sdkKey)
.add("User-Agent", "JavaClient/" + LDClient.CLIENT_VERSION);
if (config.getWrapperIdentifier() != null) {
builder.add("X-LaunchDarkly-Wrapper", config.getWrapperIdentifier());
}
return builder;
}
static void configureHttpClientBuilder(HttpConfiguration config, OkHttpClient.Builder builder) {
builder.connectionPool(new ConnectionPool(5, 5, TimeUnit.SECONDS))
.connectTimeout(config.getConnectTimeoutMillis(), TimeUnit.MILLISECONDS)
.readTimeout(config.getSocketTimeoutMillis(), TimeUnit.MILLISECONDS)
.writeTimeout(config.getSocketTimeoutMillis(), TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(false); // we will implement our own retry logic
if (config.getSslSocketFactory() != null) {
builder.sslSocketFactory(config.getSslSocketFactory(), config.getTrustManager());
}
if (config.getProxy() != null) {
builder.proxy(config.getProxy());
if (config.getProxyAuthentication() != null) {
builder.proxyAuthenticator(okhttpAuthenticatorFromHttpAuthStrategy(
config.getProxyAuthentication(),
"Proxy-Authentication",
"Proxy-Authorization"
));
}
}
}
static final Authenticator okhttpAuthenticatorFromHttpAuthStrategy(final HttpAuthentication strategy,
final String challengeHeaderName, final String responseHeaderName) {
return new Authenticator() {
public Request authenticate(Route route, Response response) throws IOException {
if (response.request().header(responseHeaderName) != null) {
return null; // Give up, we've already failed to authenticate
}
Iterable challenges = transform(response.challenges(),
new Function() {
public HttpAuthentication.Challenge apply(okhttp3.Challenge c) {
return new HttpAuthentication.Challenge(c.scheme(), c.realm());
}
});
String credential = strategy.provideAuthorization(challenges);
return response.request().newBuilder()
.header(responseHeaderName, credential)
.build();
}
};
}
static void shutdownHttpClient(OkHttpClient client) {
if (client.dispatcher() != null) {
client.dispatcher().cancelAll();
if (client.dispatcher().executorService() != null) {
client.dispatcher().executorService().shutdown();
}
}
if (client.connectionPool() != null) {
client.connectionPool().evictAll();
}
if (client.cache() != null) {
try {
client.cache().close();
} catch (Exception e) {}
}
}
/**
* Tests whether an HTTP error status represents a condition that might resolve on its own if we retry.
* @param statusCode the HTTP status
* @return true if retrying makes sense; false if it should be considered a permanent failure
*/
static boolean isHttpErrorRecoverable(int statusCode) {
if (statusCode >= 400 && statusCode < 500) {
switch (statusCode) {
case 400: // bad request
case 408: // request timeout
case 429: // too many requests
return true;
default:
return false; // all other 4xx errors are unrecoverable
}
}
return true;
}
/**
* Builds an appropriate log message for an HTTP error status.
* @param statusCode the HTTP status
* @param context description of what we were trying to do
* @param recoverableMessage description of our behavior if the error is recoverable; typically "will retry"
* @return a message string
*/
static String httpErrorMessage(int statusCode, String context, String recoverableMessage) {
StringBuilder sb = new StringBuilder();
sb.append("Received HTTP error ").append(statusCode);
switch (statusCode) {
case 401:
case 403:
sb.append(" (invalid SDK key)");
}
sb.append(" for ").append(context).append(" - ");
sb.append(isHttpErrorRecoverable(statusCode) ? recoverableMessage : "giving up permanently");
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy