HTTP asynchrone avec async-http-client en Java

1. Vue d'ensemble

AsyncHttpClient (AHC) est une bibliothèque construite au-dessus de Netty, dans le but d'exécuter facilement les requêtes HTTP et de traiter les réponses de manière asynchrone.

Dans cet article, nous présenterons comment configurer et utiliser le client HTTP, comment exécuter une requête et traiter la réponse à l'aide d'AHC.

2. Configuration

La dernière version de la bibliothèque se trouve dans le référentiel Maven. Nous devons faire attention à utiliser la dépendance avec l'id de groupe org.asynchttpclient et non celle avec com.ning:

 org.asynchttpclient async-http-client 2.2.0 

3. Configuration du client HTTP

La méthode la plus simple pour obtenir le client HTTP consiste à utiliser la classe Dsl . La méthode statique asyncHttpClient () renvoie un objet AsyncHttpClient :

AsyncHttpClient client = Dsl.asyncHttpClient();

Si nous avons besoin d'une configuration personnalisée du client HTTP, nous pouvons créer l' objet AsyncHttpClient à l'aide du générateur DefaultAsyncHttpClientConfig.Builder :

DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config()

Cela offre la possibilité de configurer des délais d'expiration, un serveur proxy, des certificats HTTP et bien d'autres:

DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config() .setConnectTimeout(500) .setProxyServer(new ProxyServer(...)); AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);

Une fois que nous avons configuré et obtenu une instance du client HTTP, nous pouvons la réutiliser dans notre application . Nous n'avons pas besoin de créer une instance pour chaque requête, car en interne, cela crée de nouveaux threads et pools de connexions, ce qui entraînera des problèmes de performances.

De plus, il est important de noter qu'une fois que nous avons fini d'utiliser le client, nous devons appeler la méthode close () pour éviter toute fuite de mémoire ou blocage de ressources.

4. Création d'une requête HTTP

Il existe deux méthodes dans lesquelles nous pouvons définir une requête HTTP en utilisant AHC:

  • lié
  • non lié

Il n'y a pas de différence majeure entre les deux types de requêtes en termes de performances. Ils ne représentent que deux API distinctes que nous pouvons utiliser pour définir une demande. Une demande liée est liée au client HTTP à partir de laquelle elle a été créée et utilisera, par défaut, la configuration de ce client spécifique, sauf indication contraire.

Par exemple, lors de la création d'une demande liée, l' indicateur disableUrlEncoding est lu à partir de la configuration du client HTTP, tandis que pour une demande indépendante, il est défini par défaut sur false. Ceci est utile car la configuration du client peut être modifiée sans recompiler l'ensemble de l'application à l'aide des propriétés système transmises en tant qu'arguments de machine virtuelle:

java -jar -Dorg.asynchttpclient.disableUrlEncodingForBoundRequests=true

Une liste complète des propriétés se trouve dans le fichier ahc-default.properties .

4.1. Demande liée

Pour créer une requête liée, nous utilisons les méthodes d'assistance de la classe AsyncHttpClient qui commencent par le préfixe «prepare» . De plus, nous pouvons utiliser la méthode prepareRequest () qui reçoit un objet Request déjà créé .

Par exemple, la méthode prepareGet () créera une requête HTTP GET:

BoundRequestBuilder getRequest = client.prepareGet("//www.baeldung.com");

4.2. Demande non liée

Une demande indépendante peut être créée à l'aide de la classe RequestBuilder :

Request getRequest = new RequestBuilder(HttpConstants.Methods.GET) .setUrl("//www.baeldung.com") .build();

ou en utilisant la classe d'assistance Dsl , qui utilise en fait RequestBuilder pour configurer la méthode HTTP et l'URL de la requête:

Request getRequest = Dsl.get("//www.baeldung.com").build()

5. Exécution de requêtes HTTP

Le nom de la bibliothèque nous donne un indice sur la manière dont les requêtes peuvent être exécutées. AHC prend en charge les requêtes synchrones et asynchrones.

Executing the request depends on its type. When using a bound request we use the execute() method from the BoundRequestBuilder class and when we have an unbound request we'll execute it using one of the implementations of the executeRequest() method from the AsyncHttpClient interface.

5.1. Synchronously

The library was designed to be asynchronous, but when needed we can simulate synchronous calls by blocking on the Future object. Both execute() and executeRequest() methods return a ListenableFuture object. This class extends the Java Future interface, thus inheriting the get() method, which can be used to block the current thread until the HTTP request is completed and returns a response:

Future responseFuture = boundGetRequest.execute(); responseFuture.get();
Future responseFuture = client.executeRequest(unboundRequest); responseFuture.get();

Using synchronous calls is useful when trying to debug parts of our code, but it's not recommended to be used in a production environment where asynchronous executions lead to better performance and throughput.

5.2. Asynchronously

When we talk about asynchronous executions, we also talk about listeners for processing the results. The AHC library provides 3 types of listeners that can be used for asynchronous HTTP calls:

  • AsyncHandler
  • AsyncCompletionHandler
  • ListenableFuture listeners

The AsyncHandler listener offers the possibility to control and process the HTTP call before it has completed. Using it can handle a series of events related to the HTTP call:

request.execute(new AsyncHandler() { @Override public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception { return null; } @Override public State onHeadersReceived(HttpHeaders headers) throws Exception { return null; } @Override public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { return null; } @Override public void onThrowable(Throwable t) { } @Override public Object onCompleted() throws Exception { return null; } });

The State enum lets us control the processing of the HTTP request. By returning State.ABORT we can stop the processing at a specific moment and by using State.CONTINUE we let the processing finish.

It's important to mention that the AsyncHandler isn't thread-safe and shouldn't be reused when executing concurrent requests.

AsyncCompletionHandler inherits all the methods from the AsyncHandler interface and adds the onCompleted(Response) helper method for handling the call completion. All the other listener methods are overridden to return State.CONTINUE, thus making the code more readable:

request.execute(new AsyncCompletionHandler() { @Override public Object onCompleted(Response response) throws Exception { return response; } });

The ListenableFuture interface lets us add listeners that will run when the HTTP call is completed.

Also, it let's execute the code from the listeners – by using another thread pool:

ListenableFuture listenableFuture = client .executeRequest(unboundRequest); listenableFuture.addListener(() -> { Response response = listenableFuture.get(); LOG.debug(response.getStatusCode()); }, Executors.newCachedThreadPool());

De plus, la possibilité d'ajouter des écouteurs, l' interface ListenableFuture nous permet de transformer la réponse Future en CompletableFuture .

7. Conclusion

AHC est une bibliothèque très puissante, avec de nombreuses fonctionnalités intéressantes. Il offre un moyen très simple de configurer un client HTTP et la capacité d'exécuter des requêtes synchrones et asynchrones.

Comme toujours, le code source de l'article est disponible à l'adresse over sur GitHub.