33

I'm using apache http client within spring mvc 3.2.2 to send 5 get requests synchronously as illustrated.

How can I send all of these asynchronously (in parallel) and wait for the requests to return in order to return a parsed payload string from all GET requests?

public String myMVCControllerGETdataMethod()
{
   // Send 1st request 
   HttpClient httpclient = new DefaultHttpClient();
   HttpGet httpget = new HttpGet("http://api/data?type=1");   
   ResponseHandler<String> responseHandler = new BasicResponseHandler();
   String responseBody = httpclient.execute(httpget, responseHandler);

   // Send 2st request 
   HttpClient httpclient2 = new DefaultHttpClient();
   HttpGet httpget2 = new HttpGet("http://api/data?type=2");   
   ResponseHandler2<String> responseHandler2 = new BasicResponseHandler();
   String responseBody2 = httpclient.execute(httpget, responseHandler2);

   // o o o more gets here

   // Perform some work here...and wait for all requests to return
   // Parse info out of multiple requests and return
   String results = doWorkwithMultipleDataReturned();

   model.addAttribute(results, results);
   return "index";

}
genxgeek
  • 13,109
  • 38
  • 135
  • 217

4 Answers4

17

Just in general, you need to encapsulate your units of work in a Runnable or java.util.concurrent.Callable and execute them via java.util.concurrent.Executor (or org.springframework.core.task.TaskExecutor). This allows each unit of work to be executed separately, typically in an asynchronous fashion (depending on the implementation of the Executor).

So for your specific problem, you could do something like this:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {
    //inject this
    private Executor executor;

    @RequestMapping("/your/path/here")
    public String myMVCControllerGETdataMethod(Model model) {
        //define all async requests and give them to injected Executor
        List<GetRequestTask> tasks = new ArrayList<GetRequestTask>();
        tasks.add(new GetRequestTask("http://api/data?type=1", this.executor));
        tasks.add(new GetRequestTask("http://api/data?type=2", this.executor));
        //...
        //do other work here
        //...
        //now wait for all async tasks to complete
        while(!tasks.isEmpty()) {
            for(Iterator<GetRequestTask> it = tasks.iterator(); it.hasNext();) {
                GetRequestTask task = it.next();
                if(task.isDone()) {
                    String request = task.getRequest();
                    String response = task.getResponse();
                    //PUT YOUR CODE HERE
                    //possibly aggregate request and response in Map<String,String>
                    //or do something else with request and response
                    it.remove();
                }
            }
            //avoid tight loop in "main" thread
            if(!tasks.isEmpty()) Thread.sleep(100);
        }
        //now you have all responses for all async requests

        //the following from your original code
        //note: you should probably pass the responses from above
        //to this next method (to keep your controller stateless)
        String results = doWorkwithMultipleDataReturned();
        model.addAttribute(results, results);
        return "index";
    }

    //abstraction to wrap Callable and Future
    class GetRequestTask {
        private GetRequestWork work;
        private FutureTask<String> task;
        public GetRequestTask(String url, Executor executor) {
            this.work = new GetRequestWork(url);
            this.task = new FutureTask<String>(work);
            executor.execute(this.task);
        }
        public String getRequest() {
            return this.work.getUrl();
        }
        public boolean isDone() {
            return this.task.isDone();
        }
        public String getResponse() {
            try {
                return this.task.get();
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    //Callable representing actual HTTP GET request
    class GetRequestWork implements Callable<String> {
        private final String url;
        public GetRequestWork(String url) {
            this.url = url;
        }
        public String getUrl() {
            return this.url;
        }
        public String call() throws Exception {
            return new DefaultHttpClient().execute(new HttpGet(getUrl()), new BasicResponseHandler());
        }
    }
}

Note that this code has not been tested.

For your Executor implementation, check out Spring's TaskExecutor and task:executor namespace. You probably want a reusable pool of threads for this use-case (instead of creating a new thread every time).

superEb
  • 5,613
  • 35
  • 38
  • Ah, very cool! I will give this a test drive. Thx! However, one question I do have is how do I know which response is which in the iterative loop? Also, what does that mean to keep my controller stateless passing in the results to the doWorkwithMultipleDataReturned() method? – genxgeek Jul 22 '13 at 21:41
  • The sample code allows you to match the original request (URL) with the response via the `GetRequestTask` abstraction. So at the line `//PUT YOUR CODE HERE` you already have both as strings. About the stateless comment, I was assuming since your method `doWorkwithMultipleDataReturned` did not take any arguments that you might hold the responses in an instance variable of the controller, which makes your controller stateful (limiting use of same instance across multiple threads). Rather, you should keep references to responses only as method variables to avoid that problem. – superEb Jul 22 '13 at 22:14
  • @superEb how about RESTtemplate? how to send synchronous requests using that? – Jack Mar 18 '15 at 22:57
15

You should use AsyncHttpClient. You can make any number of requests, and it will make a call back to you when it gets a response. You can configure how many connections it can create. All the threading is handled by the library, so it's alot easier than managing the threads yourself.

take a look a the example here: https://github.com/AsyncHttpClient/async-http-client

Alper Akture
  • 2,445
  • 1
  • 30
  • 44
  • Yes, I would rather use asynch here because using threads even from a managed pool will block incoming http requests as they will tie up the i/o->clr/jvm. The only issue that I see is the method that I'm calling it from is a spring mvc controller. So, I'm not sure how I can use a callback to call back into the same view again. That' the the draw back. The app is a web app and it's being used as User Interface. – genxgeek Jul 24 '13 at 02:58
  • I guess in that case you'd have to use some Servlet 3 async handling if you can. Is that possible? – Alper Akture Jul 24 '13 at 19:40
  • The asynchhttp worked like a charm. Added all of my requests back to back to back...and sent in parallel within my controller. Then waited for each in the future response to come back and passed into my view. Very nice! Thanks again for the info! – genxgeek Jul 29 '13 at 21:47
  • Great! You probably know there's no guarantee you will get them back in the order you requested. Just noticed that requirement in your original question. – Alper Akture Jul 30 '13 at 00:29
  • @JaJ I have a relevant problem with performance. I am using Spring Boot and tried using Spring's AsyncRestTemplate initialized as follows `return new AsyncRestTemplate(new HttpComponentsAsyncClientHttpRequestFactory())`. Then I invoke a web service to get a number of records (wide range, 0-500). For each of those records, I need to invoke another web service, wait for all the results to come in, stitch the results together across all those records and send back the response. Performance is abysmal and I am trying to use the right set up and async http library. Any words of advice? – Web User Dec 22 '16 at 22:04
  • @AlperAkture URL you shared above is broken. Please edit or remove it to avoid an empty click :) – realPK Aug 14 '19 at 23:47
  • Link is https://www.nurkiewicz.com/2013/05/java-8-definitive-guide-to.html – Alper Akture Nov 27 '19 at 18:44
  • This thread is probably inactive by now but while asynchronously sending requests, how does async-http-cient keep track of what responses correspond to what requests? Or is there a possibility that responses may not match the request? – Kalyan M Feb 26 '20 at 10:09
8

Move your request code to separate method:

private String executeGet(String url){
   HttpClient httpclient = new DefaultHttpClient();
   HttpGet httpget = new HttpGet(url);   
   ResponseHandler<String> responseHandler = new BasicResponseHandler();
   return httpclient.execute(httpget, responseHandler);
}

And submit them to ExecutorService:

ExecutorService executorService = Executors.newCachedThreadPool();

Future<String> firstCallFuture = executorService.submit(() -> executeGet(url1));
Future<String> secondCallFuture = executorService.submit(() -> executeGet(url2));

String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();

executorService.shutdown();

Or

Future<String> firstCallFuture = CompletableFuture.supplyAsync(() -> executeGet(url1));
Future<String> secondCallFuture = CompletableFuture.supplyAsync(() -> executeGet(url2));

String firstResponse = firstCallFuture.get();
String secondResponse = secondCallFuture.get();

Or use RestTemplate as described in How to use Spring WebClient to make multiple calls simultaneously?

Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114
1

For parallel execution of multiple request with single HttpClient instance.

configure PoolingHttpClientConnectionManager for parallel execution.

HttpClientBuilder builder = HttpClientBuilder.create();

PlainConnectionSocketFactory plainConnectionSocketFactory = new PlainConnectionSocketFactory();

Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", plainConnectionSocketFactory).build();
PoolingHttpClientConnectionManager ccm = new PoolingHttpClientConnectionManager(registry);
        ccm.setMaxTotal(BaseConstant.CONNECTION_POOL_SIZE); // For Example : CONNECTION_POOL_SIZE = 10 for 10 thread parallel execution
        ccm.setDefaultMaxPerRoute(BaseConstant.CONNECTION_POOL_SIZE);
        builder.setConnectionManager((HttpClientConnectionManager) ccm);

HttpClient objHttpClient = builder.build();
Radadiya Nikunj
  • 988
  • 11
  • 10