2

I am using feign client to connect to downstream service.

I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.

I am looking for a best way of doing this.

We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.

Shanid
  • 93
  • 2
  • 8

3 Answers3

2

You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:

public class ClientWrapper extends ApacheHttpClient {
   private ApacheHttpClient delegate;

   public ClientWrapper(ApacheHttpClient client) {
      this.client = client;
   }

   @Override
   public Response execute(Request request, Request.Options options) throws IOException {
      /* execute the request on the delegate */
      Response response = this.client.execute(request, options);

      /* check the response code and change */
      if (response.status() == 400) {
         response = Response.builder(response).status(200).build();
      }
      return response;
   }
}

This customized client can be used on any Feign client you need.

Kevin Davis
  • 1,193
  • 8
  • 14
  • Thanks kevin, I tried this logic by extending the default feign client as we are not using ApacheHttpClient, looks good except that I couldn't find Response.builder(response) method available ( I am using feign-core-9.5.0.jar ). I have tried another approach that I mentioned below as another answer. – Shanid May 21 '19 at 01:10
  • @Shanid, you are correct. This support was added in feign 10.x. – Kevin Davis May 23 '19 at 16:39
0

Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using @RestControllerAdvice )

public class CustomErrorDecoder implements ErrorDecoder {

@Override
public Exception decode(String methodKey, Response response) {

    if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
        ResponseData responseData;
        ObjectMapper mapper = new ObjectMapper();
        try {
            responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
        } catch (Exception e) {
            responseData = new ResponseData();
        }
        return new PartialSuccessException(responseData); 
    }
    return FeignException.errorStatus(methodKey, response);
}}

And the Exception handler as below

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(PartialSuccessException.class)
    public ResponseData handlePartialSuccessException(
            PartialSuccessException ex) {
        return ex.getResponseData();
    }
}
Shanid
  • 93
  • 2
  • 8
  • Please refer below thread too https://stackoverflow.com/questions/48441738/feignclient-throws-instead-of-returning-responseentity-with-error-http-status – Shanid May 21 '19 at 01:54
0

Change the microservice response:

public class CustomFeignClient extends Client.Default {

  public CustomFeignClient(
    final SSLSocketFactory sslContextFactory, final HostnameVerifier 
        hostnameVerifier) {
    super(sslContextFactory, hostnameVerifier);
  }

  @Override
  public Response execute(final Request request, final Request.Options 
    options) throws IOException {
    Response response = super.execute(request, options);
    if (HttpStatus.SC_OK != response.status()) {
      response =
        Response.builder()
          .status(HttpStatus.SC_OK)
          .body(InputStream.nullInputStream(), 0)
          .headers(response.headers())
          .request(response.request())
          .build();
    }
    return response;
  }
}

Add a Feign Client Config:

@Configuration
public class FeignClientConfig {
  @Bean
  public Client client() {
    return new CustomFeignClient(null, null);
  }
}