Feign: Inputstream is closed if we copy feign response

Created on 10 May 2018  路  6Comments  路  Source: OpenFeign/feign

Hi,
Is there any way to copy feign response? I have requirement to copy the feign client response and log it and it should be applied to all feign clients in the project without any configuration.

So I have build custom feign client which works like a charm. Problem is when I get response and copying to log then input stream is closed. So in later stage of decoding the response and converting to ResponseEntity, it throws the exception saying "stream is closed".

Example Class
`public class MyLoadBalancerClient extends LoadBalancerFeignClient {

public MyLoadBalancerClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory,
        SpringClientFactory clientFactory) {
    super(delegate, lbClientFactory, clientFactory);
}

@Override
public Response execute(Request request, Options options) throws IOException {
    Response response = super.execute(request, options);

    // Tried copying response
    Body body = response.body();

    Response extendedResponse = Response.builder().status(response.status()).reason(response.reason())
            .headers(response.headers()).body(body).build();

    // New extended response and hoping to use it. It works on below line.
    System.out.println(Util.toString(extendedResponse.asReader()));

    // response object input stream gets closed due to above line.
    return response;
}

}`

Exception
java.io.IOException: stream is closed at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.ensureOpen(HttpURLConnection.java:3348) at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3353) at java.io.FilterInputStream.read(FilterInputStream.java:83) at java.io.PushbackInputStream.read(PushbackInputStream.java:139) at org.springframework.web.client.MessageBodyClientHttpResponseWrapper.hasEmptyMessageBody(MessageBodyClientHttpResponseWrapper.java:97) at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:82) at org.springframework.cloud.netflix.feign.support.SpringDecoder.decode(SpringDecoder.java:57) at org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:47) at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:165) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:133) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) at com.sun.proxy.$Proxy90.myHello(Unknown Source) at com.alight.upoint.controller.YourController.myHello(YourController.java:17) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:108) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at com.alight.filter.UpointLoggingFilter.doFilterInternal(UpointLoggingFilter.java:71) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at com.alight.filter.AlightSessionTokenLoggingFilter.doFilterInternal(AlightSessionTokenLoggingFilter.java:71) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

Most helpful comment

No, I meant cloning the body. Creating yourself a new Response.Body object, maybe using a ByteArrayInputStream that uses the bytes that you read from the original body object, printing the original information and returning the cloned response (which at this point should have a ByteArrayInputStream that has not been read from yet). Have you tried that?

All 6 comments

Have you tried cloning the body as well? You can't expect to be able to read through the same input stream 2x, so you may need to cache the data and return the extended response with an extended body using the copied data in a new input stream.

yes I tried all possible combinations of body or input stream but none of them are working.

No, I meant cloning the body. Creating yourself a new Response.Body object, maybe using a ByteArrayInputStream that uses the bytes that you read from the original body object, printing the original information and returning the cloned response (which at this point should have a ByteArrayInputStream that has not been read from yet). Have you tried that?

@rage-shadowman Thanks a lot. I am able to copy response with following code.

InputStream is = response.body().asInputStream();
byte[] bytes = IOUtils.toByteArray(is);

Response copiedResponse = Response.builder().status(response.status()).reason(response.reason()).headers(response.headers()).body(bytes).build(); 

"Steam is closed" means you may read stream somewhere. In this case, the root reason is toString() of respsonse. Those function reads the steam and ensure closed the tream. That means you can not display response in debug model in IDEA. So dot no log response before read it.

Logger / System.out.println / IDE Debug mode

If you use any of the above feature to print / log / view your response object then it will process the response.body() internally and close the InputStream. So in this case you will get Stream is closed error.

To fix this issue process the response.body() before the logger. Now you can check this by running your application not in debug mode.

Sample code:

```
@Override
public Exception decode(final String methodKey, final Response response) {
final String error = getResponseBodyAsString(response.body());
LOGGER.error("{} failed with response {}", methodKey, response);
return new ServiceException("Request failed with status: " + response.status()
+ " and error: " + error);
}

private String getResponseBodyAsString(final Response.Body body) {
try {
return IOUtils.toString(body.asReader(StandardCharsets.UTF_8));
} catch (final IOException e) {
LOGGER.error("Failed to read the response body with error: ", e);
}
return null;
}
```
Note: If you are in debug mode then your IDEA will process this response so even that case also you will get same error. So please don't check this in debug mode.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JerryChaox picture JerryChaox  路  3Comments

firebook picture firebook  路  5Comments

dragontree101 picture dragontree101  路  7Comments

tjuchniewicz picture tjuchniewicz  路  5Comments

budaimartin picture budaimartin  路  4Comments