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

View File

@ -56,6 +56,10 @@ import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; 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.DoubleInetAddressDns;
import okhttp3.internal.RecordingOkAuthenticator; import okhttp3.internal.RecordingOkAuthenticator;
import okhttp3.internal.Util; import okhttp3.internal.Util;
@ -104,8 +108,11 @@ public final class CallTest {
@Rule public final MockWebServer server2 = new MockWebServer(); @Rule public final MockWebServer server2 = new MockWebServer();
@Rule public final InMemoryFileSystem fileSystem = new InMemoryFileSystem(); @Rule public final InMemoryFileSystem fileSystem = new InMemoryFileSystem();
private final RecordingEventListener listener = new RecordingEventListener();
private HandshakeCertificates handshakeCertificates = localhost(); private HandshakeCertificates handshakeCertificates = localhost();
private OkHttpClient client = defaultClient(); private OkHttpClient client = defaultClient().newBuilder()
.eventListener(listener)
.build();
private RecordingCallback callback = new RecordingCallback(); private RecordingCallback callback = new RecordingCallback();
private TestLogHandler logHandler = new TestLogHandler(); private TestLogHandler logHandler = new TestLogHandler();
private Cache cache = new Cache(new File("/cache/"), Integer.MAX_VALUE, fileSystem); 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("seed connection pool");
executeSynchronously("/").assertBody("retry success"); 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 { @Test public void recoverWhenRetryOnConnectionFailureIsTrue_HTTP2() throws Exception {

View File

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

View File

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

View File

@ -19,18 +19,18 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public final class RecordingEventListener extends EventListener { public final class RecordingEventListener extends EventListener {
final Deque<CallEvent> eventSequence = new ArrayDeque<>(); final Deque<CallEvent> eventSequence = new ConcurrentLinkedDeque<>();
final List<Object> forbiddenLocks = new ArrayList<>(); final List<Object> forbiddenLocks = new ArrayList<>();
@ -138,6 +138,10 @@ public final class RecordingEventListener extends EventListener {
logEvent(new RequestBodyEnd(call, byteCount)); logEvent(new RequestBodyEnd(call, byteCount));
} }
@Override public void requestFailed(Call call, IOException ioe) {
logEvent(new RequestFailed(call, ioe));
}
@Override public void responseHeadersStart(Call call) { @Override public void responseHeadersStart(Call call) {
logEvent(new ResponseHeadersStart(call)); logEvent(new ResponseHeadersStart(call));
} }
@ -154,6 +158,10 @@ public final class RecordingEventListener extends EventListener {
logEvent(new ResponseBodyEnd(call, byteCount)); logEvent(new ResponseBodyEnd(call, byteCount));
} }
@Override public void responseFailed(Call call, IOException ioe) {
logEvent(new ResponseFailed(call, ioe));
}
@Override public void callEnd(Call call) { @Override public void callEnd(Call call) {
logEvent(new CallEnd(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 { static final class ResponseHeadersStart extends CallEvent {
ResponseHeadersStart(Call call) { ResponseHeadersStart(Call call) {
super(call); super(call);
@ -411,4 +428,13 @@ public final class RecordingEventListener extends EventListener {
return new ResponseBodyStart(call); 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. * events.
* *
* <p>All event methods must execute fast, without external locking, cannot throw exceptions, * <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. * Any IO - writing to files or network should be done asynchronously.
*/ */
public abstract class EventListener { public abstract class EventListener {
@ -210,6 +210,15 @@ public abstract class EventListener {
public void requestBodyEnd(Call call, long byteCount) { 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. * Invoked just prior to receiving response headers.
* *
@ -256,6 +265,15 @@ public abstract class EventListener {
public void responseBodyEnd(Call call, long byteCount) { 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 * Invoked immediately after a call has completely ended. This includes delayed consumption
* of response body by the caller. * of response body by the caller.

View File

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

View File

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

View File

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