web-dev-qa-db-ja.com

パラレルGETリクエストを送信し、結果の応答を待つ方法は?

図のように、5つのget要求を同期的に送信するために、Spring mvc 3.2.2内でApache httpクライアントを使用しています。

すべての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

通常、作業単位をRunnableまたはJava.util.concurrent.Callableにカプセル化し、Java.util.concurrent.Executor(またはorg.springframework.core.task.TaskExecutor)を介して実行する必要があります。これにより、各作業単位を通常は非同期的に(Executorの実装に応じて)個別に実行できます。

したがって、特定の問題については、次のようなことができます。

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

このコードはテストされていないことに注意してください。

Executor実装については、 SpringのTaskExecutor および task:executor名前空間 を確認してください。 (毎回新しいスレッドを作成するのではなく)このユースケースのために再利用可能なスレッドのプールが必要になるでしょう。

13
superEb

AsyncHttpClientを使用する必要があります。任意の数の要求を行うことができ、応答を取得するとコールバックします。作成できる接続の数を構成できます。すべてのスレッドはライブラリによって処理されるため、スレッドを自分で管理するよりも簡単です。

ここの例を見てください: https://github.com/AsyncHttpClient/async-http-client

13
Alper Akture

単一のHttpClientインスタンスで複数のリクエストを並列実行する場合。

並列実行用にPoolingHttpClientConnectionManagerを構成します。

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

リクエストコードを別のメソッドに移動します。

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

そして、それらを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();

または

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

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

または、 で説明されているようにRestTemplateを使用しますSpring WebClientを使用して複数の呼び出しを同時に行う方法

1
Justas