response.body.asReader (), вызванный в FeignErrorDecoder, приводит к IOException: поток закрыт - PullRequest
0 голосов
/ 01 мая 2020

У меня есть приложение SpringBoot для микроуслуг, в котором мне трудно извлекать сообщение FeignException из клиента feign. Я пробовал решение здесь , но когда вызывается эта строка String result = CharStreams.toString(reader);, выдается IOException: stream is closed.. Это странно, потому что я использовал этот точный код в другом проекте, и не было выдано исключение.

Это пользовательское исключение из вызываемой службы:

public class CustomException extends RuntimeException {
    public CustomException(String s) {
        super(s);
    }
}

Это пользовательское исключение ответ:

public class CustomExceptionResponse {
    private String message;
    private HttpStatus status;

    public CustomExceptionResponse(String message, HttpStatus status) {
        this.message = message;
        this.status = status;
    }

    //getters and setters
}

это пользовательский обработчик исключений в вызываемой службе:

@ControllerAdvice
@RestController
public class ExceptionsHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public final ResponseEntity<CustomExceptionResponse> handleCustomException(Exception ex, WebRequest request) {
        CustomExceptionResponse response = new CustomExceptionResponse(ex.getMessage(), HttpStatus.UNAUTHORIZED);
        return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED);
    }
}

и здесь выдается исключение:

@Service
public class CustomServiceImpl implements CustomService {

    private CustomRepo repo;

    @Autowired
    public CustomServiceImpl(CustomRepo repo) {
        this.repo = repo;
    }

    @Override
    public int foo() {

        //some repo call
        if (condition)
            throw new CustomException("Custom Message");
        else
            //return some result
    }
}

при необходимости, я могу добавить дополнительную информацию

SpringBoot: 2.2.6 java: 1.8; springCloud: Hoxton.RELEASE NetflixEurekaClient: 2.2.2.RELEASE

EDIT Это клиентский класс симуляции:

public interface CustomServiceClient {

    @GetMapping(value = "/custom/{arg}")
    ResponseEntity<CustomEntity> customCheck(@PathVariable("arg") Long arg);
}

, откуда он вызывается:

public class CustomApiController implements CustomApi {

    private final SessionServiceImpl sessionService;
    private final CustomServiceClient customServiceClient;

    @Autowired
    public CustomApiController(CustomServiceClient customServiceClient,
                              SessionServiceImpl sessionService) {

        this.customServiceClient = customServiceClient;
        this.sessionService = sessionService;
    }

 @Override
    public ResponseEntity<CustomEntity> customCheck(@NotNull @Valid @PathVariable("arg") Long arg) {
        CallerCustomEntity entity;
        ResponseEntity<CustomEntity> customCheckResponse = customServiceClient
                .customCheck(arg);
        entity = Transformer.transformToModel(customCheckResponse.getBody());
        String sessionId = sessionService.createSession(entity);
        entity.setSessionId(sessionId);
        return ResponseEntity.ok(Transformer.transformToDto(entity));

    }


}

У меня также есть этот класс congfig (в вызывающей программе):

@Configuration
public class ClientConfig {
    @Autowired
    private Environment env;

    @PostConstruct
    private void configure() {
        // when the application runs in BCN a proxy is needed
        if (Boolean.valueOf(env.getProperty("client.proxy.enabled"))) {
            String proxyHost = env.getProperty("client.proxy.host");
            String proxyPort = env.getProperty("client.proxy.port");
            String proxyUser = env.getProperty("client.proxy.user");
            String proxyPassword = env.getProperty("client.proxy.password");
            String nonProxyHosts = env.getProperty("client.proxy.nonProxyHosts");

            Authenticator.setDefault(
                    new Authenticator() {
                        @Override
                        public PasswordAuthentication getPasswordAuthentication() {
                            return new PasswordAuthentication(
                                    proxyUser, proxyPassword.toCharArray());
                        }
                    }
            );

            System.setProperty("http.proxyHost", proxyHost);
            System.setProperty("http.proxyPort", proxyPort);
            System.setProperty("https.proxyHost", proxyHost);
            System.setProperty("https.proxyPort", proxyPort);
            System.setProperty("http.proxyUser", proxyUser);
            System.setProperty("http.proxyPassword", proxyPassword);
            System.setProperty("http.nonProxyHosts", nonProxyHosts); // for localhost and on-premise
        }
    }
}

и этот класс конфигурации (в вызывающей программе):

@Configuration
public class FeignConfiguration {

    private static final int CONNECT_TIME_OUT_MILLIS = 150000;
    private static final int READ_TIME_OUT_MILLIS = 150000;

    @Bean
    public Request.Options options() {
        return new Request.Options(CONNECT_TIME_OUT_MILLIS, READ_TIME_OUT_MILLIS);
    }
}

этот CORSFilter также ( в вызывающей программе):

@Component
public class SimpleCORSFilter implements Filter {

    private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class);

    public SimpleCORSFilter() {
        log.info("SimpleCORSFilter init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Session-ID, Accept, X-Requested-With, remember-me, "
                + "Authentication");

        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

Трассировка стека:

java.io.IOException: stream is closed
    at java.base/sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.ensureOpen(HttpURLConnection.java:3478)
    at java.base/sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3503)
    at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:297)
    at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
    at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188)
    at java.base/java.io.InputStreamReader.read(InputStreamReader.java:185)
    at java.base/java.io.Reader.read(Reader.java:229)
    at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1680)
    at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1659)
    at org.apache.commons.io.IOUtils.copy(IOUtils.java:1636)
    at org.apache.commons.io.IOUtils.toString(IOUtils.java:685)
    at com.example.webAppService.exception.decoder.FeignErrorDecoder.decode(FeignErrorDecoder.java:25)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:151)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
    at com.sun.proxy.$Proxy138.login(Unknown Source)
    at com.example.webAppService.controller.rest.CustomApiController.customCheck(CustomApiController.java:42)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at com.example.webAppService.client.rest.config.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:35)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:109)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:830)


EDIT 2 :

это кодировщик FeignErrorDecoder в вызывающей программе, как видно здесь :

@Component
public class FeignErrorDecoder implements ErrorDecoder {


    @Override
    public Exception decode(String methodKey, Response response) {
        Reader reader = null;

        try {
            reader = response.body().asReader();
            String result = CharStreams.toString(reader);//this line throws the exception because the InputStream from reader has closed=true:
            JSONObject jsonObject = new JSONObject(result);
            if(jsonObject.has("httpStatus") && jsonObject.has("message")){
                final HttpStatus status = HttpStatus.valueOf(jsonObject.get("httpStatus").toString());
                final String message = jsonObject.get("message").toString();
                return new ResponseStatusException(status,message);
            }
            else{
                return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Communication Error");
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return new Exception("Internal server Error");
    }
}
...