3

I'm use the @FeignClient and want to do some logic(like record the exception information) when Feign throw Exception and then reply the result to front end.

I noticed Feign will throw FeignException when connection fail or http status not expect.

So I defined a @ExceptionHandler to caught FeignException after the callback method was invoked.

    @ExceptionHandler(value = FeignException.class)
    @ResponseBody
    public ResponseResult feignException(FeignException exception){
        String message = exception.getMessage();
        byte[] content = exception.content();
        int status = exception.status();
        if(content!=null){
            String response=new String(content);
            message=String.format("%s response message : %s",message,response);
        }
        log.warn("{} : {} , cause by : {}",exception.getClass().getSimpleName(),message,exception.getCause());
        return ResponseResult.fail(HttpStatus.valueOf(status),String.format("9%s00",status),message);

But it can't caught when I set the callback or callbackFactory of @FeignClient.

    @FeignClient(url = "${onboardingcase.uri}",name = "OnBoardingCaseService",
            fallbackFactory = OnBoardingCaseServiceFallBack.class)


@Component
@Slf4j
public class OnBoardingCaseServiceFallBack implements FallbackFactory<OnBoardingCaseService> {

    @Override
    public OnBoardingCaseService create(Throwable throwable) {
        return new OnBoardingCaseService() {
            @Override
            public OnBoardingCaseVo query(String coid) {

                if(throwable instanceof FeignException){
                    throw (FeignException)throwable;
                }
                return null;
            }
        };
    }
}

I noticed because hystrix took over this method.And will catch exception in HystrixInvocationHandler.

try {
                                Object fallback = HystrixInvocationHandler.this.fallbackFactory.create(this.getExecutionException());
                                Object result = ((Method)HystrixInvocationHandler.this.fallbackMethodMap.get(method)).invoke(fallback, args);
                                if (HystrixInvocationHandler.this.isReturnsHystrixCommand(method)) {
                                    return ((HystrixCommand)result).execute();
                                } else if (HystrixInvocationHandler.this.isReturnsObservable(method)) {
                                    return ((Observable)result).toBlocking().first();
                                } else if (HystrixInvocationHandler.this.isReturnsSingle(method)) {
                                    return ((Single)result).toObservable().toBlocking().first();
                                } else if (HystrixInvocationHandler.this.isReturnsCompletable(method)) {
                                    ((Completable)result).await();
                                    return null;
                                } else {
                                    return HystrixInvocationHandler.this.isReturnsCompletableFuture(method) ? ((Future)result).get() : result;
                                }
                            } catch (IllegalAccessException var3) {
                                throw new AssertionError(var3);
                            } catch (ExecutionException | InvocationTargetException var4) {
                                throw new AssertionError(var4.getCause());
                            } catch (InterruptedException var5) {
                                Thread.currentThread().interrupt();
                                throw new AssertionError(var5.getCause());
                            }

So I want to know how can I throw an exception when I using callback / callbackFactory or there is another way to instead callbackFactory to do the "call back"?

Many Thanks

chris
  • 51
  • 1
  • 5

2 Answers2

1

I found a solution to this problem.

public class OnBoardingCaseServiceFallBack implements FallbackFactory<OnBoardingCaseService> {

    @Override
    public OnBoardingCaseService create(Throwable throwable) {
        return new OnBoardingCaseService() {
            @Override
            public OnBoardingCaseVo query(String coid) {
                log.error("OnBoardingCaseService#query fallback , exception",throwable);
                if(throwable instanceof FeignException){
                    throw (FeignException)throwable;
                }

                return null;
            }
        };
    }
}

And then caught the HystrixRuntimeException and get the cause of exception in ExceptionHandler for get the realException that was wrapped by Hystrix.

    @ExceptionHandler(value = HystrixRuntimeException.class)
    @ResponseBody
    public ResponseResult hystrixRuntimeException(HystrixRuntimeException exception){
        Throwable fallbackException = exception.getFallbackException();
        Throwable assertError = fallbackException.getCause();
        Throwable realException = assertError.getCause();
        if(realException instanceof FeignException){
            FeignException feignException= (FeignException) realException;
            String message = feignException.getMessage();
            byte[] content = feignException.content();
            int status = feignException.status();
            if(content!=null){
                String response=new String(content);
                message=String.format("%s response message : %s",message,response);
            }
            return ResponseResult.fail(HttpStatus.valueOf(status),String.format("9%s00",status),message);
        }
        String message = exception.getMessage();
        log.warn("{} : {} , cause by : {}",exception.getClass().getSimpleName(),message,exception.getCause());
        return ResponseResult.fail(ResultCode.FAIL.httpStatus(),ResultCode.FAIL.code(),message);
    }

But I don't think that's a good way~

chris
  • 51
  • 1
  • 5
0

I have never done this in fallback, I have implemented custom error decoder(“CustomFeignErrorDecoder”) class and extended feign.codec.ErrorDecoder, every time an error occurs it comes to this class.

In decode function throw a custom exception and catch it in the controller or service layer to show your message to the frontend.

Example:

@Component
public class CustomFeignErrorDecoder implements ErrorDecoder {

   @Override
   public Exception decode(String methodKey, Response response) {
       throw new CustomFeignErrorDecoderException(methodKey +" response status "+ response.status() +" request "+ response.request()+ " method "+ response.request().httpMethod());
   }
}
Pavan Kumar Jorrigala
  • 3,085
  • 16
  • 27
  • Thanks Pavan. I have impemented the errorDecoder like your do but I found that the process is custom error decoder-> callback method(if the callback method was implemented). And it will wrap by HystrixRuntimeException if we throw an exception on callback method.So I tried to caught the HystrixRuntimeException and then parse the cause. And then I can get the exception that I custom. – chris Jan 13 '20 at 02:21
  • I added the implementation, Maybe try adding Hystix property "timeoutInMilliseconds", if you share a minimal code that I can replicate in my local to see the issue. – Pavan Kumar Jorrigala Jan 13 '20 at 22:10
  • **RuntimeException that is thrown when a HystrixCommand fails and does not have a fallback** as per [HystrixRuntimeException](https://netflix.github.io/Hystrix/javadoc/com/netflix/hystrix/exception/HystrixRuntimeException.html) – Pavan Kumar Jorrigala Jan 14 '20 at 14:41
  • Yes Pavan. So my design is throw a custom exception on fallback method. And then use @ExceptionHandler(value = HystrixRuntimeException.class) to caught HystrixRuntimeException. And I can get the custom exception in HystrixRuntimeException. what do you think ? that's a good way or not ? – chris Jan 16 '20 at 02:29
  • I showed the code in another answer . you can pull down the page and check it. thanks – chris Jan 16 '20 at 02:32
  • To answer your question I want to know the Hystrix timeout you used. My answer is for FeignClient without feign fallbacks. Hystrix is used for circuit breakers to identify the failed nodes and keep them out of the loop for a while. Feign is for handling SockerException. If any error occurs with rest you can catch in ErrorDecoder and propagate. Generally, if feign timeouts are 2 sec(with possible 4 to 5 retry) then the Hystrix timeout will be around 10 sec. Can you try commenting on Hystrix and see whether it is working? – Pavan Kumar Jorrigala Jan 16 '20 at 04:39
  • Hi Pavan . I have closed the timeout of hystrix. used hystrix:command:default:execution: timeout:enabled: false.Because I just want to use a failback method when feign exception. and not need any feature of hystix. – chris Jan 16 '20 at 08:05