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
- Client will call connectAPI which will call a third party API.
- The third party API after processing above call will callback
postQuestionnaire. ---> This should be done seperate thread usingCallable
Task using executor service. Then you will haveFuture<ResponseFromPOstQuestionnaire>
returned from executor servicesubmit
call - The connectAPI will send response only when the
callback at postQuestionnaire is received. ---> Once you hacveFuture
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
Java.Util.Zip.Zipexception: Error in Opening Zip File
How to Reference Another Property in Java.Util.Properties
Tokenizing a String But Ignoring Delimiters Within Quotes
What Is the Point of Overloaded Convenience Factory Methods for Collections in Java 9
Concurrentmodificationexception When Adding Inside a Foreach Loop in Arraylist
Why I Can't Create an Array with Large Size
How to Simulate Keyboard Presses in Java
How to Sum Digits of an Integer in Java
Printing Runtime Exec() Outputstream to Console
The Connection Between 'System.Out.Println()' and 'Tostring()' in Java
How to Avoid Constructor Code Redundancy in Java
Java Garbage Collector - When Does It Collect
Java 8: Difference Between Method Reference Bound Receiver and Unbound Receiver