別のマイクロサービスを呼び出す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.FeignException
、cause
フィールドは、改行などを含むJSON応答自体です。ただし、feign.FeignException
のcause
フィールドは、それ自体への参照です。 HystrixRuntimeExceptionの代わりに、より深い例外を伝播する方法はありますか?
また、ダウンストリームサービスからの応答に未加工の本文を含める方法があるので、ネストされた例外のメッセージフィールドを分解する必要はありませんか?
これは、別の構成を使用して実現できます。この構成では、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
}
}