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.");
}
}
Related
I have an existing spring-mvc application with different #RestController. Now I want to add a Mono<String> endpoint, and log the request timestamp with url path, as well as the response timestamp.
But how? I cannot simply #EnableWebFlux as I would have to disable spring-mvc therefor.
How could I register a filter explicit for the webflux endpoint that catches on invocation, and right before the response is written back async?
#RestController
public class FluxService {
#PostMapping
public Mono<String> post() {
return webClient.post().uri(uri).bodyValue(payload).retrieve().bodyToMono(String.class);
}
}
I want enhanced logging on the #PostMapping endpoint, not on the webClient!
MVC is a blocking/servlet based api, mixing it with webflux isn't going to work. They use different filters, security etc as MVC assumes a lot of ThreadLocal stuff. (Can I use SpringMvc and webflux together?)
If you really want to mix them like this you could use .doOnNext() once you get your mono and call your logger there.
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
I am confused about how an infinite loop of feign calls might behave.
An example:
Assume I have 2 APIs, A & B.
if I call API A, which in turn calls API B via a feign HTTP call, which in turn calls API A again via feign, will it recognize this and break the call chain?
Quick flowchart of calls:
A -> B -> A -> B ... Repeat infinitely?
I have not tried this code, it is just an idea。
But I am assuming that spring-cloud-starter-feign will provide some methods to resolve this problem? Is this assumption correct?
#PostMapping(RestJsonPath.API_A)
ResponseEntity<byte[]> apiA();
#PostMapping(RestJsonPath.API_B)
ResponseEntity<byte[]> apiB();
Will it execute until it times out or hystrix will stop it?
TL;DR:
Feign will keep the connection open on the initial request from A to B until the pre-configured timeout kicks in. At this point, Feign will time out the request and if you have specified a Hystrix fallback, Spring will use your Hystrix fallback as the response.
Explanation:
spring-boot-starter-feign provides an abstraction layer for writing the HTTP request code. It will not handle potential loops or cycles in your code.
Here is an example spring boot feign client from their tutorials website for demonstration:
#FeignClient(value = "jplaceholder",
url = "https://jsonplaceholder.typicode.com/",
configuration = ClientConfiguration.class,
fallback = JSONPlaceHolderFallback.class)
public interface JSONPlaceHolderClient {
#RequestMapping(method = RequestMethod.GET, value = "/posts")
List<Post> getPosts();
#RequestMapping(method = RequestMethod.GET, value = "/posts/{postId}", produces = "application/json")
Post getPostById(#PathVariable("postId") Long postId);
}
Notice first that this is an interface - all the code is auto generated by Spring at startup time, and that code will make RESTful requests to the urls configured via the annotations. For instance, the 2nd request allows us to pass in a path variable, which Spring will ensure makes it on the URL path of the outbound request.
The important thing to stress here is that this interface is only responsible for the HTTP calls, not any potential loops. Logic using this interface (which I can inject to any other Spring Bean as I would any other Spring Bean), is up to you the developer.
Github repo where this example came from.
Spring Boot Docs on spring-boot-starter-openfeign.
Hope this helps you understand the purpose of the openfeign project, and helps you understand that it's up to you to deal with cycles and infinite loops in your application code.
As for Hystrix, that framework comes in to play (if it is enabled) only if one of these generated HTTP requests fails, whether it's a timeout, 4xx error, 5xx error, or a response deserialization error. You configure Hystrix, as a sensible default or fallback for when the HTTP request fails.
This is a decent tutorial on Hystrix.
Some points to call out is that a Hystrix fallback must implement your Feign client interface, and you must specify this class as your Hysterix fallback in the #FeignClient annotation. Spring and Hystrix will call your Hystrix class automatically if a Feign request fails.
I have a scenario :
UI<--->Spring boot micro-service REST API<--->server
Now, there is a situation in which I want to handle custom exceptions(which I am aware how to do) in order to return specific Http Status and message back to UI when server responds in certain manner for e.g. 500 should return "Please return after a while" insteal of "internal server error". The maven project for our micro-service is divided into 3 layers (sub maven projects) i.e. Business, Web and Domain. Where web contains controller class, Business contains Service class and Domain contains #Entity, #Components etc.
I want to know in order to handle the above mentioned exception let say HTTP Status 500, should be done at business layer? or at web layer i.e. controller level. What would be the best possible solution? (I am aware of ResponseEntity and how it can provide a customized response to UI).
I personally believe that if I include custom exception class at Business Level and return that in Controller class using response entity after checking the response status would do the trick. But officials feel that it should be done at service level? I am not able to understand why(it makes the process more complex)? can anyone suggest which solution is best?
Officials are right, it should be in Service Layer. I would say best practice would be to use #ExceptionHandler. As the downside to handling the exception in the controller method is that it makes the code less readable and might be repeated across many controller methods.
I would recommend having a base class for your controllers with the #ExceptionHandler defined. This way it can be used for many different controllers, without any code duplication. This would be more readable than the exception resolver approach, but could be used in conjunction
This is clearly explained here
If you want to handle error in a global level, you can use #ControllerAdvice which is very easy when comes to the handling custom exceptions as well as runtime exceptions.
you can throw exceptions from business layer to web controllers and define a #ControllerAdvice class to catch those errors and provide responses with correct response status.
For Ex:-
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
and throw those Exception classes from the controller classes also, you dont need to catch exception from the controllers.
Hope this helps to you...
Above code snipplet i took from here
Error response is usually generated by #ExceptionHandler matching your exception type and maybe registered with #ConrtrollerAdvice as described here.
API should be standardised (e.g. http://jsonapi.org/) and designed primarily for developers. Returning "Please return after a while" instead of "Internal Server Error" makes little sense to me. It's a 500 HTTP status response of uncertain reason e.g. NullPointerException somewhere deep in the code.
What I want to do is process AMQP messages in a very similar way the Http Requests are processed using spring-webmvc annotations such as #RequestMapping, #RequestParam etc. But, instead of the Http Request my source object will be an AMQP message. The AMQP message request will have two headers, for example -
method="POST"
url="/api/myobjects/{someParam}"
and the payload will contain data in json format.
If you have noticed, this is nothing but HTTP REST api mapped to AMQP message.
I want to be able to write a controller like handler, for example -
#Controller
public class MyObjectHandler {
#RequestMapping(value="/api/myobjects/{someParam}", method="POST")
public MyObject createMyObject(#Payload MyObject myObj, #PathParam String someParam) {
//... some processing
return myObj;
}
// ...more handlers
}
I have looked at spring-amqp/rabbitmq annotations and also spring integration annotations. They are close to what I want, but would not allow routing to handler methods based on header parameters, especially the REST url.
I don't expect that a readymade solution would be available for this. Just want to make sure I choose the best possible option. Some of the options I think are (in order of precedence)
If the spring-webmvc annotation processing mechanism is extensible, just extend it to use AMQP message as source instead of Http Request
Modify the spring-webmvc annotation processing mechanism to take the AMQP message as input instead of Http Request
Write your own solution with custom annotaions and their processors, which I think is a very involving task
Or any other possible approach than above?
Any guidance/direction is appreciated.
I think the starting point is likely AbstractMethodMessageHandler in spring-messaging.
There's currently a SimpAnnotationMethodMessageHandler implementation for websockets which invokes #Controllers.
You could use a #RabbisListener method that has a Message<?> parameter (Spring AMQP will convert the underlying Rabbit message to a spring-messaging message, including the headers). Then, invoke the message handler to route to the appropriate controller method.
If you come up with a robust implementation, please consider contributing it.