使用Feys with Hystrix时如何允许传播400个错误?

时间:2017-03-11 03:06:48

标签: java spring spring-cloud hystrix spring-cloud-feign

我正在构建一个调用另一个微服务的SpringBoot微服务,并且自然希望使用Hystrix和Feign客户端,它们都包含在Spring Cloud中。我正在使用版本Camden.SR5。

对于Feign的任何超时,连接失败和50x响应代码,我希望Hystrix正常启动并正常工作:绊倒断路器并调用回退(如果已配置)等。默认情况下这样做,所以我很好。

但是对于40x响应代码,包括无效输入,字段格式错误等,我希望Hystrix将这些异常传播给调用者,所以我也可以按照我的选择处理它们。这不是我观察到的默认值。 如何配置Hystrix / Feign在Spring Cloud中执行此操作?

使用以下代码开箱即用:

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);
}

生成此异常,它不能很好地将40x响应传递给调用者:

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

我当然可以查看包含com.netflix.hystrix.exception.HystrixRuntimeException的{​​{1}},cause字段,并在说明中隐藏JSON响应本身,并使用换行符等。但feign.FeignException的{​​{1}}字段是对自身的引用。有没有办法让传播更深层的异常而不是HystrixRuntimeException?

还有一种方法可以将原始主体包含在下游服务的响应中,因此我不必解构嵌套异常的消息字段吗?

1 个答案:

答案 0 :(得分:5)

这可以使用单独的配置来实现,该配置将400包装在HystrixBadRequestException的子类中并将它们抛给客户端代码。 这些例外情况不会影响断路器状态 - 如果电路关闭,它将保持关闭状态,如果它打开,它将保持打开状态。

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

其中FeignPropagateBadRequestsConfiguration

@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);
            }
        };
    }
}

FeignBadResponseWrapper

@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;
    }
}

这有点像黑客,你只能在ErrorDecoder获得响应主体,因为之后流将被关闭。但是使用它,您可以将响应数据抛出到客户端代码而不会影响电路:

    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
        }
    }