How to retry RestAPI connection if it fails for first time in restTemplate? - spring-boot

I am calling a third party rest API, some times it sends response with status code 500, 504.
I want to make a another hit to the API if it gives above status code.
My current logic of retry is:
public <T> ResponseEntity<T> sendGetRequest(String url,
Class<T> responseClazz,
HttpHeaders headers) {
ResponseEntity<T> response = null;
int count = 0;
int maxTries = 2;
while(true) {
try {
HttpEntity request = new HttpEntity(headers);
response = restTemplate.exchange(url, HttpMethod.GET, request, responseClazz);
if(response.getStatusCode() != HttpStatus.OK) {
log.error("null or Error response from server for ", url);
}
log.info("Response received {}", response.toString());
return response;
}catch (ResourceAccessException rae){
log.warn("retry count {} {}", count, rae);
if (++count == maxTries) throw new ServerErrorException("API timeout");
}
}
}
I have also used apache http where I use CloseableHttpClient to retry for status code 500 and 504.
I have also looks to the solution of spring-retry. Is there any other method to do this?

When calling HTTP request with RestTemplate, there are 2 main cases to retry:
Specific response HTTP statuses. For example:
503 Service Unavailable status can be retried.
404 Not Found can be proceeded without a retry attempt.
ResourceAccessException which can represent some IO exception received without getting the HTTP server response, like SocketException: Broken pipe.
For solution based on RestTemplate/HttpClient, while it exposes options to retry based on the HTTP response, combining it with IOException handling can be tricky.
Solution based on Spring RetryTemplate
#Bean(name = "restTemplateRetryTemplate")
public RetryTemplate restTemplateRetryTemplate() {
return createRestTemplateRetryTemplate();
}
private RetryTemplate createRestTemplateRetryTemplate(boolean retryOnServerErrors) {
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(ResourceAccessException.class, true);
retryableExceptions.put(HttpServerErrorException.ServiceUnavailable.class, true);
retryableExceptions.put(HttpServerErrorException.BadGateway.class, true);
retryableExceptions.put(HttpServerErrorException.GatewayTimeout.class, true);
retryableExceptions.put(HttpClientErrorException.TooManyRequests.class, true);
return createRetryTemplate(retryableExceptions);
}
private RetryTemplate createRetryTemplate(Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
exponentialRandomBackOffPolicy.setInitialInterval(INITIAL_INTERVAL);
exponentialRandomBackOffPolicy.setMaxInterval(MAX_INTERVAL);
exponentialRandomBackOffPolicy.setMultiplier(MULTIPLIER);
retryTemplate.setBackOffPolicy(exponentialRandomBackOffPolicy);
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(MAX_ATTEMPTS, retryableExceptions));
// Optional, for additional logging on failures.
retryTemplate.registerListener(retryTemplateLogListener);
return retryTemplate;
}
Usage example
#Autowired
#Qualifier("restTemplateRetryTemplate")
private RetryTemplate retryTemplate;
...
String result = retryTemplate.execute(arg -> {
return longRestTemplate.getForObject(url, String.class);
});

Related

how to Throw an exception from an #Async annotated method

I have two modules, one calls the other from a rest template.
( admin calls notifServer)
the notifServer has a method annotated with #Async . I want to throw an exception in that method, but the admin gets the response too quickly and the exception method cannot be caught at admin.
I an new to spring and the #Async process. I've tried mapping the response body from the NotifServer to a CCompletableFuture.class .
But Still I get no error response.
This code is from admin
ResponseEntity response = fcmRestTemplate.exchange(nsUrl + "/fcm/admin/" + bulkFcmId, HttpMethod.POST,
HttpEntityUtils.getHttpEntity(moduleCode), CompletableFuture.class);
if (response.getStatusCode() != HttpStatus.CREATED && response.getStatusCode() != HttpStatus.ACCEPTED) {
String errorMessage = ErrorResourceUtil.getErrorMessage((HashMap) response.getBody(),"Unable to send fcm");
setStatusToFailedByBulkFcmId(bulkFcmId);
throw new ClientException(errorMessage);
}
now this is from NotifServer
JobExecution jobExecution = jobLauncher
.run(importJob, new JobParametersBuilder()
.addString("fullPathFileName", TMP_DIR)
.addString("batch_fcm_id", String.valueOf(id))
.addLong("time",System.currentTimeMillis())
.toJobParameters());
if(jobExecution.getStepExecutions().stream().map(StepExecution::getStatus).findFirst().get().equals(BatchStatus.ABANDONED)){
throw new ClientException("INVALID CSV");
This is annotated with #Async.
So is there a way for me to catch the client exception in the response body in the Admin?
EDIT
This is the API from notifServer
#ResponseStatus(HttpStatus.CREATED)
#PostMapping(value = "/admin/{bulkFcmId}")
public void pushFCMByAdmin(#PathVariable Long bulkFcmId) {
fcmService.sendFcmByAdmin(bulkFcmId, AuthUtil.getCurrentUser());
}
Then the sendFcmByAdmin has #Async annotation.
In below code can you provide the return type to be a business object rather than CompletableFuture.class.Since you are passing CompletableFuture.class as a parameter to the exchange it expects a response return value of the type CompletableFuture.class.
ResponseEntity response = fcmRestTemplate.exchange(nsUrl + "/fcm/admin/" + bulkFcmId, HttpMethod.POST,
HttpEntityUtils.getHttpEntity(moduleCode), CompletableFuture.class);
if (response.getStatusCode() != HttpStatus.CREATED && response.getStatusCode() != HttpStatus.ACCEPTED) {
String errorMessage = ErrorResourceUtil.getErrorMessage((HashMap) response.getBody(),"Unable to send fcm");
setStatusToFailedByBulkFcmId(bulkFcmId);
throw new ClientException(errorMessage);
}
Instead of passing Completable Future ,can you try creating it as follows:
Use an asynchronous method to make the rest template call in admin:
#Async
public CompletableFuture<List<BusinessObject>> getResponseAsynchronously(String value) {
String url = "https://restendpoint.eu/rest/v2/lang/" + value + "?fields=name";
BusinessObject[] response = restTemplate.getForObject(url, Country[].class);
return CompletableFuture.completedFuture(Arrays.asList(response));
}
then in the controller read the CompletableFuture like:
#GetMapping("")
public List<String> getAllDataFromRestCall() throws Throwable {
CompletableFuture<List<BusinessObject>> businessObjectsFuture = countryClient.getResponseAsynchronously("fr");
List<String> europeanFrenchSpeakingCountries;
try {
europeanFrenchSpeakingCountries = new ArrayList<>(businessObjectsFuture
.get()
.stream()
.map(Country::getName)
.collect(Collectors.toList()));
} catch (Throwable e) {
throw e.getCause();
}
return europeanFrenchSpeakingCountries;
}

How to handle exceptions thrown by the webclient?

I'm trying to figure out how to log exceptions from the webclient, whatever the error status code that is returned from the api that gets called.
I've seen the following implementation:
.onStatus(status -> status.value() != HttpStatus.OK.value(),
rs -> rs.bodyToMono(String.class).map(body -> new IOException(String.format(
"Response HTTP code is different from 200: %s, body: '%s'", rs.statusCode(), body))))
Another example I've seen uses a filter. I guess this filter could be used to log errors as well, aside from requests like in this example:
public MyClient(WebClient.Builder webClientBuilder) {
webClient = webClientBuilder // you can also just use WebClient.builder()
.baseUrl("https://httpbin.org")
.filter(logRequest()) // here is the magic
.build();
}
But are we serious that there is no dedicated exception handler to this thing?
Found it.
bodyToMono throws a WebClientException if the status code is 4xx (client error) or 5xx (Server error).
Full implementation of the service:
#Service
public class FacebookService {
private static final Logger LOG = LoggerFactory.getLogger(FacebookService.class);
private static final String URL_DEBUG = "https://graph.facebook.com/debug_token";
private WebClient webClient;
public FacebookService() {
webClient = WebClient.builder()
.filter(logRequest())
.build();
}
public Mono<DebugTokenResponse> verifyFbAccessToken(String fbAccessToken, String fbAppToken) {
LOG.info("verifyFacebookToken for " + String.format("fbAccessToken: %s and fbAppToken: %s", fbAccessToken, fbAppToken));
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(URL_DEBUG)
.queryParam("input_token", fbAccessToken)
.queryParam("access_token", fbAppToken);
return this.webClient.get()
.uri(builder.toUriString())
.retrieve()
.bodyToMono(DebugTokenResponse.class);
}
private static ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
LOG.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOG.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
#ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) {
LOG.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex);
return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString());
}
}

Retrotif2 + RxJava sending POST request failed

I want to send POST request with Retrofit + RxJava, but it is failing and I don't know the reason. In one activity it's working, in another - don't want to work:
private void sendMerchantInfo() {
try {
String advertiserOriginalDeepLink = "https://mywebsite.com/main-1?param1=value1&param2=value2";
String urlGetParams = LinkParser.getUrlGETParams(advertiserOriginalDeepLink);
Map<Object, Object> merchantInfo = LinkParser.parseUrlGetParams(urlGetParams);
String merchantInfoJson = new Gson().toJson(merchantInfo); //{"param1":"value1","param2":"value2"}
String url = "https://api.endpoint.com/v1/system/merchant/process";
userService = this.serviceGenerator.createService(UserService.class, true);
final Observable observable = userService.sendUserInfo(
url, new RetrofitMapBody(merchantInfo))
.doOnNext(new Consumer<ResponseBody>() {
#Override
public void accept(ResponseBody responseBody) throws Exception {
//handle 200 OK.
}
})
.onErrorResumeNext((ObservableSource<? extends ResponseBody>) v ->
Crashlytics.log("Send user info attempt failed."))
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
addDisposable(observable.subscribe());
}
} catch (Exception exception) {
Crashlytics.log("Send user info attempt failed. " + exception.getMessage());
}
}
I suspect that problem in this part, I am trying to send request in OnCreate() method:
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
Tried to use this, but no effect:
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
What I am doing wrong? It always call onErrorResumeNext() It's probably something with threads because one time I got exception: networkonmainthreadexception. Please help.
Try using RxJava2 Adapter, it will save you a lot!
Step 1: Retrofit client setup
private Retrofit getRetrofitClient() {
return new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //option 1
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.newThread())) //option 2
.build();
}
Step 2: APIService interface (Example)
#GET("endpoint")
Single<ResponseModel> fetch();
Step 3: Usage
Single<ResponseModel> fetch() {
return getRetrofitClient()
.create(APIService.class)
.fetch();
}
Any non-2xx HTTP response will be wrapped in HttpException from which you can extract the status code, the status message and the full HTTP response.
Any connection errors will be wrapped in IOException
And that is all you need to do to wrap your network call in any RxJava stream.

simultaneously call multiple Api request in spring mvc

My requirement is, I have to call multiple API in my REST application . I want each API will work independently irrespective of waiting for response from the other. i.e I need if one API request take 5sec then all the api request should take 5sec also
You can make the API call inside a seperate thread as shown below:
new Thread(new Runnable() {
public void run() {
try {
// Do your api call here
}
catch(Exception e)
{// Log or do watever you need}
}
Thus the API call will work asynchronously !
You can use the org.springframework.web.client.AsyncRestTemplate class which return a ListenableFuture to get the value asynchronously. So your method will have take time equal to the most slowest api call.
public static void main(String[] args) {
AsyncRestTemplate asycTemp = new AsyncRestTemplate();
HttpMethod method = HttpMethod.GET;
// create request entity using HttpHeaders
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> requestEntity = new HttpEntity<String>("params", headers);
ListenableFuture<ResponseEntity<String>> future = asycTemp.exchange("https://www.google.com", method, requestEntity, String.class);
ListenableFuture<ResponseEntity<String>> future1 = asycTemp.exchange("https://www.yahoo.com", method, requestEntity, String.class);
ListenableFuture<ResponseEntity<String>> future2 = asycTemp.exchange("https://www.bing.com", method, requestEntity, String.class);
try {
// waits for the result
ResponseEntity<String> entity = future.get();
// prints body source code for the given URL
System.out.println(entity.getBody());
entity = future1.get();
System.out.println(entity.getBody());
entity = future2.get();
System.out.println(entity.getBody());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

Global Exception handling in Spring reactor and dependent responses

I am trying to create a web application using spring 5 . It's a micro-service which hit few other micro-services. Response from one service is dependent the other.I am using global exception handing in my application.
Here is my code:
#Override
public Mono<Response> checkAvailablity(Request request) {
Mono<Response> authResponse = userService.authenticateToken(request);
return authResponse.doOnSuccess(t -> {
// if success is returned.
// Want to return this innerResponse
Mono<Response> innerResponse =
httpService.sendRequest(Constant.SER_BOOKING_SERVICE_CHECK_AVAILABILTY,
request.toString(), Response.class);
}).doOnError(t -> {
logger.info("Subscribing mono in Booking service - On Error");
Mono.error(new CustomException(Constant.EX_MODULE_CONNECTION_TIMED_OUT));
});
In case of error I want to throw CustomException and catch it in global exception handler:
#ControllerAdvice
public class ExceptionInterceptor {
public static Logger logger = Logger.getLogger(ExceptionInterceptor.class);
#ExceptionHandler(value = CustomException.class)
#ResponseBody
public Response authenticationFailure(ServerHttpRequest httpRequest, ServerHttpResponse response,
CustomException ex) {
logger.info("CustomException Occured with code => " + ex.getMessage());
return buildErrorResponse(ex.getMessage());
}
Based on the above code I have two problems:
The exception which is thrown in Mono.error() is not captured in global exception handler.
In case of success, response from the inner service should be returned.
Used two methods in mono: flatmap() and onErrorMap()
and updated my checkAvailablity() code:
public Mono<Response> checkAvailablity(Request request) {
Mono<Response> authResponse = userService.authenticateToken(request);
return authResponse.flatmap(t -> {
// Added transform() for success case
Mono<Response> response = httpService.sendRequest(Constant.SER_BOOKING_SERVICE_CHECK_AVAILABILTY,
request.toString(), Response.class);
logger.info("Response from SER_BOOKING_SERVICE_CHECK_AVAILABILTY");
return response;
}).onErrorMap(t -> {
// Added onErrorMap() for failure case & now exception is caught in global exception handler.
throw new CustomException(Constant.EX_MODULE_CONNECTION_TIMED_OUT);
});
}

Resources