1
0
mirror of https://github.com/square/okhttp.git synced 2025-08-01 16:06:56 +03:00

Introduce EventListener.requestFailed, responseFailed events

These replace requestBodyEnd() / responseBodyEnd() in some failure scenarios.
They may also be issued in cases where no event was published previously.
This commit is contained in:
Jesse Wilson
2019-02-23 18:38:47 -05:00
parent 03f4170e89
commit aac6c8da07
9 changed files with 115 additions and 12 deletions

View File

@ -124,6 +124,11 @@ public final class LoggingEventListener extends EventListener {
logWithTime("requestBodyEnd: byteCount=" + byteCount);
}
@Override
public void requestFailed(Call call, IOException ioe) {
logWithTime("requestFailed: " + ioe);
}
@Override
public void responseHeadersStart(Call call) {
logWithTime("responseHeadersStart");
@ -144,6 +149,11 @@ public final class LoggingEventListener extends EventListener {
logWithTime("responseBodyEnd: byteCount=" + byteCount);
}
@Override
public void responseFailed(Call call, IOException ioe) {
logWithTime("responseFailed: " + ioe);
}
@Override
public void callEnd(Call call) {
logWithTime("callEnd");

View File

@ -56,6 +56,10 @@ import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import okhttp3.RecordingEventListener.CallEnd;
import okhttp3.RecordingEventListener.ConnectionAcquired;
import okhttp3.RecordingEventListener.ConnectionReleased;
import okhttp3.RecordingEventListener.ResponseFailed;
import okhttp3.internal.DoubleInetAddressDns;
import okhttp3.internal.RecordingOkAuthenticator;
import okhttp3.internal.Util;
@ -104,8 +108,11 @@ public final class CallTest {
@Rule public final MockWebServer server2 = new MockWebServer();
@Rule public final InMemoryFileSystem fileSystem = new InMemoryFileSystem();
private final RecordingEventListener listener = new RecordingEventListener();
private HandshakeCertificates handshakeCertificates = localhost();
private OkHttpClient client = defaultClient();
private OkHttpClient client = defaultClient().newBuilder()
.eventListener(listener)
.build();
private RecordingCallback callback = new RecordingCallback();
private TestLogHandler logHandler = new TestLogHandler();
private Cache cache = new Cache(new File("/cache/"), Integer.MAX_VALUE, fileSystem);
@ -1041,6 +1048,17 @@ public final class CallTest {
executeSynchronously("/").assertBody("seed connection pool");
executeSynchronously("/").assertBody("retry success");
// The call that seeds the connection pool.
listener.removeUpToEvent(CallEnd.class);
// The ResponseFailed event is not necessarily fatal!
listener.removeUpToEvent(ConnectionAcquired.class);
listener.removeUpToEvent(ResponseFailed.class);
listener.removeUpToEvent(ConnectionReleased.class);
listener.removeUpToEvent(ConnectionAcquired.class);
listener.removeUpToEvent(ConnectionReleased.class);
listener.removeUpToEvent(CallEnd.class);
}
@Test public void recoverWhenRetryOnConnectionFailureIsTrue_HTTP2() throws Exception {

View File

@ -319,7 +319,7 @@ public final class DuplexTest {
"RequestHeadersStart", "RequestHeadersEnd", "RequestBodyStart", "ResponseHeadersStart",
"ResponseHeadersEnd", "ResponseBodyStart", "ResponseBodyEnd", "RequestHeadersStart",
"RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart",
"ResponseBodyEnd", "ConnectionReleased", "CallEnd", "RequestBodyEnd");
"ResponseBodyEnd", "ConnectionReleased", "CallEnd", "RequestFailed");
assertEquals(expectedEvents, listener.recordedEventTypes());
}

View File

@ -38,6 +38,7 @@ import okhttp3.RecordingEventListener.DnsStart;
import okhttp3.RecordingEventListener.RequestBodyEnd;
import okhttp3.RecordingEventListener.RequestHeadersEnd;
import okhttp3.RecordingEventListener.ResponseBodyEnd;
import okhttp3.RecordingEventListener.ResponseFailed;
import okhttp3.RecordingEventListener.ResponseHeadersEnd;
import okhttp3.RecordingEventListener.SecureConnectEnd;
import okhttp3.RecordingEventListener.SecureConnectStart;
@ -168,7 +169,8 @@ public final class EventListenerTest {
List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd",
"ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart",
"RequestHeadersEnd", "ResponseHeadersStart", "ConnectionReleased", "CallFailed");
"RequestHeadersEnd", "ResponseHeadersStart", "ResponseFailed", "ConnectionReleased",
"CallFailed");
assertEquals(expectedEvents, listener.recordedEventTypes());
}
@ -197,10 +199,10 @@ public final class EventListenerTest {
List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd",
"ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart",
"RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart",
"ResponseBodyEnd", "ConnectionReleased", "CallFailed");
"ResponseFailed", "ConnectionReleased", "CallFailed");
assertEquals(expectedEvents, listener.recordedEventTypes());
ResponseBodyEnd bodyEnd = listener.removeUpToEvent(ResponseBodyEnd.class);
assertEquals(5, bodyEnd.bytesRead);
ResponseFailed responseFailed = listener.removeUpToEvent(ResponseFailed.class);
assertEquals("unexpected end of stream", responseFailed.ioe.getMessage());
}
@Test public void canceledCallEventSequence() {
@ -1052,7 +1054,7 @@ public final class EventListenerTest {
List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd", "ConnectStart",
"ConnectEnd", "ConnectionAcquired", "RequestHeadersStart", "RequestHeadersEnd",
"RequestBodyStart", "RequestBodyEnd", "ConnectionReleased", "CallFailed");
"RequestBodyStart", "RequestFailed", "ConnectionReleased", "CallFailed");
assertEquals(expectedEvents, listener.recordedEventTypes());
}

View File

@ -19,18 +19,18 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import javax.annotation.Nullable;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public final class RecordingEventListener extends EventListener {
final Deque<CallEvent> eventSequence = new ArrayDeque<>();
final Deque<CallEvent> eventSequence = new ConcurrentLinkedDeque<>();
final List<Object> forbiddenLocks = new ArrayList<>();
@ -138,6 +138,10 @@ public final class RecordingEventListener extends EventListener {
logEvent(new RequestBodyEnd(call, byteCount));
}
@Override public void requestFailed(Call call, IOException ioe) {
logEvent(new RequestFailed(call, ioe));
}
@Override public void responseHeadersStart(Call call) {
logEvent(new ResponseHeadersStart(call));
}
@ -154,6 +158,10 @@ public final class RecordingEventListener extends EventListener {
logEvent(new ResponseBodyEnd(call, byteCount));
}
@Override public void responseFailed(Call call, IOException ioe) {
logEvent(new ResponseFailed(call, ioe));
}
@Override public void callEnd(Call call) {
logEvent(new CallEnd(call));
}
@ -374,6 +382,15 @@ public final class RecordingEventListener extends EventListener {
}
}
static final class RequestFailed extends CallEvent {
final IOException ioe;
RequestFailed(Call call, IOException ioe) {
super(call, ioe);
this.ioe = ioe;
}
}
static final class ResponseHeadersStart extends CallEvent {
ResponseHeadersStart(Call call) {
super(call);
@ -411,4 +428,13 @@ public final class RecordingEventListener extends EventListener {
return new ResponseBodyStart(call);
}
}
static final class ResponseFailed extends CallEvent {
final IOException ioe;
ResponseFailed(Call call, IOException ioe) {
super(call, ioe);
this.ioe = ioe;
}
}
}

View File

@ -46,7 +46,7 @@ import javax.annotation.Nullable;
* events.
*
* <p>All event methods must execute fast, without external locking, cannot throw exceptions,
* attempt to mutate the event parameters, or be reentrant back into the client.
* attempt to mutate the event parameters, or be re-entrant back into the client.
* Any IO - writing to files or network should be done asynchronously.
*/
public abstract class EventListener {
@ -210,6 +210,15 @@ public abstract class EventListener {
public void requestBodyEnd(Call call, long byteCount) {
}
/**
* Invoked when a request fails to be written.
*
* <p>This method is invoked after {@link #requestHeadersStart} or {@link #requestBodyStart}. Note
* that request failures do not necessarily fail the entire call.
*/
public void requestFailed(Call call, IOException ioe) {
}
/**
* Invoked just prior to receiving response headers.
*
@ -256,6 +265,15 @@ public abstract class EventListener {
public void responseBodyEnd(Call call, long byteCount) {
}
/**
* Invoked when a response fails to be read.
*
* <p>This method is invoked after {@link #responseHeadersStart} or {@link #responseBodyStart}.
* Note that response failures do not necessarily fail the entire call.
*/
public void responseFailed(Call call, IOException ioe) {
}
/**
* Invoked immediately after a call has completely ended. This includes delayed consumption
* of response body by the caller.

View File

@ -66,6 +66,7 @@ public final class Exchange {
codec.writeRequestHeaders(request);
eventListener.requestHeadersEnd(call, request);
} catch (IOException e) {
eventListener.requestFailed(call, e);
trackFailure(e);
throw e;
}
@ -82,6 +83,7 @@ public final class Exchange {
try {
codec.flushRequest();
} catch (IOException e) {
eventListener.requestFailed(call, e);
trackFailure(e);
throw e;
}
@ -91,6 +93,7 @@ public final class Exchange {
try {
codec.finishRequest();
} catch (IOException e) {
eventListener.requestFailed(call, e);
trackFailure(e);
throw e;
}
@ -108,6 +111,7 @@ public final class Exchange {
}
return result;
} catch (IOException e) {
eventListener.responseFailed(call, e);
trackFailure(e);
throw e;
}
@ -126,6 +130,7 @@ public final class Exchange {
ResponseBodySource source = new ResponseBodySource(rawSource, contentLength);
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
} catch (IOException e) {
eventListener.responseFailed(call, e);
trackFailure(e);
throw e;
}
@ -167,11 +172,19 @@ public final class Exchange {
trackFailure(e);
}
if (requestDone) {
if (e != null) {
eventListener.requestFailed(call, e);
} else {
eventListener.requestBodyEnd(call, bytesRead);
}
}
if (responseDone) {
if (e != null) {
eventListener.responseFailed(call, e);
} else {
eventListener.responseBodyEnd(call, bytesRead);
}
}
transmitter.exchangeMessageDone(this, requestDone, responseDone, e);
}

View File

@ -156,6 +156,10 @@ public final class PrintEvents {
printEvent("requestBodyEnd");
}
@Override public void requestFailed(Call call, IOException ioe) {
printEvent("requestFailed");
}
@Override public void responseHeadersStart(Call call) {
printEvent("responseHeadersStart");
}
@ -172,6 +176,10 @@ public final class PrintEvents {
printEvent("responseBodyEnd");
}
@Override public void responseFailed(Call call, IOException ioe) {
printEvent("responseFailed");
}
@Override public void callEnd(Call call) {
printEvent("callEnd");
}

View File

@ -131,6 +131,10 @@ public final class PrintEventsNonConcurrent {
printEvent("requestBodyEnd");
}
@Override public void requestFailed(Call call, IOException ioe) {
printEvent("requestFailed");
}
@Override public void responseHeadersStart(Call call) {
printEvent("responseHeadersStart");
}
@ -147,6 +151,10 @@ public final class PrintEventsNonConcurrent {
printEvent("responseBodyEnd");
}
@Override public void responseFailed(Call call, IOException ioe) {
printEvent("responseFailed");
}
@Override public void callEnd(Call call) {
printEvent("callEnd");
}