diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index 01e4a2fe..2dd3cd4a 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -131,12 +131,14 @@ final class SynchronousMethodHandler implements MethodHandler { if (void.class == metadata.returnType()) { return null; } else { + Object result = decode(response); shouldClose = closeAfterDecode; - return decode(response); + return result; } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { + Object result = decode(response); shouldClose = closeAfterDecode; - return decode(response); + return result; } else { throw errorDecoder.decode(metadata.configKey(), response); } diff --git a/core/src/test/java/feign/FeignBuilderTest.java b/core/src/test/java/feign/FeignBuilderTest.java index 501a3c14..3c785984 100644 --- a/core/src/test/java/feign/FeignBuilderTest.java +++ b/core/src/test/java/feign/FeignBuilderTest.java @@ -21,6 +21,8 @@ import org.junit.Rule; import org.junit.Test; import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -29,6 +31,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import feign.codec.Decoder; @@ -349,6 +352,75 @@ public class FeignBuilderTest { assertEquals(1, server.getRequestCount()); } + /** + * When {@link Feign.Builder#doNotCloseAfterDecode()} is enabled an an exception + * is thrown from the {@link Decoder}, the response should be closed. + */ + @Test + public void testDoNotCloseAfterDecodeDecoderFailure() throws Exception { + server.enqueue(new MockResponse().setBody("success!")); + + String url = "http://localhost:" + server.getPort(); + Decoder angryDecoder = new Decoder() { + @Override + public Object decode(Response response, Type type) throws IOException { + throw new IOException("Failed to decode the response"); + } + }; + + final AtomicBoolean closed = new AtomicBoolean(); + TestInterface api = Feign.builder() + .client(new Client() { + Client client = new Client.Default(null, null); + @Override + public Response execute(Request request, Request.Options options) throws IOException { + final Response original = client.execute(request, options); + return Response.builder() + .status(original.status()) + .headers(original.headers()) + .reason(original.reason()) + .request(original.request()) + .body(new Response.Body() { + @Override + public Integer length() { + return original.body().length(); + } + + @Override + public boolean isRepeatable() { + return original.body().isRepeatable(); + } + + @Override + public InputStream asInputStream() throws IOException { + return original.body().asInputStream(); + } + + @Override + public Reader asReader() throws IOException { + return original.body().asReader(); + } + + @Override + public void close() throws IOException { + closed.set(true); + original.body().close(); + } + }) + .build(); + } + }) + .decoder(angryDecoder) + .doNotCloseAfterDecode() + .target(TestInterface.class, url); + try { + api.decodedLazyPost(); + fail("Expected an exception"); + } catch (FeignException expected) { + } + assertTrue("Responses must be closed when the decoder fails", closed.get()); + } + interface TestInterface { @RequestLine("GET") Response getNoPath();