Skip to content

Commit

Permalink
New API: RequestBody.isOneShot()
Browse files Browse the repository at this point in the history
Closes: #4134
  • Loading branch information
swankjesse committed Mar 3, 2019
1 parent c78269c commit 0f624b5
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 30 deletions.
65 changes: 65 additions & 0 deletions okhttp-tests/src/test/java/okhttp3/CallTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,71 @@ private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exc
assertEquals("Body", response.body().string());
}

@Test public void canRetryNormalRequestBody() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(503)
.setHeader("Retry-After", "0")
.setBody("please retry"));
server.enqueue(new MockResponse()
.setBody("thank you for retrying"));

Request request = new Request.Builder()
.url(server.url("/"))
.post(new RequestBody() {
int attempt = 0;

@Override public @Nullable MediaType contentType() {
return null;
}

@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("attempt " + (attempt++));
}
})
.build();
Response response = client.newCall(request).execute();
assertEquals(200, response.code());
assertEquals("thank you for retrying", response.body().string());

assertEquals("attempt 0", server.takeRequest().getBody().readUtf8());
assertEquals("attempt 1", server.takeRequest().getBody().readUtf8());
assertEquals(2, server.getRequestCount());
}

@Test public void cannotRetryOneShotRequestBody() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(503)
.setHeader("Retry-After", "0")
.setBody("please retry"));
server.enqueue(new MockResponse()
.setBody("thank you for retrying"));

Request request = new Request.Builder()
.url(server.url("/"))
.post(new RequestBody() {
int attempt = 0;

@Override public @Nullable MediaType contentType() {
return null;
}

@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("attempt " + (attempt++));
}

@Override public boolean isOneShot() {
return true;
}
})
.build();
Response response = client.newCall(request).execute();
assertEquals(503, response.code());
assertEquals("please retry", response.body().string());

assertEquals("attempt 0", server.takeRequest().getBody().readUtf8());
assertEquals(1, server.getRequestCount());
}

@Test public void propfindRedirectsToPropfindAndMaintainsRequestBody() throws Exception {
// given
server.enqueue(new MockResponse()
Expand Down
21 changes: 19 additions & 2 deletions okhttp/src/main/java/okhttp3/RequestBody.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ public long contentLength() throws IOException {

/**
* A duplex request body is special in how it is <strong>transmitted</strong> on the network and
* in the <strong>API contract</strong> between OkHttp and the application. This method returns
* false unless it overridden by a subclass.
* in the <strong>API contract</strong> between OkHttp and the application.
*
* <p>This method returns false unless it is overridden by a subclass.
*
* <h3>Duplex Transmission</h3>
*
Expand Down Expand Up @@ -76,6 +77,22 @@ public boolean isDuplex() {
return false;
}

/**
* Returns true if this body expects at most one call to {@link #writeTo} and can be transmitted
* at most once. This is typically used when writing the request body is destructive and it is not
* possible to recreate the request body after it has been sent.
*
* <p>This method returns false unless it is overridden by a subclass.
*
* <p>By default OkHttp will attempt to retransmit request bodies when the original request fails
* due to a stale connection, a client timeout (HTTP 408), a satisfied authorization challenge
* (HTTP 401 and 407), or a retryable server failure (HTTP 503 with a {@code Retry-After: 0}
* header).
*/
public boolean isOneShot() {
return false;
}

/**
* Returns a new request body that transmits {@code content}. If {@code contentType} is non-null
* and lacks a charset, this will use UTF-8.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.HttpRetryException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.SocketTimeoutException;
Expand Down Expand Up @@ -126,6 +125,11 @@ public RetryAndFollowUpInterceptor(OkHttpClient client) {
return response;
}

RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}

closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
Expand All @@ -135,10 +139,6 @@ public RetryAndFollowUpInterceptor(OkHttpClient client) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

if (followUp.body() instanceof UnrepeatableRequestBody) {
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}

request = followUp;
priorResponse = response;
}
Expand All @@ -156,7 +156,7 @@ private boolean recover(IOException e, Transmitter transmitter,
if (!client.retryOnConnectionFailure()) return false;

// We can't send the request body again.
if (requestSendStarted && requestIsUnrepeatable(e, userRequest)) return false;
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
Expand All @@ -168,8 +168,9 @@ private boolean recover(IOException e, Transmitter transmitter,
return true;
}

private boolean requestIsUnrepeatable(IOException e, Request userRequest) {
return userRequest.body() instanceof UnrepeatableRequestBody
private boolean requestIsOneShot(IOException e, Request userRequest) {
RequestBody requestBody = userRequest.body();
return (requestBody != null && requestBody.isOneShot())
|| e instanceof FileNotFoundException;
}

Expand Down Expand Up @@ -289,7 +290,8 @@ private Request followUpRequest(Response userResponse, @Nullable Route route) th
return null;
}

if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
RequestBody requestBody = userResponse.request().body();
if (requestBody != null && requestBody.isOneShot()) {
return null;
}

Expand Down

This file was deleted.

0 comments on commit 0f624b5

Please sign in to comment.