From 99c9349c7430d7f31e4b90583339f2da70e5cdae Mon Sep 17 00:00:00 2001 From: Guillaume Perrot Date: Thu, 8 Dec 2016 19:08:39 -0800 Subject: [PATCH] #270 Retry transient ssl exceptions --- .../ingestion/http/HttpUtilsAndroidTest.java | 8 ++++++ .../mobile/ingestion/http/HttpUtils.java | 28 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/sdk/mobile-center/src/androidTest/java/com/microsoft/azure/mobile/ingestion/http/HttpUtilsAndroidTest.java b/sdk/mobile-center/src/androidTest/java/com/microsoft/azure/mobile/ingestion/http/HttpUtilsAndroidTest.java index e9c4195185..dc648b74bd 100644 --- a/sdk/mobile-center/src/androidTest/java/com/microsoft/azure/mobile/ingestion/http/HttpUtilsAndroidTest.java +++ b/sdk/mobile-center/src/androidTest/java/com/microsoft/azure/mobile/ingestion/http/HttpUtilsAndroidTest.java @@ -10,6 +10,9 @@ import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; + import static com.microsoft.azure.mobile.ingestion.http.HttpUtils.isRecoverableError; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -39,5 +42,10 @@ public void isRecoverableErrorTest() { assertFalse(isRecoverableError(new HttpException(413))); assertTrue(isRecoverableError(new HttpException(429))); assertTrue(isRecoverableError(new HttpException(401))); + assertTrue(isRecoverableError(new SSLException("Write error: ssl=0x59c28f90: I/O error during system call, Connection timed out"))); + assertFalse(isRecoverableError(new SSLHandshakeException("java.security.cert.CertPathValidatorException: Trust anchor for certification path not found."))); + assertFalse(isRecoverableError(new SSLException("java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty"))); + assertTrue(isRecoverableError(new SSLException("Read error: ssl=0x9dd07200: I/O error during system call, Connection reset by peer"))); + assertTrue(isRecoverableError(new SSLException("SSL handshake aborted: ssl=0x1cc160: I/O error during system call, Connection reset by peer"))); } } diff --git a/sdk/mobile-center/src/main/java/com/microsoft/azure/mobile/ingestion/http/HttpUtils.java b/sdk/mobile-center/src/main/java/com/microsoft/azure/mobile/ingestion/http/HttpUtils.java index 368749596c..a05ecf54ab 100644 --- a/sdk/mobile-center/src/main/java/com/microsoft/azure/mobile/ingestion/http/HttpUtils.java +++ b/sdk/mobile-center/src/main/java/com/microsoft/azure/mobile/ingestion/http/HttpUtils.java @@ -6,9 +6,18 @@ import java.io.InterruptedIOException; import java.net.SocketException; import java.net.UnknownHostException; +import java.util.regex.Pattern; +import javax.net.ssl.SSLException; + +/** + * HTTP utilities. + */ public final class HttpUtils { + /** + * Types of exception that can be retried, no matter what the details are. Sub-classes are included. + */ private static final Class[] RECOVERABLE_EXCEPTIONS = { EOFException.class, InterruptedIOException.class, @@ -16,19 +25,36 @@ public final class HttpUtils { UnknownHostException.class }; + /** + * Some transient exceptions can only be detected by interpreting the message... + */ + private static Pattern CONNECTION_ISSUE_PATTERN = Pattern.compile("connection (time|reset)"); + @VisibleForTesting HttpUtils() { } + /** + * Check whether an exception/error describes a recoverable error or not. + * + * @param t exception or error. + * @return true if the exception/error should be retried, false otherwise. + */ public static boolean isRecoverableError(Throwable t) { + + /* Check HTTP exception details. */ if (t instanceof HttpException) { HttpException exception = (HttpException) t; int code = exception.getStatusCode(); return code >= 500 || code == 408 || code == 429 || code == 401; } + + /* Check for a generic exception to retry. */ for (Class type : RECOVERABLE_EXCEPTIONS) if (type.isAssignableFrom(t.getClass())) return true; - return false; + + /* Check corner cases. */ + return t instanceof SSLException && CONNECTION_ISSUE_PATTERN.matcher(t.getMessage().toLowerCase()).find(); } }