web-dev-qa-db-fra.com

Comment envoyer des requêtes GET parallèles et attendre les réponses des résultats?

J'utilise le client Apache http dans Spring MVC 3.2.2 pour envoyer 5 requêtes Get de manière synchrone, comme illustré.

Comment puis-je envoyer tous ces éléments de manière asynchrone (en parallèle) et attendre le retour des demandes afin de renvoyer une chaîne de charge utile analysée à partir de toutes les demandes GET?

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

}
18
genxgeek

De manière générale, vous devez encapsuler vos unités de travail dans un Runnable ou Java.util.concurrent.Callable et les exécuter via Java.util.concurrent.Executor (ou org.springframework.core.task.TaskExecutor). Cela permet à chaque unité de travail d'être exécutée séparément, généralement de manière asynchrone (selon l'implémentation de Executor).

Donc, pour votre problème spécifique, vous pouvez faire quelque chose comme ceci:

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

Notez que ce code n'a pas été testé.

Pour votre implémentation Executor, consultez TaskExecutor de Spring et tâche: espace de noms de l'exécuteur . Vous voulez probablement un pool de threads réutilisable pour ce cas d'utilisation (au lieu de créer un nouveau thread à chaque fois).

13
superEb

Vous devez utiliser AsyncHttpClient. Vous pouvez faire n'importe quel nombre de demandes et il vous rappellera lorsqu'il recevra une réponse. Vous pouvez configurer le nombre de connexions qu'il peut créer. Tous les threads sont gérés par la bibliothèque, il est donc beaucoup plus facile que de gérer les threads vous-même.

jetez un oeil à l'exemple ici: https://github.com/AsyncHttpClient/async-http-client

13
Alper Akture

Pour l'exécution parallèle de plusieurs requêtes avec une seule instance HttpClient.

configurer PoolingHttpClientConnectionManager pour une exécution parallèle.

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();
1
Radadiya Nikunj

Déplacez votre code de demande vers une méthode distincte:

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

Et soumettez-les à 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();

Ou

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

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

Ou utilisez RestTemplate comme décrit dans Comment utiliser Spring WebClient pour effectuer plusieurs appels simultanément?

1
Justas