diff --git a/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java b/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java index 35921a02c..2abdacdf9 100644 --- a/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java +++ b/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java @@ -85,9 +85,9 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static okhttp3.TestUtil.defaultClient; import static okhttp3.internal.Util.UTF_8; -import static okhttp3.internal.huc.OkHttpURLConnection.SELECTED_PROTOCOL; import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; +import static okhttp3.internal.huc.OkHttpURLConnection.SELECTED_PROTOCOL; import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AFTER_REQUEST; import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AT_END; import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AT_START; @@ -3503,6 +3503,48 @@ public final class URLConnectionTest { } } + /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */ + @Test public void unexpectedExceptionSync() throws Exception { + urlFactory.setClient(urlFactory.client().newBuilder() + .dns(new Dns() { + @Override public List lookup(String hostname) { + throw new RuntimeException("boom!"); + } + }) + .build()); + + server.enqueue(new MockResponse()); + + HttpURLConnection connection = urlFactory.open(server.url("/").url()); + try { + connection.getResponseCode(); // Use the synchronous implementation. + fail(); + } catch (RuntimeException expected) { + assertEquals("boom!", expected.getMessage()); + } + } + + /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */ + @Test public void unexpectedExceptionAsync() throws Exception { + urlFactory.setClient(urlFactory.client().newBuilder() + .dns(new Dns() { + @Override public List lookup(String hostname) { + throw new RuntimeException("boom!"); + } + }) + .build()); + + server.enqueue(new MockResponse()); + + HttpURLConnection connection = urlFactory.open(server.url("/").url()); + try { + connection.connect(); // Force the async implementation. + fail(); + } catch (RuntimeException expected) { + assertEquals("boom!", expected.getMessage()); + } + } + private void testInstanceFollowsRedirects(String spec) throws Exception { URL url = new URL(spec); HttpURLConnection urlConnection = urlFactory.open(url); diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java index 6a2a02a7e..aeaa9ec4b 100644 --- a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java +++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java @@ -100,7 +100,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call // These fields are guarded by lock. private final Object lock = new Object(); private Response response; - private IOException callFailure; + private Throwable callFailure; Response networkResponse; boolean connectPending = true; Proxy proxy; @@ -129,7 +129,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call lock.wait(); // Wait 'til the network interceptor is reached or the call fails. } if (callFailure != null) { - throw callFailure; + throw propagate(callFailure); } } catch (InterruptedException e) { throw new InterruptedIOException(); @@ -382,6 +382,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call OkHttpClient.Builder clientBuilder = client.newBuilder(); clientBuilder.interceptors().clear(); + clientBuilder.interceptors().add(UnexpectedException.INTERCEPTOR); clientBuilder.networkInterceptors().clear(); clientBuilder.networkInterceptors().add(networkInterceptor); @@ -408,7 +409,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call } else if (networkResponse != null) { return networkResponse; } else if (callFailure != null) { - throw callFailure; + throw propagate(callFailure); } Call call = buildCall(); @@ -437,7 +438,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call } synchronized (lock) { - if (callFailure != null) throw callFailure; + if (callFailure != null) throw propagate(callFailure); if (response != null) return response; } @@ -573,7 +574,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call @Override public void onFailure(Call call, IOException e) { synchronized (lock) { - this.callFailure = e; + this.callFailure = (e instanceof UnexpectedException) ? e.getCause() : e; lock.notifyAll(); } } @@ -587,6 +588,32 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call } } + static final class UnexpectedException extends IOException { + static final Interceptor INTERCEPTOR = new Interceptor() { + @Override public Response intercept(Chain chain) throws IOException { + try { + return chain.proceed(chain.request()); + } catch (IOException e) { + throw e; + } catch (Error | RuntimeException e) { + throw new UnexpectedException(e); + } + } + }; + + public UnexpectedException(Throwable cause) { + super(cause); + } + } + + /** Throws {@code throwable} as either an IOException, RuntimeException, or Error. */ + private static IOException propagate(Throwable throwable) throws IOException { + if (throwable instanceof IOException) throw (IOException) throwable; + if (throwable instanceof Error) throw (Error) throwable; + if (throwable instanceof RuntimeException) throw (RuntimeException) throwable; + throw new AssertionError(); + } + /** * The HttpURLConnection gives the application control between establishing the connection and * transmitting the request body. This interceptor stalls async calls right at this point. The