simultaneously call multiple Api request in spring mvc - spring

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();
}
}

Related

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

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);
});

WireMock gives a null response body when mocking an API call using spring boot resttemplate

I'm facing a weird issue with Wiremock. The code below returns a null response body. Any insight will be very much appreciated.
Stub in my test:
WireMock.stubFor(post(urlPathEqualTo("http://localhost:8080/mapper"))
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withBody(asJson("ct/slotting-response/create_sample_response1.json"))
.withHeader("Content-Type","application/json;charset=UTF-8")));
Actual API call using spring boot resttemplate:
public ResponseEntity<SampleResponse> getsampleValue(final SampleRequest request, RequestHeader requestHeader) throws SlottingException {
try {
log.info("Sending request[payload={}]", request);
final HttpHeaders headers = getRequestHeader(requestHeader);
HttpEntity<?> entity = new HttpEntity<>(request, headers);
final ResponseEntity<SampleResponse> response =
restTemplate.postForEntity("http://localhost:8080/mapper",
entity, SampleResponse.class);
log.info("Sample response {}", response); // response.getBody() gives null
if (HttpStatus.OK.equals(response.getStatusCode())) {
log.info("Sample allocated successfully.");
}
else {
throw new SampleException("failed");
}
return response;
} catch (Exception e) {
throw new SampleException("Failed", e);
}
}
Can someone please point out any obvious mistakes you see in the Wiremock stub?

How to handle the http response from an api call efficiently using spring boot

When we fire an api call to a 3rd party service, we can get different HTTP responses (200, 404 etc.). How can we handle them in a standard way?
private ResponseEntity<ResultHolder> responseEntity;
public ResponseEntity<ResultHolder> serviceTest(String searchText, String countryCode) {
logger.info("Service started");
String url = prepareUrl(searchText,countryCode); //custom method
HttpHeaders header = new HttpHeaders();
prepareHeader(header); //custom method
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
ResultHolder.class);
}catch (Exception e) {
logger.error("Exception while calling the API "+ e);
//Here I am trying to get the value of response code and handle based on that
//Is this the right way to solve the problem?
if(responseEntity.getStatusCodeValue() != 200) {
responseEntity = new ResponseEntity<ResultHolder>(
HttpStatus.BAD_REQUEST);
}
}
logger.info("Service Ended");
return responseEntity;
}
What if I want to display distinct custom messages for server side errors and for user errors like 'No Internet Connection'.
Kindly help me to understand the good practises in this area.
Thank you

Handle HttpClientErrorException$BadRequest: 400 Bad Request in Cucumber RestTemplate

Hi I have written a Cucumber test case where i send a POST request with an XML body, output of that request is 400 Error with an XML body, which is expected and when i throw the request i get that too, but what i get is below:
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:79)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:97)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:709)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:462)
at uk.co.argos.services.order.StepDefinations.TestMethods.POSTrestTemplatewithXML(TestMethods.java:147)
at uk.co.argos.services.order.StepDefinations.StepDefs.user_hits_the_getSlot_request_with_OrderEnricher_with_and_and(StepDefs.java:118)
at ✽.Given User hits the getSlot request with OrderEnricher with "2020-40-32" and "150" and "MK92NW"(OrderEnricher_Negative.feature:5)
And my test step failes with bad request, But ideally that is my expected and i want to pass my test step & scenario, not sure how should i handle it, i have tried applying multiple things. Can anyone help please?
public static ResponseEntity<String> POSTrestTemplatewithXML(URI uri, String XMLforPOST){
ResponseEntity<String> responseEntity = null;
try {
RestTemplate restTemplate=new RestTemplate();
List<HttpMessageConverter<?>> messageConverters=new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new StringHttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
HttpHeaders headers=new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<String> request=new HttpEntity<String>(XMLforPOST, headers);
responseEntity=restTemplate.postForEntity(uri, request, String.class);
}
catch (Exception e){
System.out.println("RESPONSE-" +responseEntity);
byte[] bytes = ((HttpClientErrorException.BadRequest)e).getResponseBodyAsByteArray();
assertTrue(true);
//Convert byte[] to String
String s = new String(bytes);
System.out.println(s);
e.printStackTrace();
}
return responseEntity;
}
Please update your catch method like following :
try{
//your code
} catch (HttpClientErrorException e){
//your code
}

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;
}

Resources