5 façons d'effectuer des requêtes HTTP dans Java

July 21, 2020
Rédigé par
Révisé par
Diane Phan
Twilion

5 façons d'effectuer des requêtes HTTP dans Java

La possibilité d'effectuer des requêtes HTTP est l'une des caractéristiques essentielles de la programmation moderne, et souvent l'une des premières choses que l'on veut faire quand on apprend un nouveau langage de programmation. Les programmeurs Java disposent pour ce faire de nombreuses options, que ce soit via les bibliothèques de base du JDK ou des bibliothèques tierces. Cet article vous présentera les clients HTTP Java auxquels je fais appel. Si toutefois vous en utilisez d'autres, c'est très bien, n'hésitez pas à me dire lesquels. J'aborderai dans cet article :

API JAVA DE BASE :

  • HttpURLConnection
  • HttpClient

BIBLIOTHÈQUES POPULAIRES :

  • ApacheHttpClient
  • OkHttp
  • Retrofit

J'utiliserai l'API Astonomy Picture of the Day issue des API de la NASA pour les exemples de code, lequel est consultable dans son intégralité sur GitHub dans un projet basé sur Java 11.

API Java de base pour effectuer des requêtes HTTP Java

S'il existe depuis Java 1.1 un client HTTP dans les bibliothèques de base fournies avec le JDK, Java 11 a vu pour sa part l'ajout d'un nouveau client. L'un ou l'autre de ces clients sera un bon choix si vous êtes sensible à l'ajout de dépendances supplémentaires à votre projet.

HttpURLConnection de Java 1.1

Tout d'abord, faut-il ou non mettre une majuscule aux acronymes dans les noms de classe ? Vous allez devoir prendre une décision. Quoi qu'il en soit, fermez les yeux et imaginez que vous êtes en 1997. Titanic cartonne au box office et inspire des milliers de mèmes, les Spice Girls sont en tête des ventes d'albums, mais la plus grande nouvelle de l'année, c'est sans doute l'ajout de HttpURLConnection à Java 1.1.  Voilà comment l'utiliser pour effectuer une requête GET afin d'obtenir les données depuis le site APOD :

// Create a neat value object to hold the URL
URL url = new URL("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

// Open a connection(?) on the URL(??) and cast the response(???)
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Now it's "open", we can set the request method, headers etc.
connection.setRequestProperty("accept", "application/json");

// This line makes the request
InputStream responseStream = connection.getInputStream();

// Manually converting the response body InputStream to APOD using Jackson
ObjectMapper mapper = new ObjectMapper();
APOD apod = mapper.readValue(responseStream, APOD.class);

// Finally we have the response
System.out.println(apod.title);

[code complet sur GitHub]

Cela me paraît plutôt verbeux, et je trouve que l'ordre dans lequel il faut faire les choses est déroutant (pourquoi définir les en-têtes après avoir ouvert l'URL ?). Si vous avez besoin d'effectuer des requêtes plus complexes avec des corps POST, ou de personnaliser les délais d'expiration ou autres, c'est possible, mais je n'ai vraiment jamais trouvé cette API intuitive.

Alors, dans quels cas utiliser HTTPUrlConnection ? Si vous prenez en charge des clients utilisant des versions antérieures de Java et que vous ne pouvez pas ajouter de dépendance, il s'agit peut-être là de votre meilleure solution. Je suppose que cela ne concernera qu'une faible minorité de développeurs, mais il se peut que vous le rencontriez dans des bases de code plus anciennes. Pour les approches plus modernes, lisez la suite.

HttpClient de Java 11

Plus de vingt ans après HttpURLConnection, et alors que Black Panther était sur les écrans, un nouveau client HTTP a été ajouté à Java 11 : java.net.http.HttpClient. Disposant d'une API beaucoup plus logique, il est capable de gérer le protocole HTTP/2 et les WebSockets. Il permet également d'effectuer des requêtes de manière synchrone ou asynchrone à l'aide de l'API CompletableFuture.

Dans 99 % des cas, quand j'effectue une requête HTTP, je veux pouvoir lire le corps de la réponse dans mon code. Toute bibliothèque qui va rendre cette tâche complexe ne peut faire mon bonheur. HttpClient accepte un BodyHandler permettant de convertir une réponse HTTP en une classe de votre choix. Il existe quelques gestionnaires intégrés : Stringbyte[] pour les données binaires, Stream<String> qui divise par lignes, ainsi que quelques autres. Vous pouvez aussi en définir vous-même, ce qui peut être utile dans la mesure où il n'existe pas de BodyHandler intégré pour analyser le JSON. J'en ai écrit un (ici) basé sur Jackson en suivant un exemple de Java Docs. Il renvoie un Supplier pour la classe APOD, ce qui permet d'appeler .get() quand on a besoin du résultat.

Il s'agit d'une requête synchrone :

// create a client
var client = HttpClient.newHttpClient();

// create a request
var request = HttpRequest.newBuilder(
       URI.create("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"))
   .header("accept", "application/json")
   .build();

// use the client to send the request
var response = client.send(request, new JsonBodyHandler<>(APOD.class));

// the response:
System.out.println(response.body().get().title);

Pour une requête asynchrone, le client et la request sont effectués de la même manière, et il suffit ensuite d'appeler .sendAsync au lieu de .send :

// use the client to send the request
var responseFuture = client.sendAsync(request, new JsonBodyHandler<>(APOD.class));

// We can do other things here while the request is in-flight

// This blocks until the request is complete
var response = responseFuture.get();

// the response:
System.out.println(response.body().get().title);

[code complet sur GitHub]

Bibliothèques client HTTP Java tierces

Si les clients intégrés ne fonctionnent pas pour vous, ne vous inquiétez pas ! Il existe de nombreuses bibliothèques pouvant être incluses dans votre projet, qui feront parfaitement l'affaire.

Apache HttpClient

Les clients HTTP de l'Apache Software Foundation existent depuis un bon moment. Ils sont largement utilisés et sont à la base d'un grand nombre de bibliothèques de haut niveau. L'histoire est un peu déroutante. L'ancien client Commons HttpClient n'est plus développé, et la nouvelle version (qui s'appelle aussi HttpClient) fait partie du projet HttpComponents. La version 5.0 de la bibliothèque, publiée début 2020, ajoute la prise en charge du protocole HTTP/2. Elle prend également en charge les requêtes synchrones et asynchrones.

Dans l'ensemble, l'API est plutôt de bas niveau ; il faut implémenter une grande partie par soi-même. Le code ci-dessous appelle l'API de la NASA. Il ne semble pas trop difficile à utiliser, mais j'ai ignoré une grande partie de la gestion des erreurs que l'on voudrait voir dans du code mis en production, et j'ai dû là aussi ajouter du code Jackson afin d'analyser la réponse JSON. On pourrait aussi être tenté de configurer un framework de journalisation afin d'éviter les avertissements sur le flux de sortie standard (pas un problème en soi, mais c'est juste un peu agaçant). Quoi qu'il en soit, voici le code :

ObjectMapper mapper = new ObjectMapper();

try (CloseableHttpClient client = HttpClients.createDefault()) {

   HttpGet request = new HttpGet("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

   APOD response = client.execute(request, httpResponse ->
       mapper.readValue(httpResponse.getEntity().getContent(), APOD.class));

   System.out.println(response.title);
}

[code complet sur GitHub]

Apache fournit plusieurs autres exemples pour ce qui est des requêtes synchrones et asynchrones.

OkHttp

OkHttp est un client HTTP de Square offrant de nombreuses fonctions intégrées très utiles, telles que la gestion automatique du format GZIP, la mise en cache des réponses et les nouvelles tentatives ou le repli vers d'autres hôtes en cas d'erreurs réseau, ainsi que la prise en charge du protocole HTTP/2 et des WebSockets. L'API est propre, bien qu'elle ne propose pas d'analyse intégrée des réponses JSON. J'ai donc là encore ajouté du code pour analyser le JSON avec Jackson :

ObjectMapper mapper = new ObjectMapper();

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
   .url("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")
   .build(); // defaults to GET

Response response = client.newCall(request).execute();

APOD apod = mapper.readValue(response.body().byteStream(), APOD.class);

System.out.println(apod.title);

[code complet sur GitHub]

C'est déjà bien, mais OkHttp ne révèle vraiment son plein potentiel que lorsqu'on y ajoute Retrofit.

Retrofit

Retrofit est une autre bibliothèque de Square, construite sur la base d'OkHttp. En plus de toutes les fonctionnalités de bas niveau d'OkHttp, elle ajoute un moyen de construire des classes Java capables d'extraire les détails HTTP et de présenter une API conviviale et compatible avec Java.

Tout d'abord, nous devons créer une interface déclarant les méthodes que nous voulons appeler par rapport à l'API APOD, avec des annotations définissant la manière dont celles-ci correspondent aux requêtes HTTP :

public interface APODClient {
   @GET("/planetary/apod")
   @Headers("accept: application/json")
   CompletableFuture<APOD> getApod(@Query("api_key") String apiKey);
}

Le type de retour qu'offre CompletableFuture<APOD> en fait un client asynchrone. Square fournit d'autres adaptateurs, mais vous pouvez aussi écrire les vôtres. Disposer d'une telle interface aide à simuler le client en vue des tests, ce que je trouve appréciable.

Après avoir déclaré l'interface, nous demandons à Retrofit de créer une implémentation pouvant être utilisée pour envoyer des requêtes vers une URL de base donnée. Pouvoir changer l'URL de base est également utile dans le cadre des tests d'intégration. Pour générer le client, le code se présente comme suit :

Retrofit retrofit = new Retrofit.Builder()
   .baseUrl("https://api.nasa.gov")
   .addConverterFactory(JacksonConverterFactory.create())
   .build();

APODClient apodClient = retrofit.create(APODClient.class);

CompletableFuture<APOD> response = apodClient.getApod("DEMO_KEY");

// do other stuff here while the request is in-flight

APOD apod = response.get();

System.out.println(apod.title);

[code complet sur GitHub]

Authentification de l'API

S'il existe dans notre interface plusieurs méthodes nécessitant toutes une clé API, il est possible de configurer cela en ajoutant un HttpInterceptor à la base OkHttpClient. Le client personnalisé peut être ajouté au Retrofit.Builder.  Le code nécessaire pour créer le client personnalisé est :

private OkHttpClient clientWithApiKey(String apiKey) {
   return new OkHttpClient.Builder()
           .addInterceptor(chain -> {
               Request originalRequest = chain.request();
               HttpUrl newUrl = originalRequest.url().newBuilder()
                   .addQueryParameter("api_key", apiKey).build();
               Request request = originalRequest.newBuilder().url(newUrl).build();
               return chain.proceed(request);
           }).build();
}

[code complet sur GitHub]

J'aime ce type d'API Java pour tous les cas, sauf les plus simples. La création de classes pour représenter les API distantes est une bonne méthode d'abstraction qui fonctionne bien avec l'injection de dépendances, et faire en sorte que Retrofit les crée pour vous à partir d'un client OkHttp personnalisable est remarquable.

Autres clients HTTP pour Java

Depuis la publication de cet article sur Twitter, j'ai été ravi de voir les gens discuter des clients HTTP qu'ils utilisent. Si aucune des propositions ci-dessus ne correspond exactement à ce que vous recherchez, jetez un coup d'œil à ces suggestions :

  • REST Assured : un client HTTP conçu pour tester vos services REST. Il offre une interface fluide pour l'envoi des requêtes, ainsi que des méthodes utiles permettant de faire des affirmations sur les réponses.
  • cvurl : une classe wrapper pour le HttpClient de Java 11, qui résout certaines des difficultés que vous pourriez rencontrer dans le cadre de requêtes complexes.
  • Feign : de la même façon que Retrofit, Feign permet de créer des classes à partir d'interfaces annotées. Extrêmement flexible, il offre de nombreuses options pour effectuer et lire des requêtes, des indicateurs, de nouvelles tentatives, etc.
  • Clients Spring RestTemplate (synchrone) et WebClient (asynchrone) : si vous avez utilisé Spring pour tout le reste de votre projet, il pourrait être judicieux de rester fidèle à cet écosystème. Baeldung propose un article les comparant.
  • MicroProfile Rest client : un autre client fonctionnant sur la création d'une classe à partir d'une interface annotée. Son intérêt est qu'il permet de réutiliser la même interface pour créer aussi un serveur Web, en étant sûr que le client et le serveur correspondent. Si vous avez besoin de créer les deux pour ce service, c'est sans doute celui qu'il vous faut.

Résumé

Le choix est vaste en matière de clients HTTP en Java. Pour les cas simples, je recommanderais le client java.net.http.HttpClient intégré. Pour les cas d'usage plus complexes, ou si vous souhaitez que vos API HTTP soient abstraites en tant que classes Java dans le cadre d'une application plus vaste, envisagez plutôt Retrofit ou Feign. Codez bien, j'ai hâte de voir ce que vous allez construire !

 mgilliard@twilio.com

 @MaximumGilliard