How to combine asynchronous HTTP requests responses to one? - java

I'm trying to make x amount of HTTP requests asynchronously. I looked questions Asynchronous IO in Java? and How do you create an asynchronous HTTP request in JAVA?. I found good library Asynchronous Http and WebSocket Client library for Java, but I don't understand how I can safely combine multiple results into one result. For example if I have following code:
AsyncHttpClient c = new AsyncHttpClient();
List<String> urls = getUrls();
List<MyResultObject> results = new ArrayList<>();
for(String url : urls)
{
// Create asynchronous request
Future<MyResultObject> f = c.prepareGet(url).execute(handler);
// How can I add completed responses to my results list ???
}
How can I safely combine those results into List and continue when all requests have finished.

I found this tutorial for using futures. You could just do the following:
AsyncHttpClient c = new AsyncHttpClient();
List<String> urls = getUrls();
List<Future<MyResultObject>> futures = new ArrayList<>(); // keep track of your futures
List<MyResultObject> results = new ArrayList<>();
for(String url : urls)
{
// Create asynchronous request
Future<MyResultObject> f = c.prepareGet(url).execute(handler);
futures.add(f);
}
// Now retrieve the result
for (Future<MyResultObject> future : futures) {
try {
results.add(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// continue with your result list

You can call the get() method of Future class to obtain the result. Note that call to the method may block until result is available

if you want to combile several http request and get all the result.
you can look at the code blow.
package ParallelTasks;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ParallelHttpRequest {
//thread pool to execute http request task.
static final ExecutorService businessRequestExecutor = Executors.newCachedThreadPool();
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<String> urlList = new ArrayList<String>();
final CountDownLatch latch = new CountDownLatch(urlList.size());
List<Future<Pair<String, String>>> list = new ArrayList<Future<Pair<String, String>>>();
for (final String url : urlList) {
Future<Pair<String, String>> future = businessRequestExecutor.submit(new Callable<Pair<String, String>>() {
public Pair<String, String> call() throws Exception {
try {
//do post or get http request here.
//SoaHttpUtil.post(config.getUrl(), buReqJson);
String result = "";
return new MutablePair<String, String>(url, result);
} catch (Exception ex) {
System.out.println(ex);
return new MutablePair<String, String>(url, null);
} finally {
latch.countDown();
}
}
});
list.add(future);
}
//wait no more than 5 seconds.
latch.await(5000, TimeUnit.MILLISECONDS);
//print finished request's result.
for (Future<Pair<String, String>> future : list) {
if (future.isDone()) {
System.out.println(future.get().getValue());
}
}
}
}

Related

Collecting results from a list of Futures in java

I'm trying to use futures to make concurrent api calls. Code:
private void init() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
List<ApiResponse> responses = fetchAllUsingFuture(ids, 3);
log.info(responses.toString());
Long finish = System.currentTimeMillis();
log.info(MessageFormat.format("Process duration: {0} in ms", finish-start));
}
private List<ApiResponse> fetchAllUsingFuture(List<String> ids, int threadCount) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
List<List<String>> chunks = Utils.splitToChunks(ids, threadCount);
List<Future<List<ApiResponse>>> futures = new ArrayList<>();
chunks.forEach(chunk -> {
futures.add(wrapFetchInFuture(chunk));
});
Future<List<ApiResponse>> resultFuture = executorService.submit(() -> {
List<ApiResponse> responses = new ArrayList<>();
futures.forEach(future -> {
try {
responses.addAll(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
return responses;
});
executorService.shutdown();
return resultFuture.get();
}
private Future<List<ApiResponse>> wrapFetchInFuture(List<String> ids) {
return new FutureTask<>(() -> {
List<ApiResponse> responses = new ArrayList<>();
ids.forEach(id -> {
responses.add(fetchData(id));
});
return responses;
});
}
private ApiResponse fetchData(String id) {
ResponseEntity<ApiResponse> response = restTemplate.getForEntity(id, ApiResponse.class);
log.info(MessageFormat.format("Fetching from {0}", id));
ApiResponse body = response.getBody();
log.info(MessageFormat.format("Retrieved {0}", body));
return body;
}
It doesn't execute, the app starts and then just pends. Futures don't get fulfilled. All advices are appreciated.
P.S. I'm aware this is much more easily done using CompletableFuture, I was just wondering how to do this with Futures
In the original version of the question, you are creating a list of FutureTasks but never send them to the ExecutorService to run them. The tasks never complete, so Future.get blocks forever.
In the updated version of the question, you have put the code that does the waiting into the executor service as a task. The FutureTasks never run, so FutureTask.get will still block forever.
I would suggest you change the code in fetchAllUsingFuture to:
List<Callable<List<ApiResponse>>> tasks = new ArrayList<>();
chunks.forEach(chunk -> {
tasks.add(wrapFetchInCallable(chunk));
});
List<Future<List<ApiResponse>>> futures = executorService.invokeAll(tasks);
where wrapFetchInCallable creates a Callable instead of FutureTask:
private static Callable<List<ApiResponse>> wrapFetchInCallable(List<String> ids) {
return () -> {
List<ApiResponse> responses = new ArrayList<>();
ids.forEach(id -> {
responses.add(fetchData(id));
});
return responses;
};
}
It looks like you are creating a list of FutureTasks but never send them to the ExecutorService to run them.
I have implemented ExecutorService with Future Object as below, i hope it helps you:
Service layer:
public List<MovieDTO> searchMoviesParallel(String limit, String offset, String searchPhrase) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<List<MovieDTO>> digitoonResult = executor.submit(new DigitoonSearchTask(limit, offset, searchPhrase));
List<MovieDTO> movieDTOList = digitoonResult.get();
executor.shutdown();
return movieDTOList;
}
And my Search task(DigitoonSearchTask class) is as below:
public class DigitoonSearchTask implements Callable<List<MovieDTO>> {
private String limit;
private String offset;
private String searchPhrase;
private final static String digitoonSearchBaseUrl = "http://apitwo.xxx.com/partner/search/?q=";
public DigitoonSearchTask(String limit, String offset, String searchPhrase) {
this.limit = limit;
this.offset = offset;
this.searchPhrase = searchPhrase;
}
#Override
public List<MovieDTO> call() throws Exception {
List<MovieDTO> movieDTOList = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
try {
String uri = digitoonSearchBaseUrl + URLEncoder.encode(searchPhrase, "utf-8") + "&limit=" + limit + "&offset=" + offset;
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("authorization", "xxxxxxxxxx");
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
BufferedReader br = new BufferedReader(new InputStreamReader(
(conn.getInputStream())));
String output;
while ((output = br.readLine()) != null) {
movieDTOList = Arrays.asList(mapper.readValue(output, MovieDTO[].class));
}
br.close();
conn.disconnect();
} catch (UnknownHostException e) {
call();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return movieDTOList;
}}
consider that now I have just one API and after getting others they can be added as another Search task in service layer by increasing the thread number.

How to call multiple soap calls parallel in java-spring application

I want to make my soap request calls parallel in java.
I'm new to multithreading not getting the way to do it.
public List<TB600Model.Response> getTableDesc(final List<TB600Model.Request> requests)
{
List<TB600Model.Response> responses = new ArrayList<>();
for (TB600Model.Request request : requests)
{
responses.add(
this.modifyDescription(
this.getDescription(
request.getSite()
,request.getDescType()
,request.getKeyData()
,request.getEffMdy()
)
,request.getDescType()
,request.getKeyData()
)
);
}
return responses;
}
enter image description here
For Java 8+ Try to use this method instead :
requests.parallelStream().forEach(request -> {...});
put the code for response.add instead of ...
For earlier version :
ExecutorService es = Executors.newFixedThreadPool(10);
List<Response> responses = new ArrayList<>();
for(Request request: requests){
Future<Response> future = es.submit(new Callable<Response>() {
#Override
public Response call() throws Exception {
//create your response here and return
}
});
try {
responses.add(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

How socket.io get response with synchronous call on android?

I'm writing a android chat application with socket.io-client-java.I want to check whether the client user exist at first.So I need to send a command like "user/exist" to server url and get the response from server.I need to wait the server response then can go to next step.But the socket.io use the asynchronous callback.For getting the response synchronous I known the Furture and Callable only.So I tried the way using code as below:
//this is request method using socket.io
public JSONObject request(final String method,final String url,final JSONObject data){
final JSONObject responseObj = new JSONObject();
if (mSocket.connected()) {
mSocket.emit(method, reqObj, new Ack() {
#Override
public void call(Object... objects) {
System.out.println("get Ack");
try {
responseObj.put("body", (JSONObject) objects[0]);
}catch (JSONException e){
e.printStackTrace();
}
}
})
}
}
//this is Callable call implement
#Override
public JSONObject call(){
return request("get","https://my-chat-server/user/exist",new JSONObject());
}
//this is call method in activity
ExecutorService executor = Executors.newCachedThreadPool();
Future<JSONObject> response = executor.submit(mApiSocket);
executor.shutdown();
JSONObject respObj = new JSONObject();
JSONObject respBody = new JSONObject();
try {
respObj = response.get();
respBody = respObj.getJSONObject("body");
}catch (ExecutionException e){
}catch(InterruptedException e1){
}catch(JSONException e2){
}
But it dose not work.The respObj is null.
How can i get the reponse synchronous?
I am a green hand on java and forgive my poor chinese english.
Any help would be appreciated!
I known the js can use Promise and await like below:
//request method
static request(method, url, data) {
return new Promise((resolve, reject) => {
this.socket.emit(method,
{
url: url,
method,
data,
},
async (res) => {
if (res.statusCode == 100) {
resolve(res.body, res);
} else {
throw new Error(`${res.statusCode} error: ${res.body}`);
reject(res.body, res);
}
}
)
})
}
//call method
response = await mSocket.request('get','https://my-chat-server/user/exist', {
first_name: 'xu',
last_name: 'zhitong',
});
I'm not sure this is the best way but we can wait for the callback as follows:
#Nullable
Object[] emitAndWaitForAck(#NotNull String event, #Nullable Object[] args,
long timeoutMillis) {
Object[][] response = new Object[1][1];
Semaphore lock = new Semaphore(0);
socketClient.emit(event, args, ackArgs -> {
response[0] = ackArgs;
lock.release();
});
try {
boolean acquired = lock.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
if (acquired) {
return response[0];
}
} catch (InterruptedException ignored) {
}
return null;
}
Assuming your socket.io server returns one argument containing the body (or null) you would call it something like this:
String method = "get";
String url = "https://my-chat-server/user/exist";
long timeoutMillis = 5000;
Object[] args = emitAndWaitForAck(method, new String[]{url}, timeoutMillis);
JSONObject response = (JSONObject) args[0];

Partial retrieval of data from a batch of tasks

I'm using ExecutorService for submitting a batch of tasks. I'm doing it something like this:
ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threads));
List<ListenableFuture<Whatever>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
results.add(exec.submit(new MyTask(i)));
}
ListenableFuture<List<Whatever>> listListenableFuture = Futures.successfulAsList(futures);
try {
List<Whatever> responses = listListenableFuture.get(2000, TimeUnit.MILLISECONDS);
for (Whatever response : responses) {
LOG.info("Yay!");
}
} catch (TimeoutException e) {
LOG.info("Timeout Exception");
} catch (Exception e) {
// Nay!
}
The problem here is - if one of the task takes longer than 2000ms, will throw the TimeoutException and I'll get nothing in the response though some of the tasks might have finished at that very point.
So I want to retrieve the response (be it partial or complete) of the tasks that have been finished till it timeouts (2000ms). Eg:
(time relative to the START_TIME of the batch call)
Task-1: 1000ms
Task-2: 3000ms
Task-3: 1800ms
Output:
Timeout Exception
Desired Output:
Yay! <- corresponds to task-1
Yay! <- corresponds to task-3
One solution I thought of is to fetch the futures individually and set their timeout as MAX(0, TIME_OUT - TIME_NOW - START_TIME). This might work but doesn't seems like a clean solution to me.
You might use a decorate callable which handles the time out.
Suppose this is the original callable:
class OriginalCallable implements Callable<String> {
#Override
public String call() throws Exception {
return "";
}
}
You can construct a decorate callable with this original callable and the executor:
class DecorateCallable implements Callable<String> {
ExecutorService executorService;
OriginalCallable callable;
public DecorateCallable(ExecutorService executorService, OriginalCallable callable) {
this.executorService = executorService;
this.callable = callable;
}
#Override
public String call() throws Exception {
Future<String> future = executorService.submit(callable);
try {
return future.get(2000, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException e) {
}
return null;
}
}
If you decide to use this, you need double you pool size:
Executors.newFixedThreadPool(threads * 2);
and add some condition like if(future.get() != null) before put them into the final result set.
If you use Futures.getChecked, the timeout exceptions get swallowed, and the future will return null. Check out the following code, where one of the tasks throws TimeoutException, and the corresponding future returns null.
import java.io.IOException;
import com.google.common.util.concurrent.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
public class App
{
public static void main( String[] args ) throws InterruptedException, ExecutionException
{
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
ListenableFuture<String> future1 =
listeningExecutorService.submit(() -> {
throw new TimeoutException("Timeout exception");
});
ListenableFuture<String> future2 =
listeningExecutorService.submit(() -> "Hello World");
ListenableFuture<List<String>> combined = Futures.successfulAsList(future1, future2);
try {
String greeting = Futures.getChecked(combined, IOException.class, 2000l, TimeUnit.MILLISECONDS).stream().collect(Collectors.joining(" "));
System.out.println(greeting);
} catch (IOException e) {
System.out.println("Exception: " + e.getMessage());
} finally {
listeningExecutorService.shutdown();
}
}
}

Apache's HttpAsyncClient unreliable with multiple requests

I'm currently in a project where I have to do multiple, concurrent http requests to a rest service which returns a JSON response. This is a batch operation and the number of requests at any time could range from several hunderd to several thousend.
That's why I thought it would be a good idea to have an async http client so I could have concurrent requests, which dramatically could speed up the process. I first tried ning's async-http-client. Maybe I was doing something wrong, because it was kind of slow for me. About 10 seconds for 1000 requests.
After which I tried Apache's implementation which was much faster at about 4 seconds for 1000 requests. But I can't seem to get the requests to get stable. Most of the time I will get a List with a 1000 responses (like I expect), but sometimes I am just missing a few responses, like 1 or 2.
This is currently my code:
public class AsyncServiceTest {
public AsyncServiceTest(String serviceURI) {
this.httpClient = HttpAsyncClients.custom().setMaxConnPerRoute(100).setMaxConnTotal(20)
.setDefaultRequestConfig(RequestConfig.custom().build()).build();
this.objectMapper = new ObjectMapper();
this.serviceURI = serviceURI;
}
private List<Object> getResults(List<String> queryStrings) throws Exception {
try {
httpClient.start();
final List<HttpGet> requests = new ArrayList<>(addresses.size());
for (String str : queryStrings) {
requests.add(new HttpGet(buildUri(str))); // In this method we build the absolute request uri.
}
final CountDownLatch latch = new CountDownLatch(requests.size());
final List<Object> responses = new ArrayList<>(requests.size());
final List<String> stringResponses = new ArrayList<>(requests.size());
for (final HttpGet request : requests) {
httpClient.execute(request, new FutureCallback<HttpResponse>() {
#Override
public void completed(HttpResponse response) {
try {
stringResponses.add(IOUtils.toString(response.getEntity().getContent(), "UTF-8"));
latch.countDown();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void failed(Exception e) {
latch.countDown();
}
#Override
public void cancelled() {
latch.countDown();
}
});
}
latch.await();
for (String r : stringResponses) {
responses.add(mapToLocation(r)); // Mapping some Strings to JSON in this method.
}
return responses;
} finally {
httpClient.close();
}
}
}
So, in essence, I am wondering if there is something wrong with my code (probably) or is it just because of the way the library works? Because the CountDownLatch is at zero all the time. Or does anyone have a pointer in the right direction (maybe with another library)?
It seemed to be a concurrency problem (thanks to #vanOekel) in my code. The answer is to replace the ArrayList<E> with a Vector<E>, which is in fact thread-safe. Example code:
public class AsyncServiceTest {
public AsyncServiceTest(String serviceURI) {
this.httpClient = HttpAsyncClients.custom().setMaxConnPerRoute(100).setMaxConnTotal(20)
.setDefaultRequestConfig(RequestConfig.custom().build()).build();
this.objectMapper = new ObjectMapper();
this.serviceURI = serviceURI;
}
private List<Object> getResults(List<String> queryStrings) throws Exception {
try {
httpClient.start();
final CountDownLatch latch = new CountDownLatch(queryStrings.size());
final Vector<Object> responses = new Vector<>(queryStrings.size());
for (String str : queryStrings) {
// buildUri: In this method we build the absolute request uri.
httpClient.execute(new HttpGet(buildUri(str)), new FutureCallback<HttpResponse>() {
#Override
public void completed(HttpResponse response) {
try {
// mapToLocation: Mapping some Strings to JSON in this method.
responses.add(mapToLocation(IOUtils.toString(response.getEntity().getContent(), "UTF-8")));
latch.countDown();
} catch (IOException e) {
failed(e);
}
}
#Override
public void failed(Exception e) {
logger.error(e.getLocalizedMessage(), e);
latch.countDown();
}
#Override
public void cancelled() {
logger.error("Request cancelled.");
latch.countDown();
}
});
}
latch.await();
return responses;
} finally {
httpClient.close();
}
}
}
Thanks for all the helpful responses. If anyone has any suggestions regarding optimization of the above code, I will be glad to hear so.

Categories

Resources