web-dev-qa-db-ja.com

HystrixでFeignを使用する場合、400エラーをどのように伝播させるのですか?

別のマイクロサービスを呼び出すSpringBootマイクロサービスを構築していて、当然、SpringCloudに含まれているHystrixクライアントとFeignクライアントを使用したいと考えています。私はバージョンCamden.SR5を使用しています。

タイムアウト、接続障害、Feignからの50x応答コードについては、Hystrixを起動して通常どおりに動作させたい:回路ブレーカーをトリップしてフォールバックを呼び出す(構成されている場合)など。これはデフォルトで行われるので、問題ありません。 。

しかし、無効なエントリ、フィールドの誤った形式などを含む40x応答コードの場合、Hystrixがこれらの例外を呼び出し元に伝達するようにしたいので、私が選択したとおりに処理できます。これは私が観察したデフォルトではありません。 Spring Cloudでこれを行うために、Hystrix/Feignをどのように構成しますか?

次のコードを使用して、箱から出してすぐに使用できます。

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

この例外を生成しますが、その40倍の応答を呼び出し元にうまく返すことはできません。

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を含み説明に埋め込まれているfeign.FeignExceptioncauseフィールドは、改行などを含むJSON応答自体です。ただし、feign.FeignExceptioncauseフィールドは、それ自体への参照です。 HystrixRuntimeExceptionの代わりに、より深い例外を伝播する方法はありますか?

また、ダウンストリームサービスからの応答に未加工の本文を含める方法があるので、ネストされた例外のメッセージフィールドを分解する必要はありませんか?

6
peterl

これは、別の構成を使用して実現できます。この構成では、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
        }
    }
8
jihor