Resilience4J Circuit Breaker to kick-in on specific HTTP status code - java

I am aware we can use recordExceptions() while building CircuitBreakerConfig to register exceptions on which Circuit Breaker should transition to OPEN state.
Code
I am using resilience4j-feign to decorate my CircuitBreaker. Would be really helpful if you can point me to a code example.
Question
How to make the Circuit Breaker kick-in in case of a specific HTTP status code (e.g. on 503 Service Unavailable) ?

You need to write an Exception/Response handler to your client's external calls and throw custom exceptions based on http status received. Then register these exceptions as record exceptions in your circuit breaker config. Following is a small example. The CB will be open only on AbcException. The CB config isresilience4j.circuitbreaker.instances.bookService.record-exceptions=com.sk.example.cb.circuitbreakerr4j.AbcException
#Service
#Slf4j
public class BookApiService {
RestTemplate restTemplate = new RestTemplate();
#CircuitBreaker(name = "bookService", fallbackMethod = "getBookFallback")
public String getBook(){
try {
ResponseEntity<String> stringResponseEntity = restTemplate.getForEntity(new URI("http://localhost:8080/book"), String.class);
if(null != stringResponseEntity){
if(stringResponseEntity.getStatusCode().is2xxSuccessful()){
return stringResponseEntity.getBody();
}
}
} catch (URISyntaxException e) {
e.printStackTrace();
}catch (HttpServerErrorException e){
log.error("Service unavailable",e);
if(e.getMessage().startsWith("503")) {
throw new AbcException();
}else{
throw e;
}
}
return "";
}

From the docs, Create and configure a CircuitBreaker:
// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordExceptions(IOException.class, TimeoutException.class) // add you exceptions here!!!
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.build();
A list of exceptions that are recorded as a failure and thus increase
the failure rate. Any exception matching or inheriting from one of the
list counts as a failure, unless explicitly ignored via
ignoreExceptions.

TL;DR: use a custom exception that communicates HTTP status (e.g. 503) from HTTP client (e.g. Feign) to Resilience4J
Feign: implement and configure an ErrorDecoder to throw a custom exception on HTTP status like 503
Resilience4J: record that custom exception using Circuit Breaker config.
Feign
Feign by default throws a FeignException in case of an erroneous HTTP status code. You can get the status code number via method int status().
To customize your feign-clients error-handling configure a (custom) implementation of ErrorDecoder
If you need more control over handling unexpected responses, Feign instances can register a custom ErrorDecoder via the builder.
[..]
All responses that result in an HTTP status not in the 2xx range will trigger the ErrorDecoder's decode method, allowing you to handle the response, wrap the failure into a custom exception or perform any additional processing. If you want to retry the request again, throw a RetryableException. This will invoke the registered Retryer.
Customize Feign error-handling
Implement and configure a custom ErrorDecoder to throw an exception in case of HTTP status 503.
#Component
#Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 400:
log.error("Status code {} on methodKey '{}'", response.status(), methodKey);
case 503:
return new ServiceUnavailableException("HTTP status 503 when calling " methodKey);
default:
return new Exception(response.reason());
}
}
}
This will then throw your custom exception ServiceUnavailableException.
Resilienc4J's CircuitBreaker
By default the circuit-breaker reacts on exceptions. It records them and will open the circuit if there are too much in too less time.
You can configure, which Exceptions to record and which to ignore as expected on the business-level.
Trigger CircuitBreaker on specific exceptions
You can configure CiruitBreaker to record that exception. Joke's answer explains how to do that.
See also
spring feign client exception handling
Feign Client Error Handling
Apps Developer Blog: Feign Error Handling with ErrorDecoder
Resilience4J docs: Feign, Decorating Feign Interfaces

Related

ControllerAdvice is not able to catch exception being thrown from an interceptor middleware

So we have implemented an interceptor middleware in java spring boot. The intention is to use this middleware in any client application (rest api clients) and validate all incoming http requests to prevent any CSRF attack. We are using this middleware inside one of our rest client, say java-demo. Everything works fine except the case when an invalid url i.e. the end point which does not exist, is hit.
For ex: localhost:8080/java-demo/doesnotexist
In this case, when the interceptor logic raises CSRFException, it does not go to the ControllerAdvice where we have defined Exception handler for this particular exception. And in result, we get status 200 in postman, whereas we should get the exception with http status code as 403 which we throw with the response entity.
But when we hit a valid url,
For ex: localhost:8080/java-demo/exists
and if the exception is raised from the interceptor logic, then ControllerAdvice catches the exception and throws to the client, which is expected behaviour.
What can be the possible reason ControllerAdvice is not able to catch the exception when the url is not valid.
For the sake of clarity: When invalid url is hit and interceptor does not throw any exception, we get 404 not found error in postman.
ExceptionHandler class code which is present inside csrf middleware
#ControllerAdvice
public class CSRFExceptionHandler {
#ExceptionHandler({CSRFException.class})
public ResponseEntity<ErrorResponse> handleCSRFException(CSRFException e) {
return new ResponseEntity<>(new ErrorResponse(e.getErrorCode(), e.getMessage()), HttpStatus.FORBIDDEN);
}
#ExceptionHandler({ExpectedArgNotAvailableException.class})
public ResponseEntity<ErrorResponse> handleExpectedArgsNotAvailable(ExpectedArgNotAvailableException e) {
return new ResponseEntity<>(new ErrorResponse(e.getErrorCode(), e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}

How to preserve the error message that is thrown during a 500 internal exception using restTemplate

I want to know what is best practice to preserve error messages when calling several micro services that is chained: I have an angular front end that calls a back end rest service which calls another rest service which calls another 3rd party service.
The 3rd party service is somewhat unreliable. And I want the response from that service to be propagated to my front end.
So to make it easier for the sake of demo’ing the problem.
I have a control class in downstream project (separate micro-service/application)
#RestController
#RequestMapping("/my-down-stream-service")
public class MyController {
#RequestMapping(value = "my-method")
public MyCustomResponse method1() {
//Some complex logic that catch exceptions and propogates a nice little message
throw new RuntimeException(“This is my exception that indicates what the response is to my 3rd party service”);
}
}
On the other micro-service calling the service above I have a restTemplate making the call to the above service
public MyResponse doIt() {
try {
restTemplate.postForEntity(“MyUrl…”, req, MyResponse.class);
} catch (final HttpStatusCodeException ex) {
//If I add a break point and inspect the exception here
}
}
I can see it is a 500 internal exception that gets send to the front end.
If I go and get the ex.getResponseBodyAsString() I get back a JSON map with the actual detail of the exception.
{
"timestamp": "2020-05-06T22:17:08.401+0200",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "This is my exception that indicates what the response is to my 3rd party service",
"path": "…"
}
And I can convert this into a map and get the message portion and construct a new exception and throw that
new ObjectMapper().readValue(ex.getResponseBodyAsString(), HashMap.class).get("message")
But this seems like a lot of work that needs to be implemented where ever I need this.
Is there a better way of doing this?
I also tried creating my own HttpStatus - Like a 550 with my "Own custom message". But you cannot set the message for the HttpStatus code dynamically aka at Runtime. Not even sure if this is the correct venture or path to go down.
My solution in the end based on Amit's suggestion
I finally ended up creating a custom class that extends springs ResponseEntityExceptionHandler. If this is on the class path of your springboot app it will intercept the exception before returning it from the controller. I also created my own exception. Reason being this way if I want my functionality to trigger I fire my own exception and everyone else can still follow the normal way. It can be changed at any time.
Also on the client side I had to cast the exception's getBody() JSON to my exception. But I didn't knew if it was my exception to start of with. So I also added some HTTP header. And on the client side I check if that header is present then I know the body is my exception and I could comfortable convert the JSON to my exception.
#ControllerAdvice
public class MyRestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {MyCustomException.class})
protected ResponseEntity<Object> handleConflict(final MyCustomException ex, final HttpServletResponse response) {
if (!response.containsHeader("MYTAG")) {
response.addHeader("EX_TYPE", "MYTAG");
}
//here you can go wild as to what type of or just the normal 500
//return ResponseEntity.status(ex.getHttpStatus()).body(ex); // 500 internal exception
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex);
}
}
If I were you, I would like to create a controller advice to handle all kind of exceptions. Then I would like to create a ErrorMessage class which will have custom errorCode, errorMessage fields as per requirements. From this controller advice, for any kind of exceptions occurred in application, it will create an instance of ErrorMessage with details like errorCode and errorMessage and wrap into ResponseEntity object (with HTTP status) and return to the other microservices.
At consumer end check the response status and act accordingly.
I think the answer you are looking for is creating an implementation of ExceptionMapper. The interface is designed to handle java exceptions that map to Response.
In your case, if the 3rd part throws an exception which is handled by the ExceptionMapper implementation you can access the error message and return that in the response.
public class ServiceExceptionMapper implements ExceptionMapper<ServiceException>
{
/**
* {#inheritDoc}
*/
#Override
public Response toResponse(ServiceException exception)
{
//grab the message from the exception and return it in the response
}

HTTP Response Exception Handling in Spring 5 Reactive

I'm developing some reactive microservices using Spring Boot 2 and Spring 5 with WebFlux reactive starter.
I'm facing the following problem: I want to handle all HTTP Statuses that I receive from calling another REST Services and throws an exception when I receive some bad HTTP Status. For example, when I call an endpoint and I receive an 404 HTTP Status, I want to throw an exception and that exception to be handled in some ExceptionHandler class, just like the way it was in Spring 4 with #ControllerAdvice.
What is the right way to do this? Hope to receive some good suggestions.
This can be addressed in two independent parts.
How to convert HTTP 404 responses received by WebClient into custom exceptions
When using WebClient, you can receive HTTP 404 responses from remote services. By default, all 4xx and 5xx client responses will be turned into WebClientResponseException. So you can directly handle those exceptions in your WebFlux app.
If you'd like to turn only 404 responses into custom exceptions, you can do the following:
WebClient webClient = //...
webClient.get().uri("/persons/1")
.retrieve()
.onStatus(httpStatus -> HttpStatus.NOT_FOUND.equals(httpStatus),
clientResponse -> Mono.error(new MyCustomException()))
.bodyToMono(...);
This is obviously done on a per client call basis.
You can achieve the same in a more reusable way with an ExchangeFilterFunction that you can set once and for all on a WebClient instance like this:
WebClient.builder().filter(myExchangeFilterFunction)...
How to handle custom exceptions in WebFlux apps
With Spring WebFlux with annotations, you can handle exceptions with methods annotated with #ExceptionHandler (see Spring Framework reference documentation).
Note: using a WebExceptionHandler is possible, but it's quite low level as you'll have no high-level support there: you'll need to manually write the response with buffers without any support for serialization.
I think what you are looking for is WebFluxResponseStatusExceptionHandler the check this for reference.
In the WebHandler API, a WebExceptionHandler can be used to to handle
exceptions from the chain of WebFilter's and the target WebHandler.
When using the WebFlux Config, registering a WebExceptionHandler is as
simple as declaring it as a Spring bean, and optionally expressing
precedence via #Order on the bean declaration or by implementing
Ordered.
This example may help, have not tried it myself.
#Component
#Order(-2)
class RestWebExceptionHandler implements WebExceptionHandler{
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof PostNotFoundException) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
// marks the response as complete and forbids writing to it
return exchange.getResponse().setComplete();
}
return Mono.error(ex);
}
}
class PostNotFoundException extends RuntimeException {
PostNotFoundException(String id) {
super("Post:" + id + " is not found.");
}
}

RxJava custom exception handling/propagation in spring boot rest application

I am wondering what is the best with RxJava and spring REST API?
I have a simple REST service and in the repository, in case of an error, I'd like to propagate a specific custom error to the client. But I am not sure how to map different custom exceptions with RxJava.
Here is a call to the backend:
private Single<Customer> findCustomerById(long customerId) {
return Single.fromCallable(() -> getRestTemplate().getForObject(
MyBackendService.SEARCH_CUSTOMER_BY_ID.getUrl(),
Customer.class, customerId))
.onErrorResumeNext(ex -> Single.error(new BackendException(ex)));
}
My exception:
public class BackendException extends Exception {
public BackendException(String message) {
super(message);
}
public BackendException(Throwable cause) {
super(cause);
}
So the question is how to map/propagate with RxJava this BackendException to let's say NotFound (404) or InternalServerError (500)?
I've used exception libraries that have exceptions for each type of HTTP response that you care about and a canonical error class that can be put into the body of an HTTP Response and easily parsed by a REST client, i.e, something with a code and a message.
As for translating exceptions to different HTTP responses, that depends on the version of Spring and REST library you're using. There are various ways to do that documented here, here and here.
The fact that you're using RxJava doesn't matter so much in determining your approach. I have used similar onErrorResumeNext code to what you have in your example.
Use RxJava subscription mechanism to handle errors, use onErrorResumeNext to return values and not exceptions. I would do something like this:
//The web service call is just this
private Single<Customer> findCustomerById(long customerId) {
return Single.fromCallable(() ->
getRestTemplate().getForObject(MyBackendService.SEARCH_CUSTOMER_BY_ID.getUrl(),
Customer.class,
customerId));
}
...
//Then just manage the exception on the subscription
findCustomerById(long customerId)
.subscribe(customer -> {
//Write the success logic here
System.out.println(customer);
}, throwable -> {
//Manage the error, for example
throwable.printStackTrace();
});
Hope it helps...

Spring Boot how to ignore HttpStatus Exceptions

I'm building an Application using Spring Boot. This application is distributed, which means I have multiple API's that call each others.
One of my underlying services interacts with a database and responds with the requested data. If a request to an unexisting ID is made, I response with a 404 HttpStatus:
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
(Same with 400 error on certain operations, or 204 for deleting an entry etc).
The problem is that I have some other Spring Boot applications that call these API's, throw an org.springframework.web.client.HttpClientErrorException: 404 Not Found Exception when they request, in this example, an unexisting entry. But the 404 status code is intended and should not return this exception (causing my Hystrix circuit breaker to call its fallback function).
How can I solve this problem?
The call to the service is implemented like this in my code: ResponseEntity<Object> data = restTemplate.getForEntity(url, Object.class);
My RestTemplate is set up like this:
private RestTemplate restTemplate = new RestTemplate();
Spring's RestTemplate uses a ResponseErrorHandler to handle errors in responses. This interface provides both a way to determine if the response has an error (ResponseErrorHandler#hasError(ClientHttpResponse)) and how to handle it (ResponseErrorHandler#handleError(ClientHttpResponse)).
You can set the RestTemplate's ResponseErrorHandler with RestTemplate#setErrorHandler(ResponseErrorHandler) whose javadoc states
By default, RestTemplate uses a DefaultResponseErrorHandler.
This default implementation
[...] checks for the status code on the
ClientHttpResponse: any code with series
HttpStatus.Series.CLIENT_ERROR or HttpStatus.Series.SERVER_ERROR is
considered to be an error. This behavior can be changed by overriding
the hasError(HttpStatus) method.
In case of an error, it throws the exception you are seeing.
If you want to change this behavior, you can provide your own ResponseErrorHandler implementation (maybe by overriding DefaultResponseErrorHandler) which doesn't consider 4xx as an error or that doesn't throw an exception.
For example
restTemplate.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false; // or whatever you consider an error
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// do nothing, or something
}
});
You can then check the status code of the ResponseEntity returned by getForEntity and handle it yourself.

Categories