RestTemplate之java.io.IOException:stream closed 异常的原因及处理

springboot集成resttemplate时想打印相关请求日志,设置统一的拦截器

拦截器相关代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * resttempate日志记录
 *
 * @author qijun
 * @date 2020/3/16 0016 17:27
 */
public class RestTemplateLogRecordInterceptor implements ClientHttpRequestInterceptor {

    private final static Logger LOGGER = LoggerFactory.getLogger(RestTemplateLogRecordInterceptor.class);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        traceResponse(response);
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        LOGGER.debug("===========================request begin================================================");
        LOGGER.debug("URI         : {}", request.getURI());
        LOGGER.debug("Method      : {}", request.getMethod());
        LOGGER.debug("Headers     : {}", request.getHeaders());
        LOGGER.debug("Request body: {}", new String(body, "UTF-8"));
        LOGGER.debug("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        InputStream body = response.getBody();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, "UTF-8"))) {
            String line = bufferedReader.readLine();
            while (line != null) {
                inputStringBuilder.append(line);
                inputStringBuilder.append('\n');
                line = bufferedReader.readLine();
            }
        }
        LOGGER.debug("============================response begin==========================================");
        LOGGER.debug("Status code  : {}", response.getStatusCode());
        LOGGER.debug("Status text  : {}", response.getStatusText());
        LOGGER.debug("Headers      : {}", response.getHeaders());
        LOGGER.debug("Response body: {}", inputStringBuilder.toString());
        LOGGER.debug("=======================response end=================================================");
    }


}

配置resttemplate自定义bean

@Configuration
public class RestTemplateConfiguration {

    @Bean
    public RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        //motor服务有的服务响应时间较长,暂定半个小时
        factory.setReadTimeout(1800000);
        factory.setConnectTimeout(1800000);
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.getInterceptors().add(new RestTemplateLogRecordInterceptor());
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }

}

经过上述运行后,会出现一个问题,问题如下:

在这里插入图片描述

试过很多方法依旧找不到原因,不知道为啥会被close掉
解决方案:怀疑是我加了拦截器的原因,我把拦截器注释掉,则不会报该错误,思考:为啥加了拦截器就会报错呢?
只能一步一步跟踪源码,发现只要加了拦截器,inputStream中的close属性就为true,所以这也是导致为啥调取接口的时候会is close
接下来就是找到为啥inputStream中的close为啥为true?
后来某大神看了这段代码说response和request流只能读取一次,只要读取完就结束了

private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        InputStream body = response.getBody();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, "UTF-8"))) {
            String line = bufferedReader.readLine();
            while (line != null) {
                inputStringBuilder.append(line);
                inputStringBuilder.append('\n');
                line = bufferedReader.readLine();
            }
        }
        LOGGER.debug("============================response begin==========================================");
        LOGGER.debug("Status code  : {}", response.getStatusCode());
        LOGGER.debug("Status text  : {}", response.getStatusText());
        LOGGER.debug("Headers      : {}", response.getHeaders());
        LOGGER.debug("Response body: {}", inputStringBuilder.toString());
        LOGGER.debug("=======================response end=================================================");
    }

我尝试了一次,把该代码注释掉,果然不出所料,是这里的原因,那么接下来就是如何解决流只能读取一次的问题,上网搜索了一番,网上好多人解决都是说用包装类可以解决该问题,将该对象缓存下来,就不会有问题,这也就是Servlet中Fileter的实现,Filter调用链如果不包装,也可能会出现该问题

好了,现在找到解决方案了,那么如何将该返回的ClientHttpResponse 包装一下呢,又不想自己自定义,后来就在org.springframework.http.client下面找了找,发现有类似的类,但是可用的包装类都是protected的,那么就想一下既然他提供了类似的包装类,那么看他内部咋调用类似的包装类,只能一点一点看源码,最后发现一个类BufferingClientHttpRequestWrapper,但是这个也是内部调用类,外部是无法新建的,只要找调用该类的地方,发现BufferingClientHttpRequestFactory类中有相关的实现,而且这个刚好是我需要的,刚好该类是public修饰,有点意思哈,不得不惊叹作者设计模式(工厂模式)玩的很溜啊
既然找到解决方案,代码修改如下:
在RestTemplate定义的时候,将BufferingClientHttpRequestFactory传入,而非传入SimpleClientHttpRequestFactory,最终解决问题

@Bean
    public RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(factory);
        //motor服务有的服务响应时间较长,暂定半个小时
        factory.setReadTimeout(1800000);
        factory.setConnectTimeout(1800000);
        RestTemplate restTemplate = new RestTemplate(bufferingClientHttpRequestFactory);
        restTemplate.getInterceptors().add(new RestTemplateLogRecordInterceptor());
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值