java 在使用 Feign 和 Hystrix 时,如何允许 400 错误传播?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42730881/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 06:50:50  来源:igfitidea点击:

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

javaspringspring-cloudhystrixspring-cloud-feign

提问by peterl

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.

我正在构建一个调用另一个微服务的 SpringBoot 微服务,自然想使用 Hystrix 和 Feign 客户端,它们都包含在 Spring Cloud 中。我正在使用版本 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.

对于来自 Feign 的任何超时、连接失败和 50x 响应代码,我希望 Hystrix 启动并正常工作:跳闸断路器并调用回退(如果已配置)等。它默认执行此操作,所以我很好.

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?

但是对于 40x 响应代码,其中包括无效条目、错误的字段格式等,我希望 Hystrix 将这些异常传播给调用者,这样我也可以按照自己的选择处理它们。这不是我观察到的默认值。你如何配置 Hystrix/Feign 在 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:

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

com.netflix.hystrix.exception.HystrixRuntimeException: DogsFeignClient#createDog(Dog) failed and no fallback available.
    at com.netflix.hystrix.AbstractCommand.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.run(HystrixInvocationHandler.java:108) ~[feign-hystrix-9.3.1.jar:na]
    at com.netflix.hystrix.HystrixCommand.call(HystrixCommand.java:301) ~[hystrix-core-1.5.6.jar:1.5.6]
    at com.netflix.hystrix.HystrixCommand.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, causefield which contains a feign.FeignExceptionand buried in the description is the JSON response itself, with line breaks and such. But the causefield of feign.FeignExceptionis a reference to itself. Is there a way to get a deeper exception propagated instead of the HystrixRuntimeException?

我当然可以查看包含 a的com.netflix.hystrix.exception.HystrixRuntimeException,cause字段,feign.FeignException并且隐藏在描述中的是 JSON 响应本身,带有换行符等。但是causefeign.FeignException是对自身的引用。有没有办法传播更深层次的异常而不是 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?

还有没有办法从下游服务的响应中获取原始正文,所以我不必解构嵌套异常的消息字段?

回答by jihor

This can be achieved using a separate configuration, which will wrap 400's in a subclass of HystrixBadRequestExceptionand 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.

这可以使用单独的配置来实现,它将 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);
}

where FeignPropagateBadRequestsConfigurationis

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

and FeignBadResponseWrapperis

并且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;
    }
}

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:

这有点麻烦,您只能在 中获得响应正文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
        }
    }