Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you allow 400 Errors to propagate when using Feign with Hystrix?

I'm building a SpringBoot microservice that calls another microservice and naturally want to use Hystrix and Feign clients, which are both included with Spring Cloud. I'm using version Camden.SR5.

For any timeouts, connection failures and 50x response codes from Feign, I want Hystrix to kick in and work as normal: tripping the circuit breaker and calling the fallback (if configured), etc. It does this by default, so I'm good.

But for 40x response codes, which include things like invalid entry, the wrong format of fields etc, I want Hystrix to propagate these exceptions to the caller, so I can handle them as I choose too. This isn't the default I've observed. How do you configure Hystrix/Feign to do this in Spring Cloud?

Out of the box using the following code:

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.hateoas.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient(name = "dog-service", url = "http://...")
public interface DogsFeignClient {
  @RequestMapping(method = RequestMethod.POST, path = "/dogs")
  Resource<Dog> createDog(Dog dog);
}

Generates this exception, which doesn't lend itself to nicely passing that 40x response back to the caller:

com.netflix.hystrix.exception.HystrixRuntimeException: DogsFeignClient#createDog(Dog) failed and no fallback available.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:805) ~[hystrix-core-1.5.6.jar:1.5.6]
    ....lines ommited for brevity....
Caused by: feign.FeignException: status 400 reading DogsFeignClient#createDog(Dog); content:
{
  "errors" : [ {
    "entity" : "Dog",
    "property" : "numberOfLegs",
    "invalidValue" : "3",
    "message" : "All dogs must have 4 legs"
  } ]
}
    at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-9.3.1.jar:na]
    at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-9.3.1.jar:na]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-9.3.1.jar:na]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.3.1.jar:na]
    at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108) ~[feign-hystrix-9.3.1.jar:na]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:301) ~[hystrix-core-1.5.6.jar:1.5.6]
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:297) ~[hystrix-core-1.5.6.jar:1.5.6]
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.1.10.jar:1.1.10]
    ... 26 common frames omitted

I can of course look at the com.netflix.hystrix.exception.HystrixRuntimeException, cause field which contains a feign.FeignException and buried in the description is the JSON response itself, with line breaks and such. But the cause field of feign.FeignException is a reference to itself. Is there a way to get a deeper exception propagated instead of the HystrixRuntimeException?

Also is there a way to get the raw body included with the response from the downstream service, so I don't have to deconstruct the message field of the nested exception?

like image 607
peterl Avatar asked Mar 11 '17 03:03

peterl


People also ask

How do you catch exceptions in Hystrix fallback?

Simply add a Throwable parameter to the fallback method and it will receive the exception which the original command produced. For Throwable e , it can show me the real reason, like FAILURE/SHORTCIRCUITED/TIMEOUT/BAD_REQUEST/SEMAPHORE_REJECTED/THREAD_POOL_REJECTED.

How do you use feign error decoder?

Error Decoder for Feign client You can use error-decoder to act based on the erroneous HTTP responses. I have observed that error-decoder does not get triggered on success scenarios. To implement an error-decoder, you need to implement a class using ErrorDecoder interface and add that in the configuration.

What is feign Hystrix?

Feign is a declarative web service client, which comes with Hystrix built in when you use it with Spring Cloud. Hystrix is a Netflix OSS library that implements the circuit breaker pattern.


1 Answers

This can be achieved using a separate configuration, which will wrap 400's in a subclass of HystrixBadRequestException and throw them to the client code. These exceptions don't affect the circuit breaker state - if the circuit is closed, it will remain closed, and if it's open, it will remain open.

@FeignClient(name = "dog-service", 
             url = "http://...", 
             configuration=FeignPropagateBadRequestsConfiguration.class)
public interface DogsFeignClient {
  @RequestMapping(method = RequestMethod.POST, path = "/dogs")
  Resource<Dog> createDog(Dog dog);
}

where FeignPropagateBadRequestsConfiguration is

@Configuration
public class FeignSkipBadRequestsConfiguration {
    @Bean
    public ErrorDecoder errorDecoder() {
        return (methodKey, response) -> {
            int status = response.status();
            if (status == 400) {
                String body = "Bad request";
                try {
                    body = IOUtils.toString(response.body().asReader());
                } catch (Exception ignored) {}
                HttpHeaders httpHeaders = new HttpHeaders();
                response.headers().forEach((k, v) -> httpHeaders.add("feign-" + k, StringUtils.join(v,",")));
                return new FeignBadResponseWrapper(status, httpHeaders, body);
            }
            else {
                return new RuntimeException("Response Code " + status);
            }
        };
    }
}

and FeignBadResponseWrapper is

@Getter
@Setter
public class FeignBadResponseWrapper extends HystrixBadRequestException {
    private final int status;
    private final HttpHeaders headers;
    private final String body;

    public FeignBadResponseWrapper(int status, HttpHeaders headers, String body) {
        super("Bad request");
        this.status = status;
        this.headers = headers;
        this.body = body;
    }
}

This is a bit of a hack, and you can get the response body only in ErrorDecoder, because after that the stream will be closed. But using this, you can throw the response data to client code without affecting the circuit:

    try {
        return dogsFeignClient.createDog(dog);
    } catch (HystrixBadRequestException he) {
        if (he instanceof FeignBadResponseWrapper) {
            // obtain data from wrapper and return it to client
        } else {
            // return basic error data for other exceptions
        }
    }
like image 92
jihor Avatar answered Sep 18 '22 16:09

jihor