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;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章