Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

HTTP/2: richieste sincrone e asincrone

Come gestire le richieste sincrone e asincrone tramite il supporto ad HTTP/2 di Java.
Come gestire le richieste sincrone e asincrone tramite il supporto ad HTTP/2 di Java.
Link copiato negli appunti

Fino ad ora abbiamo realizzato richieste HTTP utilizzando il metodo send() che prevede una modalità di tipo sincrono. Questo significa che il metodo si blocca in attesa
di una risposta. Scriviamo un esempio di codice per una richiesta sincrona con l'intento di una maggiore chiarezza.

HttpResponse<String> response = HttpClient.newBuilder()
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

Nella richiesta di esempio la successiva istruzione del flusso di codice verrà eseguita solo dopo la restituzione di un oggetto HTTP. In alternativa possiamo utilizzare il metodo sendAsync() al posto di send() per convertire la richiesta ad una modalità asincrona:

CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder()
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

In questo caso otteniamo un oggetto CompletableFeature<HttpResponse> da utilizzare per la gestione del processo asincrono. L'esecuzione del programma continuerà senza alcun blocco.

Vediamo ora come utilizzare la classe CompletableFeature per gestire l'invio asincrono di richieste HTTP ed il loro processo. Supponiamo di voler inviare due richieste asincrone utilizzando il server postman per delle risposte echo-server:

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;
....
List<URI> requests = Arrays.asList(
  new URI("https://postman-echo.com/get?param1=1"),
  new URI("https://postman-echo.com/get?param2=2"));
HttpClient client = HttpClient.newHttpClient();
List<CompletableFuture<String>> completableFutureList = requests.stream()
  .map(request -> client
    .sendAsync(
      HttpRequest.newBuilder(request).GET().build(),
      HttpResponse.BodyHandler.asString())
    .thenApply(response -> response.body()))
  .collect(Collectors.toList());

Una volta che ciascuna richiesta è stata inviata, il Thread principale non rimane bloccato sulla risposta ma prosegue la sua esecuzione. Possiamo quindi affidare il processo delle risposte ad un ulteriore Thread attraverso l'utilizzo di un Executor:

ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(() -> {
            CompletableFuture.allOf(completableFutureList.
                    toArray(new CompletableFuture<?>[completableFutureList.size()])).join();
            completableFutureList.stream().forEach(future -> {
                System.out.println("Async response: " + future.getNow(null));
            });
        });

Il metodo statico allOf() riceve l'array di oggetti CompletableFuture relativi alle richieste HTTP che abbiamo effettuato in precedenza. Viene quindi sfruttato il metodo join() per attendere la ricezione di tutte le risposte HTTP. Una volta ottenute, la successiva istruzione viene eseguita e, attraverso uno stream ottenuto dalla lista iniziale, si estrae il contenuto di ogni singola risposta ottenuta dal server.

Non rimane che completare questa semplice implementazione con una corretta chiusura dell'Executor:

try {
            System.out.println("Tentativo shutdown executor");
            executor.shutdown();
            executor.awaitTermination(100, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            System.err.println(e.getMessage());
        } finally {
            if (!executor.isTerminated()) {
                System.err.println("Shutdown normale non riuscito nel tempo stabilito");
                System.err.println("Invocazione shutdownNow()");
            }
            executor.shutdownNow();
            System.out.println("Shutdown completato");
        }

Siamo pronti per vedere in azione il risultato del codice presentato. Inseriamo il tutto all'interno del metodo main() di una classe di test ed eseguiamo il programma. Dovremmo ottenere in console una stampa del tipo:

Tentativo shutdown executor

Async response: {"args":{"param1":"1"},"headers":{"host":"postman-echo.com","x-forwarded-port":"443","x-forwarded-proto":"https"},"url":"https://postman-echo.com/get?param1=1"}
Async response: {"args":{"param2":"2"},"headers":{"host":"postman-echo.com","x-forwarded-port":"443","x-forwarded-proto":"https"},"url":"https://postman-echo.com/get?param2=2"}
Shutdown completato

La modalità asincrona è particolarmente utile quando dobbiamo attendere risposte che impiegano un tempo T non trascurabile, essa consente di continuare l'esecuzione di un programma impiegando utilmente il tempo di attesa.

Ti consigliamo anche