mirror of
https://github.com/square/okhttp.git
synced 2025-12-03 18:31:17 +03:00
Don't retry streamed HTTP request bodies.
This commit is contained in:
@@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import okhttp3.AbstractResponseCache;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.OkUrlFactory;
|
||||
@@ -83,11 +84,15 @@ public class CacheAdapterTest {
|
||||
ResponseCache responseCache = new AbstractResponseCache() {
|
||||
@Override public CacheResponse get(
|
||||
URI uri, String method, Map<String, List<String>> headers) throws IOException {
|
||||
try {
|
||||
assertEquals(toUri(serverUrl), uri);
|
||||
assertEquals("GET", method);
|
||||
assertTrue("Arbitrary standard header not present", headers.containsKey("User-Agent"));
|
||||
assertEquals(Collections.singletonList("value1"), headers.get("key1"));
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
throw new IOException("unexpected cache failure", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
setInternalCache(new CacheAdapter(responseCache));
|
||||
@@ -105,12 +110,16 @@ public class CacheAdapterTest {
|
||||
ResponseCache responseCache = new AbstractResponseCache() {
|
||||
@Override public CacheResponse get(URI uri, String method, Map<String, List<String>> headers)
|
||||
throws IOException {
|
||||
try {
|
||||
assertEquals("https", uri.getScheme());
|
||||
assertEquals(toUri(serverUrl), uri);
|
||||
assertEquals("GET", method);
|
||||
assertTrue("Arbitrary standard header not present", headers.containsKey("User-Agent"));
|
||||
assertEquals(Collections.singletonList("value1"), headers.get("key1"));
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
throw new IOException("unexpected cache failure", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
setInternalCache(new CacheAdapter(responseCache));
|
||||
@@ -136,6 +145,7 @@ public class CacheAdapterTest {
|
||||
|
||||
ResponseCache responseCache = new AbstractResponseCache() {
|
||||
@Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
|
||||
try {
|
||||
assertTrue(connection instanceof HttpURLConnection);
|
||||
assertFalse(connection instanceof HttpsURLConnection);
|
||||
|
||||
@@ -157,6 +167,9 @@ public class CacheAdapterTest {
|
||||
// The RI and OkHttp supports case-insensitive matching for this method.
|
||||
assertEquals("c", httpUrlConnection.getHeaderField("a"));
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
throw new IOException("unexpected cache failure", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
setInternalCache(new CacheAdapter(responseCache));
|
||||
@@ -175,6 +188,7 @@ public class CacheAdapterTest {
|
||||
|
||||
ResponseCache responseCache = new AbstractResponseCache() {
|
||||
@Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
|
||||
try {
|
||||
assertTrue(connection instanceof HttpURLConnection);
|
||||
assertFalse(connection instanceof HttpsURLConnection);
|
||||
|
||||
@@ -196,6 +210,9 @@ public class CacheAdapterTest {
|
||||
// The RI and OkHttp supports case-insensitive matching for this method.
|
||||
assertEquals("c", httpUrlConnection.getHeaderField("a"));
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
throw new IOException("unexpected cache failure", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
setInternalCache(new CacheAdapter(responseCache));
|
||||
@@ -211,6 +228,7 @@ public class CacheAdapterTest {
|
||||
|
||||
ResponseCache responseCache = new AbstractResponseCache() {
|
||||
@Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
|
||||
try {
|
||||
assertTrue(connection instanceof HttpsURLConnection);
|
||||
assertEquals(toUri(serverUrl), uri);
|
||||
assertEquals(serverUrl, connection.getURL());
|
||||
@@ -229,6 +247,9 @@ public class CacheAdapterTest {
|
||||
assertEquals(realHttpsUrlConnection.getLocalPrincipal(),
|
||||
cacheHttpsUrlConnection.getLocalPrincipal());
|
||||
return null;
|
||||
} catch (Throwable t) {
|
||||
throw new IOException("unexpected cache failure", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
setInternalCache(new CacheAdapter(responseCache));
|
||||
|
||||
@@ -322,6 +322,29 @@ public final class URLConnectionTest {
|
||||
assertEquals("body", server.takeRequest().getBody().readUtf8());
|
||||
}
|
||||
|
||||
@Test public void streamedBodyIsNotRetried() throws Exception {
|
||||
server.enqueue(new MockResponse()
|
||||
.setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST));
|
||||
|
||||
urlFactory = new OkUrlFactory(new OkHttpClient.Builder()
|
||||
.dns(new DoubleInetAddressDns())
|
||||
.build());
|
||||
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
||||
connection.setDoOutput(true);
|
||||
connection.setChunkedStreamingMode(100);
|
||||
OutputStream os = connection.getOutputStream();
|
||||
os.write("OutputStream is no fun.".getBytes("UTF-8"));
|
||||
os.close();
|
||||
|
||||
try {
|
||||
connection.getResponseCode();
|
||||
fail();
|
||||
} catch (IOException expected) {
|
||||
}
|
||||
|
||||
assertEquals(1, server.getRequestCount());
|
||||
}
|
||||
|
||||
@Test public void getErrorStreamOnSuccessfulRequest() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("A"));
|
||||
connection = urlFactory.open(server.url("/").url());
|
||||
|
||||
@@ -273,6 +273,7 @@ public final class Spdy3ConnectionTest {
|
||||
peer.play();
|
||||
|
||||
// play it back
|
||||
final CountDownLatch maxConcurrentStreamsUpdated = new CountDownLatch(1);
|
||||
final AtomicInteger maxConcurrentStreams = new AtomicInteger();
|
||||
FramedConnection.Listener listener = new FramedConnection.Listener() {
|
||||
@Override public void onStream(FramedStream stream) throws IOException {
|
||||
@@ -281,6 +282,7 @@ public final class Spdy3ConnectionTest {
|
||||
|
||||
@Override public void onSettings(FramedConnection connection) {
|
||||
maxConcurrentStreams.set(connection.maxConcurrentStreams());
|
||||
maxConcurrentStreamsUpdated.countDown();
|
||||
}
|
||||
};
|
||||
FramedConnection connection = connectionBuilder(peer, SPDY3)
|
||||
@@ -292,6 +294,7 @@ public final class Spdy3ConnectionTest {
|
||||
synchronized (connection) {
|
||||
assertEquals(10, connection.peerSettings.getMaxConcurrentStreams(-1));
|
||||
}
|
||||
maxConcurrentStreamsUpdated.await();
|
||||
assertEquals(10, maxConcurrentStreams.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ final class RealCall implements Call {
|
||||
releaseConnection = false;
|
||||
} catch (RouteException e) {
|
||||
// The attempt to connect via a route failed. The request will not have been sent.
|
||||
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), true, null);
|
||||
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), true, request);
|
||||
if (retryEngine != null) {
|
||||
releaseConnection = false;
|
||||
engine = retryEngine;
|
||||
@@ -251,7 +251,7 @@ final class RealCall implements Call {
|
||||
throw e.getLastConnectException();
|
||||
} catch (IOException e) {
|
||||
// An attempt to communicate with a server failed. The request may have been sent.
|
||||
HttpEngine retryEngine = engine.recover(e, false, null);
|
||||
HttpEngine retryEngine = engine.recover(e, false, request);
|
||||
if (retryEngine != null) {
|
||||
releaseConnection = false;
|
||||
engine = retryEngine;
|
||||
|
||||
@@ -295,13 +295,17 @@ public final class HttpEngine {
|
||||
* engine that should be used for the retry if {@code e} is recoverable, or null if the failure is
|
||||
* permanent. Requests with a body can only be recovered if the body is buffered.
|
||||
*/
|
||||
public HttpEngine recover(IOException e, boolean routeException, Response userResponse) {
|
||||
public HttpEngine recover(IOException e, boolean routeException, Request userRequest) {
|
||||
streamAllocation.streamFailed(e);
|
||||
|
||||
if (!client.retryOnConnectionFailure()) {
|
||||
return null; // The application layer has forbidden retries.
|
||||
}
|
||||
|
||||
if (!routeException && userRequest.body() instanceof UnrepeatableRequestBody) {
|
||||
return null; // We can't send the request body again.
|
||||
}
|
||||
|
||||
if (!isRecoverable(e, routeException)) {
|
||||
return null; // This exception is fatal.
|
||||
}
|
||||
@@ -310,11 +314,10 @@ public final class HttpEngine {
|
||||
return null; // No more routes to attempt.
|
||||
}
|
||||
|
||||
StreamAllocation streamAllocation = close(userResponse);
|
||||
StreamAllocation streamAllocation = close(null);
|
||||
|
||||
// For failure recovery, use the same route selector with a new connection.
|
||||
return new HttpEngine(client, userRequestUrl, forWebSocket, streamAllocation,
|
||||
priorResponse);
|
||||
return new HttpEngine(client, userRequestUrl, forWebSocket, streamAllocation, priorResponse);
|
||||
}
|
||||
|
||||
private boolean isRecoverable(IOException e, boolean routeException) {
|
||||
|
||||
Reference in New Issue
Block a user