Skip to content

Commit cef5e55

Browse files
committed
do not unwrap checked and undeclared exceptions
1 parent 537b906 commit cef5e55

4 files changed

Lines changed: 77 additions & 3 deletions

File tree

core/src/main/java/feign/SynchronousMethodHandler.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import feign.interceptor.Invocation;
2525
import feign.interceptor.MethodInterceptor;
2626
import java.io.IOException;
27+
import java.lang.reflect.Method;
2728
import java.util.List;
2829
import java.util.concurrent.TimeUnit;
2930
import java.util.stream.Stream;
@@ -75,7 +76,12 @@ private Object runWithRetry(Invocation invocation, Options options) throws Throw
7576
retryer.continueOrPropagate(e);
7677
} catch (RetryableException th) {
7778
Throwable cause = th.getCause();
78-
if (methodHandlerConfiguration.getPropagationPolicy() == UNWRAP && cause != null) {
79+
if (methodHandlerConfiguration.getPropagationPolicy() == UNWRAP
80+
&& cause != null
81+
&& (cause instanceof RuntimeException
82+
|| cause instanceof Error
83+
|| isDeclaredCheckedException(
84+
cause, methodHandlerConfiguration.getMetadata().method()))) {
7985
throw cause;
8086
} else {
8187
throw th;
@@ -160,6 +166,16 @@ Options findOptions(Object[] argv) {
160166
.getMethodOptions(methodHandlerConfiguration.getMetadata().method().getName()));
161167
}
162168

169+
private static boolean isDeclaredCheckedException(Throwable cause, Method method) {
170+
Class<?>[] exceptionTypes = method.getExceptionTypes();
171+
for (Class<?> exType : exceptionTypes) {
172+
if (exType.isAssignableFrom(cause.getClass())) {
173+
return true;
174+
}
175+
}
176+
return false;
177+
}
178+
163179
static class Factory implements MethodHandler.Factory<Object> {
164180

165181
private final Client client;

core/src/test/java/feign/FeignTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import feign.querymap.FieldQueryMapEncoder;
5151
import java.io.IOException;
5252
import java.lang.reflect.Type;
53+
import java.net.ProtocolException;
5354
import java.net.URI;
5455
import java.time.Clock;
5556
import java.time.Instant;
@@ -708,6 +709,39 @@ void throwsRetryableExceptionIfNoUnderlyingCause() throws Exception {
708709
assertThat(exception.getMessage()).contains(message);
709710
}
710711

712+
@Test
713+
void doesNotUnwrapUndeclaredCheckedCauseWhenPropagationPolicyIsUnwrap() {
714+
TestInterface api =
715+
Feign.builder()
716+
.exceptionPropagationPolicy(UNWRAP)
717+
.retryer(new DefaultRetryer(1, 1, 1))
718+
.client(
719+
(_, _) -> {
720+
throw new ProtocolException("missing Location header for redirect");
721+
})
722+
.target(TestInterface.class, "http://localhost:" + server.getPort());
723+
724+
RetryableException exception = assertThrows(RetryableException.class, () -> api.post());
725+
assertThat(exception.getMessage()).contains("missing Location header for redirect");
726+
}
727+
728+
@Test
729+
void unwrapDeclaredCheckedCauseWhenPropagationPolicyIsUnwrap() {
730+
TestInterface api =
731+
Feign.builder()
732+
.exceptionPropagationPolicy(UNWRAP)
733+
.retryer(new DefaultRetryer(1, 1, 1))
734+
.client(
735+
(_, _) -> {
736+
throw new ProtocolException("missing Location header for redirect");
737+
})
738+
.target(TestInterface.class, "http://localhost:" + server.getPort());
739+
740+
ProtocolException exception =
741+
assertThrows(ProtocolException.class, () -> api.postThrowsProtocolException());
742+
assertThat(exception.getMessage()).contains("missing Location header for redirect");
743+
}
744+
711745
@Test
712746
void whenReturnTypeIsResponseNoErrorHandling() {
713747
Map<String, Collection<String>> headers = new LinkedHashMap<>();
@@ -1234,6 +1268,9 @@ interface TestInterface {
12341268
@RequestLine("POST /")
12351269
String post() throws TestInterfaceException;
12361270

1271+
@RequestLine("POST /")
1272+
String postThrowsProtocolException() throws ProtocolException;
1273+
12371274
@RequestLine("POST /")
12381275
@Body(
12391276
"%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\":"

httpclient/src/main/java/feign/httpclient/ApacheHttpClient.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,12 @@ public Response execute(Request request, Request.Options options) throws IOExcep
8181
} catch (URISyntaxException e) {
8282
throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);
8383
}
84-
HttpResponse httpResponse = client.execute(httpUriRequest);
85-
return toFeignResponse(httpResponse, request);
84+
try {
85+
HttpResponse httpResponse = client.execute(httpUriRequest);
86+
return toFeignResponse(httpResponse, request);
87+
} catch (Exception e) {
88+
throw new IOException(e);
89+
}
8690
}
8791

8892
HttpUriRequest toHttpUriRequest(Request request, Request.Options options)

httpclient/src/test/java/feign/httpclient/ApacheHttpClientTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import feign.Feign.Builder;
2424
import feign.FeignException;
2525
import feign.Request.Options;
26+
import feign.RetryableException;
27+
import feign.Retryer;
2628
import feign.client.AbstractClientTest;
2729
import feign.jaxrs.JAXRSContract;
2830
import java.nio.charset.StandardCharsets;
@@ -89,6 +91,21 @@ void notFollowRedirectIsRespected() throws InterruptedException {
8991
assertThat(server.takeRequest().getPath()).isEqualTo("/withOptions");
9092
}
9193

94+
@Test
95+
void redirectWithoutLocationHeader() {
96+
JaxRsTestInterface api =
97+
Feign.builder()
98+
.contract(new JAXRSContract())
99+
.retryer(Retryer.NEVER_RETRY)
100+
.client(new ApacheHttpClient(HttpClientBuilder.create().build()))
101+
.target(JaxRsTestInterface.class, "http://localhost:" + server.getPort());
102+
103+
server.enqueue(new MockResponse().setResponseCode(303));
104+
RetryableException exception =
105+
assertThrows(RetryableException.class, () -> api.withoutBody("foo"));
106+
assertThat(exception.getMessage()).contains("org.apache.http.client.ClientProtocolException");
107+
}
108+
92109
private JaxRsTestInterface buildTestInterface() {
93110
return Feign.builder()
94111
.contract(new JAXRSContract())

0 commit comments

Comments
 (0)