we are using reactive feign client (com.playtika.reactivefeign:feign-reactor-spring-cloud-starter:3.2.0)
circuit breaker version : org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j:2.1.0
and spring boot application version org.springframework.boot’ version ’2.6.6
when we get an error from reactive feign client (such as 404 error)
#ReactiveFeignClient(name = "someRestClient", url = "${react-gpi-service.url}",configuration = AuthConfigurationsomeRestClient.class, fallbackFactory = someRestClienttFallbackFactory.class)
#Profile("!test")
public interface someRestClient {
#PostMapping(value = "/v2/{entity}/any", produces = MediaType.ALL_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
Mono<String> any(#PathVariable(value = "entity")
it goes over the error decoder to check if it should be retried
#Slf4j
#RequiredArgsConstructor
public class RetryableErrorDecoder implements ErrorDecoder {
private static ErrorDecoder defaultErrorDecoder = new Default();
private final String clientName;
public Exception decode(String methodKey, Response response) {
String body = "";
try {
body = IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("failed to parse error response body", e);
}
log.error("In RetryableErrorDecoder, got an error from {}. status: {}, body: {}, reason: {}, request: {}",
clientName, response.status(), body, response.reason(), response.request());
if (response.status() == HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE ||
response.status() == HttpStatusCodes.STATUS_CODE_BAD_GATEWAY) {
log.warn("Retry on error 503 or 502");
return createRetryableException(response, "Service Unavailable 503, 502");
} else {
Exception decode = defaultErrorDecoder.decode(methodKey, response);
if (decode instanceof FeignException &&
decode.getMessage().contains("authorizing")) {
log.warn("Retry on {}", decode.getMessage());
return createRetryableException(response, "Service authorizing problem");
}
return decode;
}
}
private Exception createRetryableException(Response response, String message) {
return new RetryableException(
response.status(),
message,
response.request().httpMethod(),
null,
null,
response.request());
}
}
after that it goes to Circuit beaker predicate
public class someFailurePredicate implements Predicate<Throwable> {
#Override
public boolean test(Throwable throwable) {
return throwable instanceof ThirdPartyException
|| throwable instanceof ReadTimeoutException
|| throwable instanceof OutOfRetriesException;
}
}
and then it goes to fallBackFactory mechanism because the circuit breaker requires the fallback method so the circuit breaker predicate is activated again.
#Component
public class someRestClientFallbackFactory implements FallbackFactory<someRestClient> {
#Override
public someRestClient apply(Throwable throwable) {
return new someRestClientFallback(throwable);
}
}
public class someRestClientFallback implements someRestClient {
private final Throwable cause;
public someClientFallback(Throwable cause) {
this.cause = cause;
}
public Mono<String> performSearchRequest(String entity,
) {
return Mono.error(cause);
}
}
because we have 2 mechanisms of error handling the circuit predicate is calling twice and duplicating the error.
I tried to move the retry mechanism(error decoder) to fallback method but the fallbackfactory method accepts throwable and reactiveFeignClientException doesn't have a status code and it's hard to determine if we should do the retry.
if I remove the fallback method I get this error message :
org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException: No fallback available.
we need to add it but then we have two mechanisms and a duplicate circuit breaker predicate count
Reactive Feign Client enables its own CB by default, it is possible to disable it by setting reactive.feign.circuit.breaker.enabled to false - https://github.com/PlaytikaOSS/feign-reactive/blob/develop/feign-reactor-spring-configuration/README.md
Related
I've two microservices, let us say a FrontEnd and BackEnd, for FrontEnd I'm using WebFlux and calling backend service using feign client as shown in below code excample, though the below code example works, but I wanted to have a generic exception handler using Function and feed onto onErrorMap
#RestController
#Slf4j
public class MyFrentEndService {
#Autowired
private MyBackEndService client;
#PostMapping(value="/hello", consumes="application/json")
public Mono<Void> sayHello(#Valid String msg) {
log.info("Message is {}", msg);
return Mono.create(sink-> {
try {
client.hello(msg);
}catch (FeignException e) {
System.out.println(e.status());
HttpStatus status = e.status() ==0 ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.valueOf(e.status());
String message = e.getMessage();
sink.error(new ResponseStatusException(status, message));
}
sink.success();
});
}
}
Tried to use onErrorMap, but getting compilation error stating, use Mono instead of Mono<Void>
#RestController
#Slf4j
public class MyFrentEndService {
#Autowired
private MyBackEndService client;
#PostMapping(value="/hello", consumes="application/json")
public Mono<Void> sayHello(#Valid String msg) {
log.info("Message is {}", msg);
return Mono.fromSupplier(() -> {
client.hello(msg);
return null;
}).onErrorMap(e->{
HttpStatus status = e.status() ==0 } HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.valueOf(e.status());
String message = e.getMessage();
return new ResponseStatusException(status, message);
});
}
}
How to use onErrorMap?
This error is unrelated to the operator onErrorMap. This code dont compile because the compiler can not infer the generic type returned by the method Mono.fromSupplier to be Void - you are returning null on the supplied function.
This should be corrected by doing the following:
#PostMapping(value="/hello", consumes="application/json")
public Mono<Void> sayHello(#Valid String msg) {
log.info("Message is {}", msg);
return Mono.<Void>fromSupplier(() -> {
client.hello(msg);
return null;
}).onErrorMap(e->{
HttpStatus status = e.status() ==0 ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.valueOf(e.status());
String message = e.getMessage();
return new ResponseStatusException(status, message);
});
}
I think that it is more idiomatic to do the following:
#PostMapping(value="/hello", consumes="application/json")
public Mono<Void> sayHello(#Valid String msg) {
log.info("Message is {}", msg);
return Mono.fromRunnable(() -> {
client.hello(msg);
})
.then()
.onErrorMap(e->{
HttpStatus status = e.status() ==0 ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.valueOf(e.status());
String message = e.getMessage();
return new ResponseStatusException(status, message);
});
}
Finally, I would advise against using blocking calls inside the reactive pipeline unless you really have to. (prefer WebClient or other nonblocking HTTP client over blocking clients as feign).
We are using Zuul, Eureka and spring boot application services for REST APIs.
Suppose my spring boot service is down and when I tried to access the API using Zuul API gateway, I am getting ZuulException and response is below :
{
"timestamp": "2018-10-12T14:29:09.632+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "com.netflix.zuul.exception.ZuulException",
"message": "GENERAL"
}
I want to customize the response format like below:
{
"success": false,
"message": "Service is down. Please try later"
}
I tried to implement https://stackoverflow.com/a/39841785/5506061 but its not working for me.
Please suggest how to customize the response for ZuulException.
You can implement your own FallbackProvider and customize the response based on the cause if needed.
Something like :
#Component
public class CustomFallbackBasedOnCause implements FallbackProvider {
private static final String DEFAULT_MSG = "{\"success\": false,\"message\": \"Service is down. Please try later\"}";
#Override
public String getRoute() {
return "*"; // * = all routes
}
#Override
public ClientHttpResponse fallbackResponse(final Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return fallbackResponse();
}
}
#Override
public ClientHttpResponse fallbackResponse() {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
#Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
#Override
public int getRawStatusCode() throws IOException {
return status.value();
}
#Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
#Override
public void close() {
}
#Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(DEFAULT_MSG.getBytes());
}
#Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
As you can see in the getRoute() method, you can specify if this customFallback will be used for all routes (return "*") or for a specific route.
In case you work with Registry service (e.g Eureka). You don’t specify the route URL but the service id instead. return "SERVICEID"
validate Rest URL in spring boot.
Requirement: If I hit the wrong URL then it should throw a custom exception.
ex. Correct URL is "/fulfillment/600747l/send_to_hub" If I hit "/api/600747l/send_to_hub_1" then it should return exception like
"404:- URL not Found.".
Right now it returning "500 : -
{
"timestamp": 1531995246549,
"status": 500,
"error": "Internal Server Error",
"message": "Invalid Request URL.",
"path": "/api/600747l/send_to_hub_1"
}"
you need to write NewClass with annotation #ControllerAdvice which will redirect all exceptions to this NewClass.
example
Your Custom Exception Class:
#Data
#AllArgsConstructor
#EqualsAndHashCode(callSuper = false)
public class IOApiException extends IOException {
private ErrorReason errorReason;
public IOApiException(String message, ErrorReason errorReason) {
super(message);
this.errorReason = errorReason;
}
}
Now the CustomExceptionHandler Class -
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = IOApiException.class)
public GlobalErrorResponse handleException(IOApiException e) {
logger.error("UNAUTHORIZED: ", e);
return new GlobalErrorResponse("URL Not Found", HttpStatus.UNAUTHORIZED.value(), e.getErrorReason());
}
//this to handle customErrorResponseClasses
public GlobalErrorResponse getErrorResponseFromGenericException(Exception ex) {
if (ex == null) {
return handleException(new Exception("INTERNAL_SERVER_ERROR"));
}
else if (ex instanceof IOApiException) {
return handleException((IOApiException) ex);
}
}
Now Your error response class:
public class GlobalErrorResponse {
private String message;
#JsonIgnore
private int statusCode;
private ErrorReason reason;
}
ErrorReason Class
public enum ErrorReason {
INTERNAL_SERVER_ERROR,
INVALID_REQUEST_PARAMETER,
INVALID_URL
}
add and register one filter who calls the GlobalExceptionHandler in exception case like this
public class ExceptionHandlerFilter implements Filter {
private final GlobalExceptionHandler globalExceptionHandler;
public ExceptionHandlerFilter(GlobalExceptionHandler globalExceptionHandler) {
this.globalExceptionHandler = globalExceptionHandler;
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception exception) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
GlobalErrorResponse errorResponse = globalExceptionHandler.getErrorResponseFromGenericException(exception);
httpResponse.setStatus(errorResponse.getStatusCode());
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
#Override
public void destroy() {
}
}
Like this you can add as many exceptions you want.. and can handle it manually.
As per your question first of all you need to define a base url(e.g.-/api) so that any url must be handled through your controller.Now after base url as shown /api/600747l/send_to_hub_1 #PathVariable int id. This circumstance is important, because Spring documentation said that if method argument annotated with #PathVariable can’t be casted to specified type (in our case to int), it will be exposed as String. Hence it can cause a TypeMismatchException.
To handle this I will use #ExceptionHandler annotation on #Controller level. Such approach suits for this situation as no one else. I just need to make 2 changes in the Controller:
1.Add MessageSource field
2.Add exception handler method
#Autowired
private MessageSource messageSource;
...
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorInfo handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.smartphone.id", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
return new ErrorInfo(errorURL, errorMessage);
}
...
I have an endpoint who throw an Exception using spring annotation, here is the code of my Exception :
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class MyException extends BaseApiException{
public MyException (String variable){
super("variable :"+variable+" can not be updated.");
}
}
When i use postman to test the Rest endpoint i get a correct result with a correct status code :
{
"errorType": "MyExption",
"message": "variable : XYZ can not be updated."
}
My problem is when i try to call the service using restTemplate I did not receive a body in the response, here is my code :
ResponseEntity<Document> response;
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(builder.build().encode().toUri(), HttpMethod.POST, entity, Document.class);
you need to define an error handler to extract this
public class MyResponseErrorHandler implements ResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(MyResponseErrorHandler.class);
#Override
public void handleError(ClientHttpResponse response) throws IOException {
//here you should be able to get //response.getBody()
log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText());
}
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return isError(response.getStatusCode());
}
public static boolean isError(HttpStatus status) {
HttpStatus.Series series = status.series();
return (HttpStatus.Series.CLIENT_ERROR.equals(series)
|| HttpStatus.Series.SERVER_ERROR.equals(series));
}
}
RestTemplate template = new RestTemplate();
template.setErrorHandler(new MyResponseErrorHandler());
I have a rest service which send an 404 error when the resources is not found.
Here the source of my controller and the exception which send Http 404.
#Controller
#RequestMapping("/site")
public class SiteController
{
#Autowired
private IStoreManager storeManager;
#RequestMapping(value = "/stores/{pkStore}", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public StoreDto getStoreByPk(#PathVariable long pkStore) {
Store s = storeManager.getStore(pkStore);
if (null == s) {
throw new ResourceNotFoundException("no store with pkStore : " + pkStore);
}
return StoreDto.entityToDto(s);
}
}
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException
{
private static final long serialVersionUID = -6252766749487342137L;
public ResourceNotFoundException(String message) {
super(message);
}
}
When i try to call it with RestTemplate with this code :
ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
System.out.println(r.getStatusCode());
System.out.println(r.getBody());
I receive this exception :
org.springframework.web.client.RestTemplate handleResponseError
ATTENTION: GET request for "http://........./stores/99" resulted in 404 (Introuvable); invoking error handler
org.springframework.web.client.HttpClientErrorException: 404 Introuvable
I was thinking I can explore my responseEntity Object and do some things with the statusCode. But exception is launch and my app go down.
Is there a specific configuration for restTemplate to not send exception but populate my ResponseEntity.
As far as I'm aware, you can't get an actual ResponseEntity, but the status code and body (if any) can be obtained from the exception:
try {
ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
}
catch (final HttpClientErrorException e) {
System.out.println(e.getStatusCode());
System.out.println(e.getResponseBodyAsString());
}
RESTTemplate is quite deficient in this area IMO. There's a good blog post here about how you could possibly extract the response body when you've received an error:
http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate
As of today there is an outstanding JIRA request that the template provides the possibility to extract the response body:
https://jira.spring.io/browse/SPR-10961
The trouble with Squatting Bear's answer is that you would have to interrogate the status code inside the catch block eg if you're only wanting to deal with 404's
Here's how I got around this on my last project. There may be better ways, and my solution doesn't extract the ResponseBody at all.
public class ClientErrorHandler implements ResponseErrorHandler
{
#Override
public void handleError(ClientHttpResponse response) throws IOException
{
if (response.getStatusCode() == HttpStatus.NOT_FOUND)
{
throw new ResourceNotFoundException();
}
// handle other possibilities, then use the catch all...
throw new UnexpectedHttpException(response.getStatusCode());
}
#Override
public boolean hasError(ClientHttpResponse response) throws IOException
{
return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
|| response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
}
The ResourceNotFoundException and UnexpectedHttpException are my own unchecked exceptions.
The when creating the rest template:
RestTemplate template = new RestTemplate();
template.setErrorHandler(new ClientErrorHandler());
Now we get the slightly neater construct when making a request:
try
{
HttpEntity response = template.exchange("http://localhost:8080/mywebapp/customer/100029",
HttpMethod.GET, requestEntity, String.class);
System.out.println(response.getBody());
}
catch (ResourceNotFoundException e)
{
System.out.println("Customer not found");
}
Since it's 2018 and I hope that when people say "Spring" they actually mean "Spring Boot" at least, I wanted to expand the given answers with a less dust-covered approach.
Everything mentioned in the previous answers is correct - you need to use a custom ResponseErrorHandler.
Now, in Spring Boot world the way to configure it is a bit simpler than before.
There is a convenient class called RestTemplateBuilder. If you read the very first line of its java doc it says:
Builder that can be used to configure and create a RestTemplate.
Provides convenience methods to register converters, error handlers
and UriTemplateHandlers.
It actually has a method just for that:
new RestTemplateBuilder().errorHandler(new DefaultResponseErrorHandler()).build();
On top of that, Spring guys realized the drawbacks of a conventional RestTemplate long time ago, and how it can be especially painful in tests. They created a convenient class, TestRestTemplate, which serves as a wrapper around RestTemplate and set its errorHandler to an empty implementation:
private static class NoOpResponseErrorHandler extends
DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}
You can create your own RestTemplate wrapper which does not throw exceptions, but returns a response with the received status code. (You could also return the body, but that would stop being type-safe, so in the code below the body remains simply null.)
/**
* A Rest Template that doesn't throw exceptions if a method returns something other than 2xx
*/
public class GracefulRestTemplate extends RestTemplate {
private final RestTemplate restTemplate;
public GracefulRestTemplate(RestTemplate restTemplate) {
super(restTemplate.getMessageConverters());
this.restTemplate = restTemplate;
}
#Override
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
return withExceptionHandling(() -> restTemplate.getForEntity(url, responseType));
}
#Override
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
return withExceptionHandling(() -> restTemplate.postForEntity(url, request, responseType));
}
private <T> ResponseEntity<T> withExceptionHandling(Supplier<ResponseEntity<T>> action) {
try {
return action.get();
} catch (HttpClientErrorException ex) {
return new ResponseEntity<>(ex.getStatusCode());
}
}
}
Recently had a usecase for this. My solution:
public class MyErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
return hasError(clientHttpResponse.getStatusCode());
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
HttpStatus statusCode = clientHttpResponse.getStatusCode();
MediaType contentType = clientHttpResponse
.getHeaders()
.getContentType();
Charset charset = contentType != null ? contentType.getCharset() : null;
byte[] body = FileCopyUtils.copyToByteArray(clientHttpResponse.getBody());
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
default:
throw new RestClientException("Unknown status code [" + statusCode + "]");
}
}
private boolean hasError(HttpStatus statusCode) {
return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}
There is no such class implementing ResponseErrorHandler in Spring framework, so I just declared a bean:
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplateBuilder()
.errorHandler(new DefaultResponseErrorHandler() {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
//do nothing
}
})
.build();
}
The best way to make a RestTemplate to work with 4XX/5XX errors without throwing exceptions I found is to create your own service, which uses RestTemplate :
public ResponseEntity<?> makeCall(CallData callData) {
logger.debug("[makeCall][url] " + callData.getUrl());
logger.debug("[makeCall][httpMethod] " + callData.getHttpMethod());
logger.debug("[makeCall][httpEntity] " + callData.getHttpEntity());
logger.debug("[makeCall][class] " + callData.getClazz());
logger.debug("[makeCall][params] " + callData.getQueryParams());
ResponseEntity<?> result;
try {
result = restTemplate.exchange(callData.getUrl(), callData.getHttpMethod(), callData.getHttpEntity(),
callData.getClazz(), callData.getQueryParams());
} catch (RestClientResponseException e) {
result = new ResponseEntity<String>(e.getResponseBodyAsString(), e.getResponseHeaders(), e.getRawStatusCode());
}
return result;
}
And in case of exception, simply catch it and create your own ResponseEntity.
This will allow you to work with the ResponseEntity object as excepted.