From 52c05c225e8a0479d695c8cdaa921e277fb49744 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 24 Aug 2026 09:21:58 -0400 Subject: [PATCH 1/2] feat(internal): add FailureClass enum and classifier helpers to HttpErrors (SDK-2789) Introduces a shared classifier that categorizes network failures into NORMAL or UNEXPECTED regimes. Enables downstream server-SDK code to select between normal and extended-regime backoff without duplicating the classification rules. - FailureClass enum (NORMAL, UNEXPECTED) with a package-private cause-chain scan for TLS / certificate exceptions. - HttpErrors.classifyHTTPFailure(int): 400 / 408 / 429 and 5xx are NORMAL; other 4xx (401 / 403 / etc.) are UNEXPECTED; non-4xx / non-5xx failure statuses are NORMAL. - HttpErrors.classifyTransportFailure(Throwable): TLS or certificate validation anywhere in the exception chain is UNEXPECTED; all other transport failures are NORMAL. - HttpErrors.classifyAndLogHTTPFailure / classifyAndLogTransportFailure: classify, log at the appropriate level (Error for UNEXPECTED, Warn for NORMAL), and return the classification. - Deprecates HttpErrors.isHttpErrorRecoverable and checkIfErrorIsRecoverableAndLog in favor of the classify* helpers. The boolean "give up permanently" contract does not fit callers that keep retrying regardless of classification; existing callers can migrate incrementally. Unit coverage: HttpErrorsClassificationTest exercises the classifier against the full 4xx / 5xx / transport / TLS-cause matrix. Enables SDK-2789 (server SDK's RETRY-spec conformance work), which consumes these helpers in its FDv1 streaming and polling data sources. --- .../sdk/internal/http/FailureClass.java | 46 +++++++ .../sdk/internal/http/HttpErrors.java | 119 ++++++++++++++++-- .../http/HttpErrorsClassificationTest.java | 78 ++++++++++++ 3 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java create mode 100644 lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java diff --git a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java new file mode 100644 index 00000000..5911fd8e --- /dev/null +++ b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java @@ -0,0 +1,46 @@ +package com.launchdarkly.sdk.internal.http; + +import javax.net.ssl.SSLException; + +import java.security.GeneralSecurityException; +import java.security.cert.CertificateException; + +/** + * Classifies a failure into one of two regimes: {@link #NORMAL} or + * {@link #UNEXPECTED}. Used by data sources and other network-facing components + * to decide whether a failure should trigger extended-regime backoff. + *

+ * This class is for internal use only and should not be documented in the SDK API. + * It is not supported for any use outside of the LaunchDarkly SDKs, and is subject + * to change without notice. + */ +public enum FailureClass { + /** + * Ordinary transient failure. Use the normal-regime backoff. Includes HTTP + * 400 / 408 / 429, HTTP 5xx, any other HTTP status the SDK treats as a + * failure, and generic transport failures (connection refused, read timeout, + * DNS failure, etc.). + */ + NORMAL, + + /** + * Unexpected failure indicative of a longer-lived condition. Use the + * extended-regime backoff. Includes HTTP 401 / 403 and any other 4xx not in + * the NORMAL list, plus TLS / certificate validation failures. + */ + UNEXPECTED; + + /** + * Scans an exception chain for TLS / certificate validation causes. + */ + static boolean hasTlsOrCertificateCause(Throwable t) { + for (Throwable c = t; c != null; c = c.getCause()) { + if (c instanceof SSLException + || c instanceof CertificateException + || c instanceof GeneralSecurityException) { + return true; + } + } + return false; + } +} diff --git a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java index e99126ea..1fd05b47 100644 --- a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java +++ b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java @@ -10,14 +10,14 @@ */ public abstract class HttpErrors { private HttpErrors() {} - + /** * Represents an HTTP response error as an exception. */ @SuppressWarnings("serial") public static final class HttpErrorException extends Exception { private final int status; - + /** * Constructs an instance. * @param status the status code @@ -26,7 +26,7 @@ public HttpErrorException(int status) { super("HTTP error " + status); this.status = status; } - + /** * Returns the status code. * @return the status code @@ -35,12 +35,18 @@ public int getStatus() { return status; } } - + /** * 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 + * + * @deprecated Prefer {@link #classifyHTTPFailure(int)}, which returns a {@link FailureClass} + * that lets the caller distinguish an extended-regime backoff signal from an ordinary + * transient failure. This boolean method treats {@code false} as "give up permanently", + * which does not fit callers that keep retrying regardless of classification. */ + @Deprecated public static boolean isHttpErrorRecoverable(int statusCode) { if (statusCode >= 400 && statusCode < 500) { switch (statusCode) { @@ -54,18 +60,25 @@ public static boolean isHttpErrorRecoverable(int statusCode) { } return true; } - + /** * Logs an HTTP error or network error at the appropriate level and determines whether it is recoverable * (as defined by {@link #isHttpErrorRecoverable(int)}). - * + * * @param logger the logger to log to * @param errorDesc description of the error * @param errorContext a phrase like "when doing such-and-such" * @param statusCode HTTP status code, or 0 for a network error * @param recoverableMessage a phrase like "will retry" to use if the error is recoverable * @return true if the error is recoverable + * + * @deprecated Prefer {@link #classifyAndLogHTTPFailure} and + * {@link #classifyAndLogTransportFailure}, which return a {@link FailureClass} that lets + * the caller distinguish an extended-regime backoff signal from an ordinary transient + * failure. This method treats a {@code false} return as "give up permanently", which does + * not fit callers that keep retrying regardless of classification. */ + @Deprecated public static boolean checkIfErrorIsRecoverableAndLog( LDLogger logger, String errorDesc, @@ -81,10 +94,10 @@ public static boolean checkIfErrorIsRecoverableAndLog( return true; } } - + /** * Returns a text description of an HTTP error. - * + * * @param statusCode the status code * @return the error description */ @@ -92,4 +105,94 @@ public static String httpErrorDescription(int statusCode) { return "HTTP error " + statusCode + (statusCode == 401 || statusCode == 403 ? " (invalid SDK key)" : ""); } + + /** + * Classifies an HTTP response by its status code. Returns + * {@link FailureClass#UNEXPECTED} for 401 / 403 and any other 4xx not in the NORMAL list; + * returns {@link FailureClass#NORMAL} for 400 / 408 / 429, 5xx, and any other status the SDK + * treats as a failure. + * + * @param statusCode the HTTP status code + * @return the classification + */ + public static FailureClass classifyHTTPFailure(int statusCode) { + if (statusCode == 400 || statusCode == 408 || statusCode == 429) { + return FailureClass.NORMAL; + } + if (statusCode >= 500) { + return FailureClass.NORMAL; + } + if (statusCode >= 400 && statusCode < 500) { + return FailureClass.UNEXPECTED; + } + return FailureClass.NORMAL; + } + + /** + * Classifies a transport-level exception. TLS or certificate validation failures anywhere in + * the exception chain are {@link FailureClass#UNEXPECTED}; all other transport failures are + * {@link FailureClass#NORMAL}. + * + * @param t the transport-level exception + * @return the classification + */ + public static FailureClass classifyTransportFailure(Throwable t) { + return FailureClass.hasTlsOrCertificateCause(t) ? FailureClass.UNEXPECTED : FailureClass.NORMAL; + } + + /** + * Classifies an HTTP failure per {@link #classifyHTTPFailure(int)}, logs it at the appropriate + * level, and returns the classification for the caller to act on. Unexpected classifications + * log at Error since they typically indicate a customer-side problem (invalid or expired SDK + * key, misconfiguration); normal classifications log at Warn since they are typically transient. + * + * @param logger the logger to log to + * @param statusCode the HTTP status + * @param errorContext a phrase like "in stream connection" or "on polling request" + * @param willRetryMessage a phrase like "will retry" or "will retry at next scheduled poll interval" + * @return the classification + */ + public static FailureClass classifyAndLogHTTPFailure( + LDLogger logger, + int statusCode, + String errorContext, + String willRetryMessage + ) { + FailureClass failureClass = classifyHTTPFailure(statusCode); + String errorDesc = httpErrorDescription(statusCode); + if (failureClass == FailureClass.UNEXPECTED) { + logger.error("Error {} ({}): {}", errorContext, willRetryMessage, errorDesc); + } else { + logger.warn("Error {} ({}): {}", errorContext, willRetryMessage, errorDesc); + } + return failureClass; + } + + /** + * Classifies a transport failure per {@link #classifyTransportFailure(Throwable)}, logs it at + * the appropriate level, and returns the classification. Unexpected classifications (TLS / + * certificate validation) log at Error since they typically indicate a customer-side problem + * (misconfigured trust store, expired cert); other transport failures log at Warn since they + * are typically transient. + * + * @param logger the logger to log to + * @param e the transport-level exception + * @param errorContext a phrase like "in stream connection" or "on polling request" + * @param willRetryMessage a phrase like "will retry" or "will retry at next scheduled poll interval" + * @return the classification + */ + public static FailureClass classifyAndLogTransportFailure( + LDLogger logger, + Throwable e, + String errorContext, + String willRetryMessage + ) { + FailureClass failureClass = classifyTransportFailure(e); + if (failureClass == FailureClass.UNEXPECTED) { + logger.error("Error {} ({}): {}", errorContext, willRetryMessage, e.toString()); + } else { + logger.warn("Error {} ({}): {}", errorContext, willRetryMessage, e.toString()); + } + return failureClass; + } } diff --git a/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java b/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java new file mode 100644 index 00000000..74da4fb2 --- /dev/null +++ b/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java @@ -0,0 +1,78 @@ +package com.launchdarkly.sdk.internal.http; + +import org.junit.Test; + +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; + +import static com.launchdarkly.sdk.internal.http.FailureClass.NORMAL; +import static com.launchdarkly.sdk.internal.http.FailureClass.UNEXPECTED; +import static org.junit.Assert.assertEquals; + +/** + * Unit coverage for {@link HttpErrors#classifyHTTPFailure(int)} and + * {@link HttpErrors#classifyTransportFailure(Throwable)}. + */ +@SuppressWarnings("javadoc") +public class HttpErrorsClassificationTest { + + // 400, 408, 429 are NORMAL. + @Test public void http400IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(400)); } + @Test public void http408IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(408)); } + @Test public void http429IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(429)); } + + // Other 4xx (including 401, 403) is UNEXPECTED. + @Test public void http401IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(401)); } + @Test public void http403IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(403)); } + @Test public void http404IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(404)); } + @Test public void http418IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(418)); } + @Test public void http451IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(451)); } + + // 5xx is NORMAL. + @Test public void http500IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(500)); } + @Test public void http502IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(502)); } + @Test public void http503IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(503)); } + @Test public void http504IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(504)); } + @Test public void http599IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(599)); } + + // Unusual non-4xx / non-5xx failure statuses are NORMAL. + @Test public void http300IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(300)); } + @Test public void http0IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(0)); } + + // Ordinary network I/O failures are NORMAL. + @Test public void connectExceptionIsNormal() { + assertEquals(NORMAL, HttpErrors.classifyTransportFailure(new ConnectException("connection refused"))); + } + @Test public void socketTimeoutIsNormal() { + assertEquals(NORMAL, HttpErrors.classifyTransportFailure(new SocketTimeoutException("timeout"))); + } + @Test public void ioExceptionIsNormal() { + assertEquals(NORMAL, HttpErrors.classifyTransportFailure(new IOException("something else"))); + } + + // TLS / certificate validation failures are UNEXPECTED. + @Test public void sslHandshakeIsUnexpected() { + assertEquals(UNEXPECTED, HttpErrors.classifyTransportFailure(new SSLHandshakeException("handshake failed"))); + } + @Test public void sslPeerUnverifiedIsUnexpected() { + assertEquals(UNEXPECTED, HttpErrors.classifyTransportFailure(new SSLPeerUnverifiedException("peer not verified"))); + } + @Test public void certificateExceptionIsUnexpected() { + assertEquals(UNEXPECTED, HttpErrors.classifyTransportFailure(new CertificateException("cert invalid"))); + } + @Test public void certificateExpiredIsUnexpected() { + assertEquals(UNEXPECTED, HttpErrors.classifyTransportFailure(new CertificateExpiredException("expired"))); + } + + // Cause-chain walk finds TLS deep in wrapper exceptions. + @Test public void sslCauseWrappedIsUnexpected() { + IOException wrapper = new IOException("wrapped", new SSLHandshakeException("real cause")); + assertEquals(UNEXPECTED, HttpErrors.classifyTransportFailure(wrapper)); + } +} From f67fd5ef68e4119b7b2d90dbaa1d7f191f5477a6 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Mon, 24 Aug 2026 14:08:08 -0400 Subject: [PATCH 2/2] fix: address review feedback on classifier helpers - FailureClass: drop redundant CertificateException check (already covered by GeneralSecurityException, which is its parent) - HttpErrors: rename classifyHTTPFailure -> classifyHttpFailure and classifyAndLogHTTPFailure -> classifyAndLogHttpFailure to match the existing Http camelCase convention (httpErrorDescription, HttpErrorException) and Java standard-library naming - HttpErrors.classifyAndLogTransportFailure: log exceptions via LogValues.exceptionSummary instead of e.toString(), matching the existing pattern in DefaultEventProcessor --- .../sdk/internal/http/FailureClass.java | 2 -- .../sdk/internal/http/HttpErrors.java | 17 +++++----- .../http/HttpErrorsClassificationTest.java | 32 +++++++++---------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java index 5911fd8e..c678b718 100644 --- a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java +++ b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/FailureClass.java @@ -3,7 +3,6 @@ import javax.net.ssl.SSLException; import java.security.GeneralSecurityException; -import java.security.cert.CertificateException; /** * Classifies a failure into one of two regimes: {@link #NORMAL} or @@ -36,7 +35,6 @@ public enum FailureClass { static boolean hasTlsOrCertificateCause(Throwable t) { for (Throwable c = t; c != null; c = c.getCause()) { if (c instanceof SSLException - || c instanceof CertificateException || c instanceof GeneralSecurityException) { return true; } diff --git a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java index 1fd05b47..9d61b3f0 100644 --- a/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java +++ b/lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/http/HttpErrors.java @@ -1,6 +1,7 @@ package com.launchdarkly.sdk.internal.http; import com.launchdarkly.logging.LDLogger; +import com.launchdarkly.logging.LogValues; /** * Contains shared helpers related to HTTP response validation. @@ -41,7 +42,7 @@ public int getStatus() { * @param statusCode the HTTP status * @return true if retrying makes sense; false if it should be considered a permanent failure * - * @deprecated Prefer {@link #classifyHTTPFailure(int)}, which returns a {@link FailureClass} + * @deprecated Prefer {@link #classifyHttpFailure(int)}, which returns a {@link FailureClass} * that lets the caller distinguish an extended-regime backoff signal from an ordinary * transient failure. This boolean method treats {@code false} as "give up permanently", * which does not fit callers that keep retrying regardless of classification. @@ -72,7 +73,7 @@ public static boolean isHttpErrorRecoverable(int statusCode) { * @param recoverableMessage a phrase like "will retry" to use if the error is recoverable * @return true if the error is recoverable * - * @deprecated Prefer {@link #classifyAndLogHTTPFailure} and + * @deprecated Prefer {@link #classifyAndLogHttpFailure} and * {@link #classifyAndLogTransportFailure}, which return a {@link FailureClass} that lets * the caller distinguish an extended-regime backoff signal from an ordinary transient * failure. This method treats a {@code false} return as "give up permanently", which does @@ -115,7 +116,7 @@ public static String httpErrorDescription(int statusCode) { * @param statusCode the HTTP status code * @return the classification */ - public static FailureClass classifyHTTPFailure(int statusCode) { + public static FailureClass classifyHttpFailure(int statusCode) { if (statusCode == 400 || statusCode == 408 || statusCode == 429) { return FailureClass.NORMAL; } @@ -141,7 +142,7 @@ public static FailureClass classifyTransportFailure(Throwable t) { } /** - * Classifies an HTTP failure per {@link #classifyHTTPFailure(int)}, logs it at the appropriate + * Classifies an HTTP failure per {@link #classifyHttpFailure(int)}, logs it at the appropriate * level, and returns the classification for the caller to act on. Unexpected classifications * log at Error since they typically indicate a customer-side problem (invalid or expired SDK * key, misconfiguration); normal classifications log at Warn since they are typically transient. @@ -152,13 +153,13 @@ public static FailureClass classifyTransportFailure(Throwable t) { * @param willRetryMessage a phrase like "will retry" or "will retry at next scheduled poll interval" * @return the classification */ - public static FailureClass classifyAndLogHTTPFailure( + public static FailureClass classifyAndLogHttpFailure( LDLogger logger, int statusCode, String errorContext, String willRetryMessage ) { - FailureClass failureClass = classifyHTTPFailure(statusCode); + FailureClass failureClass = classifyHttpFailure(statusCode); String errorDesc = httpErrorDescription(statusCode); if (failureClass == FailureClass.UNEXPECTED) { logger.error("Error {} ({}): {}", errorContext, willRetryMessage, errorDesc); @@ -189,9 +190,9 @@ public static FailureClass classifyAndLogTransportFailure( ) { FailureClass failureClass = classifyTransportFailure(e); if (failureClass == FailureClass.UNEXPECTED) { - logger.error("Error {} ({}): {}", errorContext, willRetryMessage, e.toString()); + logger.error("Error {} ({}): {}", errorContext, willRetryMessage, LogValues.exceptionSummary(e)); } else { - logger.warn("Error {} ({}): {}", errorContext, willRetryMessage, e.toString()); + logger.warn("Error {} ({}): {}", errorContext, willRetryMessage, LogValues.exceptionSummary(e)); } return failureClass; } diff --git a/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java b/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java index 74da4fb2..9b284b75 100644 --- a/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java +++ b/lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/http/HttpErrorsClassificationTest.java @@ -16,34 +16,34 @@ import static org.junit.Assert.assertEquals; /** - * Unit coverage for {@link HttpErrors#classifyHTTPFailure(int)} and + * Unit coverage for {@link HttpErrors#classifyHttpFailure(int)} and * {@link HttpErrors#classifyTransportFailure(Throwable)}. */ @SuppressWarnings("javadoc") public class HttpErrorsClassificationTest { // 400, 408, 429 are NORMAL. - @Test public void http400IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(400)); } - @Test public void http408IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(408)); } - @Test public void http429IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(429)); } + @Test public void http400IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(400)); } + @Test public void http408IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(408)); } + @Test public void http429IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(429)); } // Other 4xx (including 401, 403) is UNEXPECTED. - @Test public void http401IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(401)); } - @Test public void http403IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(403)); } - @Test public void http404IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(404)); } - @Test public void http418IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(418)); } - @Test public void http451IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHTTPFailure(451)); } + @Test public void http401IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHttpFailure(401)); } + @Test public void http403IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHttpFailure(403)); } + @Test public void http404IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHttpFailure(404)); } + @Test public void http418IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHttpFailure(418)); } + @Test public void http451IsUnexpected() { assertEquals(UNEXPECTED, HttpErrors.classifyHttpFailure(451)); } // 5xx is NORMAL. - @Test public void http500IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(500)); } - @Test public void http502IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(502)); } - @Test public void http503IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(503)); } - @Test public void http504IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(504)); } - @Test public void http599IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(599)); } + @Test public void http500IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(500)); } + @Test public void http502IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(502)); } + @Test public void http503IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(503)); } + @Test public void http504IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(504)); } + @Test public void http599IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(599)); } // Unusual non-4xx / non-5xx failure statuses are NORMAL. - @Test public void http300IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(300)); } - @Test public void http0IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHTTPFailure(0)); } + @Test public void http300IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(300)); } + @Test public void http0IsNormal() { assertEquals(NORMAL, HttpErrors.classifyHttpFailure(0)); } // Ordinary network I/O failures are NORMAL. @Test public void connectExceptionIsNormal() {