diff --git a/okhttp-tests/src/test/java/okhttp3/CallTest.java b/okhttp-tests/src/test/java/okhttp3/CallTest.java index 31f5062305c4..d2e48e62f502 100644 --- a/okhttp-tests/src/test/java/okhttp3/CallTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CallTest.java @@ -1583,6 +1583,22 @@ private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exc assertEquals("Body", response.body().string()); } + @Test public void getClientRequestTimeoutWithBackPressure() throws Exception { + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) + .setResponseCode(408) + .setHeader("Connection", "Close") + .setHeader("Retry-After", "1") + .setBody("You took too long!")); + + Request request = new Request.Builder() + .url(server.url("/")) + .build(); + Response response = client.newCall(request).execute(); + + assertEquals("You took too long!", response.body().string()); + } + @Test public void requestBodyRetransmittedOnClientRequestTimeout() throws Exception { server.enqueue(new MockResponse() .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) @@ -1649,6 +1665,48 @@ private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exc assertEquals(2, server.getRequestCount()); } + @Test public void maxUnavailableTimeoutRetries() throws IOException { + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) + .setResponseCode(503) + .setHeader("Connection", "Close") + .setHeader("Retry-After", "0") + .setBody("You took too long!")); + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) + .setResponseCode(503) + .setHeader("Connection", "Close") + .setHeader("Retry-After", "0") + .setBody("You took too long!")); + + Request request = new Request.Builder() + .url(server.url("/")) + .build(); + Response response = client.newCall(request).execute(); + + assertEquals(503, response.code()); + assertEquals("You took too long!", response.body().string()); + + assertEquals(2, server.getRequestCount()); + } + + @Test public void retryOnUnavailableWith0RetryAfter() throws IOException { + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) + .setResponseCode(503) + .setHeader("Connection", "Close") + .setHeader("Retry-After", "0") + .setBody("You took too long!")); + server.enqueue(new MockResponse().setBody("Body")); + + Request request = new Request.Builder() + .url(server.url("/")) + .build(); + Response response = client.newCall(request).execute(); + + assertEquals("Body", response.body().string()); + } + @Test public void propfindRedirectsToPropfindAndMaintainsRequestBody() throws Exception { // given server.enqueue(new MockResponse() diff --git a/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java b/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java index 937ba28ea786..ed8c9e0a7541 100644 --- a/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java +++ b/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java @@ -49,6 +49,7 @@ import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; import static java.net.HttpURLConnection.HTTP_SEE_OTHER; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; import static okhttp3.internal.Util.closeQuietly; import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; @@ -359,13 +360,47 @@ private Request followUpRequest(Response userResponse) throws IOException { return null; } + if (retryAfter(userResponse, 0) > 0) { + return null; + } + return userResponse.request(); + case HTTP_UNAVAILABLE: + if (userResponse.priorResponse() != null + && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) { + // We attempted to retry and got another timeout. Give up. + return null; + } + + if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) { + // specifically received an instruction to retry without delay + return userResponse.request(); + } + + return null; + default: return null; } } + private int retryAfter(Response userResponse, int defaultDelay) { + String header = userResponse.header("Retry-After"); + + if (header == null) { + return defaultDelay; + } + + // https://tools.ietf.org/html/rfc7231#section-7.1.3 + // currently ignores a HTTP-date, and assumes any non int 0 is a delay + if (header.matches("\\d+")) { + return Integer.valueOf(header); + } + + return Integer.MAX_VALUE; + } + /** * Returns true if an HTTP request for {@code followUp} can reuse the connection used by this * engine.