How to Create an Asynchronous Http Request in Java

How do you create an asynchronous HTTP request in JAVA?

Note that java11 now offers a new HTTP api HttpClient, which supports fully asynchronous operation, using java's CompletableFuture.

It also supports a synchronous version, with calls like send, which is synchronous, and sendAsync, which is asynchronous.

Example of an async request (taken from the apidoc):

   HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/"))
.timeout(Duration.ofMinutes(2))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);

Concurrent asynchronous HTTP requests in Java and/or Spring Boot

Basically you will want to do something like this:

package com.example.demo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public class GamesProcessor {
private static final String GAME_URI_BASE = "https://la2.api.riotgames.com/lol/match/v4/matches/";
private static final String ACCOUNT_URI_BASE = "https://la2.api.riotgames.com/lol/match/v4/matchlists/by-account/";
private Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1);

@Autowired
private RestTemplate restTemplate;

public void processGames(String accountId) throws JsonProcessingException, ExecutionException, InterruptedException {
String responseAsString = restTemplate.getForObject(ACCOUNT_URI_BASE + accountId, String.class);
ObjectMapper objectMapper = new ObjectMapper();

if (responseAsString != null) {
Map<String, Object> response = objectMapper.readValue(responseAsString, new TypeReference<Map<String, Object>>() {
});

List<Map<String, Object>> matches = (List<Map<String, Object>>) ((Map<String, Object>) response.get("data")).get("matches");

List<CompletableFuture<Void>> futures = matches.stream()
.map(m -> (String) m.get("gameId"))
.map(gameId -> CompletableFuture.supplyAsync(() -> restTemplate.getForObject(GAME_URI_BASE + gameId, String.class), executor)
.thenAccept(r -> {
System.out.println(r); //do whatever you wish with the response here
}))
.collect(Collectors.toList());

// now we execute all requests asynchronously
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
}
}
}

Please note that it is not a refined code, but just a quick example of how to achieve this. Ideally you would replace that JSON processing that I've done "by hand" using Map by a response bean that matches the structure of the response you get from the service you are calling.

A quick walk through:

String responseAsString = restTemplate.getForObject(ACCOUNT_URI_BASE + accountId, String.class);

This executes the first REST request and gets it as a String (the JSON response). You will want to properly map this using a Bean object instead. Then this is processed using the ObjectMapper provided by Jackson and transformed into a map so you can navigate the JSON and get the matches.

List<CompletableFuture<Void>> futures = matches.stream()
.map(m -> (String) m.get("gameId"))
.map(gameId -> CompletableFuture.supplyAsync(() -> restTemplate.getForObject(GAME_URI_BASE + gameId, String.class), executor)
.thenAccept(r -> {
System.out.println(r); //do whatever you wish with the response here
}))
.collect(Collectors.toList());

Once we have all the matches we will use the Stream API to transform them into CompletableFutures that will be executed asynchronously. Each thread will make another request in order to get the response for each individual matchId.

System.out.println(r);

This will be executed for each response that you get for each matchId, just like in your example. This should also be replaced by a proper bean matching the output for clearer processing.

Note that List<CompletableFuture<Void>> futures only "holds the code" but will not get executed until we combine everything in the end using CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(); and execute the blocking get() method.

How to abstract asynchronous HTTP requests as synchronous

If you have this:

public ImmediateResponse asynchCall(Callback callback) throws ImmediateException {...}

Where Callback looks like this:

interface Callback {
void onSuccess(EventualResponse eventualResponse);
void onFailure(Exception e);
}

Then what you want is something like this:

public static EventualResponse synchCall(long timeout, TimeUnit timeUnit)
throws InterruptedException, TimeoutException, EventualException
{
CompletableFuture<EventualResponse> responseFuture = new CompletableFuture<>();
Callback callback = new Callback() {
public void onSuccess(EventualResponse response) {
responseFuture.complete(response);
}
public void onFailure(Exception e) {
responseFuture.completeExceptionally(e);
}
};
try {
/*ImmediateResponse immediateResponse = */asynchCall(callback);
// use immediateResponse if you need it
return responseFuture.get(timeout, timeUnit);
} catch (ImmediateException e) {
throw new EventualException(e);
} catch (ExecutionException ee) {
throw new EventualException(ee.getCause());
}
}

asynchronous HTTP request in java

If you're not interested in reading the response at all you can just use URL.openStream() to create a connection and then immediately close the socket (or ignore it and let it time out, if you feel like being mean to the server). This isn't strictly asynchronous, but it will be quite a bit faster than any approach that relies upon fetching and parsing the server's response.

This can of course be made asynchronous by offloading the openStream() calls to another thread, either manually or by using the utilities available in java.util.concurrent.

Fast and asynchronous way of making multiple http requests in JAVA

Answering my own question. Tried Apaches asynchronous http client but after a while I started using Ning's async client and I am happy with it.

How to send asynchronous Http Response?

Ideally this should be your flow

  1. Client will call connectAPI which will call a third party API.
  2. The third party API after processing above call will callback
    postQuestionnaire. ---> This should be done seperate thread using Callable Task using executor service. Then you will have Future<ResponseFromPOstQuestionnaire> returned from executor service submit call
  3. The connectAPI will send response only when the
    callback at postQuestionnaire is received. ---> Once you hacve Future object you can do wait on by calling .get() (this is blocking call) so it will wait for response to come then you can return the same response or modified respone back to client.

Example on how to use callable task with executor service is explain here -> https://www.journaldev.com/1090/java-callable-future-example

CompletableFuture: how to combine two asynchronous requests into one response

return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenCompose(httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
final CombinationBothResponses combinationBothResponses = jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
return sendSecondPostRequest(valueToBeUsedInRequestBody2)
.thenApply(responseBodyRequestTwo -> {
// 4. Combine response 1 and response 2 to the final response of REST controller
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
});
}
return CompletableFuture.failedFuture(new RuntimeException());
});


Related Topics



Leave a reply



Submit