12

Sto usando il client http apache all'interno di spring mvc 3.2.2 per inviare 5 richieste get in modo sincrono come illustrato.Come inviare richieste GET parallele e attendere le risposte dei risultati?

Come posso inviare tutti questi in modo asincrono (in parallelo) e attendere il ritorno delle richieste per restituire una stringa di payload analizzata da tutte le richieste 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"; 

} 

risposta

10

Proprio in generale, è necessario incapsulare le unità di lavoro in un Runnable o java.util.concurrent.Callable ed eseguirle tramite java.util.concurrent.Executor (o org.springframework.core.task.TaskExecutor). Ciò consente a ciascuna unità di lavoro di essere eseguita separatamente, in genere in modo asincrono (a seconda dell'implementazione di Executor).

Quindi per la vostra problema specifico, si potrebbe fare qualcosa di simile:

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

Si noti che questo codice non è stato testato.

Per l'implementazione Executor, vedere Spring's TaskExecutor e task:executor namespace. Probabilmente vuoi un pool riutilizzabile di thread per questo caso d'uso (invece di creare un nuovo thread ogni volta).

+0

Ah, molto cool! Darò questo un test drive. Grazie! Tuttavia, una domanda che ho è come faccio a sapere qual è la risposta nel ciclo iterativo? Inoltre, che cosa significa mantenere il mio controller stateless passando i risultati al metodo doWorkwithMultipleDataReturned()? – JaJ

+0

Il codice di esempio consente di abbinare la richiesta originale (URL) con la risposta tramite l'astrazione 'GetRequestTask'. Quindi alla riga '// PUT YOUR CODE HERE' hai già entrambi come stringhe. Per quanto riguarda il commento stateless, stavo assumendo poiché il tuo metodo 'doWorkwithMultipleDataReturned' non ha accettato argomenti che potresti contenere le risposte in una variabile di istanza del controller, il che rende lo stato del controller (limitando l'uso della stessa istanza su più thread) . Piuttosto, dovresti mantenere i riferimenti alle risposte solo come variabili di metodo per evitare questo problema. – superEb

+0

Ottimi punti! Grazie ancora per le informazioni! – JaJ

11

È necessario utilizzare AsyncHttpClient. Puoi fare un numero qualsiasi di richieste e ti richiamerà quando riceve una risposta. Puoi configurare quante connessioni può creare. Tutto il threading è gestito dalla libreria, quindi è molto più semplice della gestione dei thread.

un'occhiata un esempio qui: https://github.com/AsyncHttpClient/async-http-client

+0

fantastico! apprezzare le informazioni! – JaJ

+0

spero che aiuti! è solido come una roccia. lo usiamo per tutte le nostre chiamate http a servizi esterni. –

+0

Sì, preferisco usare asynch qui perché l'utilizzo di thread anche da un pool gestito bloccherà le richieste HTTP in arrivo poiché vincoleranno i/o-> clr/jvm. L'unico problema che vedo è il metodo da cui lo chiamo è un controller mvc di primavera. Quindi, non sono sicuro di come utilizzare una richiamata per richiamare di nuovo la stessa vista. Quel 'il tirato indietro. L'app è un'app Web ed è utilizzata come interfaccia utente. – JaJ

Problemi correlati