Aller au contenu

Requêtes Http avec Rest Client dans le Spring Framework 6

Spring Restclient HTTP Rest Openai
Auteur
Harpal Singh
Software Engineer
Traduit par
Namastecode
Sommaire

1. Introduction

Dans ce tutoriel, nous allons apprendre à effectuer des requêtes HTTP en utilisant le client HTTP RestClient introduit dans Spring 6.1 (M2).

Tout d’abord, nous allons comprendre l’API RestClient. Ensuite, nous verrons comment effectuer des requêtes GET, POST, PUT, et DELETE en utilisant l’API RestClient. Enfin, nous examinerons un exemple pratique de requêtes HTTP vers l’API OpenAI et nous appellerons ChatGPT pour générer du texte pour nous.

2. Comprendre RestClient

Il y a quelque temps, le framework Spring a introduit le WebClient pour effectuer des requêtes HTTP non bloquantes. WebClient utilise un paradigme réactif et convient aux applications qui fonctionnent bien en mode asynchrone et dans des scénarios de streaming. Par conséquent, l’utiliser dans une application non réactive ajoute une complexité inutile et nécessite que nous comprenions très bien les types Mono et Flux. De plus, WebClient ne rend pas le programme plus performant, car nous devons de toute façon attendre la réponse.

Donc, le framework Spring a introduit l’API RestClient pour effectuer des requêtes HTTP synchrones. Une approche plus moderne par rapport au classique RestTemplate. Cela ne signifie pas que RestTemplate est obsolète. En fait, en coulisse, ils utilisent tous deux la même infrastructure, comme les convertisseurs de messages, les usines de requêtes, etc. Cependant, il est judicieux de s’en tenir à l’API RestClient car elle sera au centre des futures itérations du framework Spring et bénéficiera de plus de fonctionnalités.

RestClient utilise des bibliothèques HTTP telles que JDK HttpClient, Apache HttpComponents, et d’autres. Il utilise la bibliothèque disponible dans le classpath. Ainsi, RestClient abstrait tous les détails du client HTTP et utilise celui qui est le plus approprié, en fonction des dépendances que nous avons dans notre projet.

3. Créer RestClient

Nous créons RestClient en utilisant la méthode create(), qui retourne une nouvelle instance avec les paramètres par défaut :

RestClient simpleRestClient = RestClient.create();

Si nous voulons personnaliser le RestClient, nous pouvons utiliser la méthode builder() et employer l’API fluide pour définir les configurations dont nous avons besoin :

RestClient customRestClient = RestClient.builder()
 .requestFactory(new HttpComponentsClientHttpRequestFactory())
 .messageConverters(converters -> converters.add(new MappingJackson2HttpMessageConverter()))
 .baseUrl("https://example.com")
 .defaultHeader("Authorization", "Bearer your-token")
 .defaultHeader("Content-Type", "application/json")
 .build();

Notre RestClient personnalisé utilise le client Apache pour effectuer des requêtes HTTP. Ensuite, nous ajoutons un convertisseur de messages personnalisé pour gérer la sérialisation et la désérialisation JSON en utilisant Jackson. Nous définissons l’URL de base, les en-têtes par défaut et le type de contenu pour toutes les requêtes effectuées avec ce client.

Gardons à l’esprit que la plupart de ces configurations sont déjà configurées par défaut. Maintenant, nous avons un RestClient personnalisé que nous pouvons utiliser pour effectuer des requêtes HTTP. Nous pouvons réutiliser cette instance de RestClient dans toute notre application sans problème car elle est sécurisée pour les threads.

Nous pouvons également migrer vers RestClient à partir de RestTemplate progressivement et réutiliser les configurations existantes pour créer un RestClient :

RestTemplate restTemplate = new RestTemplate();
RestClient restClientFromRestTemplate = RestClient.create(restTemplate);

4. Effectuer des requêtes HTTP

Dans cette section, nous allons expliquer brièvement comment effectuer des requêtes GET, POST, PUT et DELETE en utilisant l’API RestClient. Nous utiliserons une fausse API appelée awesomeapi.com pour démontrer les requêtes. Pour simplifier, nous utiliserons la méthode retrieve() pour effectuer les requêtes et obtenir la réponse sous forme de String.

4.1. Effectuer une requête GET

Le type de requête HTTP le plus courant est la requête GET, que nous utilisons pour obtenir des données d’un serveur. Le RestClient offre un moyen simple d’effectuer des requêtes GET avec diverses options pour gérer les paramètres et les types de réponses :

String response = simpleRestClient.get()
 .uri("https://awesomeapi.com/string/")
 .retrieve()
 .body(String.class);

record Item(String id, String name, double price) {    
} // JSON: {"id": 123, "name": "Product", "price": 99.99}

Item item = simpleRestClient.get()
 .uri("https://awesomeapi.com/item/{id}", 123)
 .retrieve()
 .body(Item.class);

Dans cet extrait, nous effectuons deux requêtes GET. La première obtient simplement une réponse brute sous forme de String. La seconde reçoit une réponse JSON d’un objet Item avec les champs name et price. La méthode body() désérialise automatiquement la réponse en une instance valide de Item en utilisant Jackson en arrière-plan. Ainsi, si la réponse diffère de la structure de Item, elle lance une exception.

4.2. Effectuer une requête POST

Pour envoyer des données au serveur, nous utilisons la requête POST. Plus formellement, les requêtes POST créent de nouvelles ressources sur le serveur. Comme nous l’avons vu avec les requêtes GET, le RestClient facilite l’envoi d’objets encodés en JSON comme corps de la requête :

record ItemRequest(String name, double price) {
} // JSON: {"name": "Product", "price": 99.99}

ItemRequest newItem = new ItemRequest("Product", 99.99);

Item createdItem = simpleRestClient.post()
 .uri("https://awesomeapi.com/item/")
 .contentType(MediaType.APPLICATION_JSON)
 .body(newItem)
 .retrieve()
 .body(Item.class);

Dans cet exemple, nous créons un nouvel objet ItemRequest et l’envoyons au serveur. La méthode contentType() spécifie que nous envoyons des données JSON, et la méthode body() sérialise automatiquement l’objet Item en un JSON valide. En général, le serveur répond avec la ressource qu’il a créée, que nous pouvons désérialiser en un objet Item. La différence entre Item et ItemRequest est que Item possède un champ id généré par le serveur.

4.3. Effectuer une requête PUT

Disons que nous souhaitons mettre à jour une ressource existante sur le serveur. Nous pouvons utiliser la requête PUT à cette fin. Tout comme les requêtes POST, les requêtes PUT incluent un corps de requête de la ressource que nous voulons mettre à jour :

ItemRequest updatedItem = new ItemRequest("Updated Product", 129.99);

Item updated = simpleRestClient.put()
 .uri("https://awesomeapi.com/item/{id}", 123)
 .contentType(MediaType.APPLICATION_JSON)
 .body(updatedItem)
 .retrieve()
 .body(Item.class);

L’exemple de requête PUT montre comment mettre à jour un produit existant. Nous spécifions l’identifiant du produit dans la méthode uri() et fournissons les données mises à jour du produit dans le corps de la requête. La réponse contient les informations du produit mises à jour telles que confirmées par le serveur.

Notez que la méthode uri() utilise la syntaxe des variables de chemin, un moyen pratique de spécifier des parties dynamiques de l’URI. Certaines APIs utilisent des paramètres de requête au lieu de variables de chemin. Pour ajouter des paramètres de requête, nous pouvons utiliser le UriBuilder et définir le chemin, les paramètres en utilisant la méthode queryParam(), et enfin construire l’Uri à passer à la méthode uri() :    

Item updatedWithQuery = simpleRestClient.put()
 .uri(uriBuilder ->
            uriBuilder.path("https://awesomeapi.com/item/")
 .queryParam("id", 123)
 .build())
 .contentType(MediaType.APPLICATION_JSON)
 .body(updatedItem)
 .retrieve()
 .body(Item.class);

Les paramètres de requête sont ajoutés à l’URI après le caractère ?. Ainsi, l’uri résultant sera https://awesomeapi.com/item/?id=123.

4.4. Effectuer une requête DELETE

Enfin, examinons comment supprimer une ressource du serveur en utilisant la requête DELETE :

simpleRestClient.delete()
 .uri("https://awesomeapi.com/item/{id}", 123)
 .retrieve()
 .toBodilessEntity();

Dans l’exemple DELETE, nous utilisons toBodilessEntity() car les requêtes DELETE ne retournent généralement pas de corps de réponse. La syntaxe de la variable de chemin spécifie quelle ressource supprimer.

Si la suppression est réussie, aucune exception n’est levée. Cela est valable pour toutes les requêtes ; si le serveur répond avec un code d’erreur, une exception sera levée. Nous pouvons utiliser des blocs try-catch pour gérer ces exceptions, mais il existe des moyens plus rationnels de gérer ces erreurs. Nous verrons cela dans la section suivante.

5. Gérer les erreurs avec onStatus()

Lorsqu’on travaille avec des APIs REST, il est nécessaire de gérer les erreurs de manière élégante. Nous ne pouvons pas utiliser des blocs try-catch pour chaque requête, car cela entraîne un code répétitif et illisible. Le RestClient offre une manière très claire de gérer les erreurs en fonction du code de statut de la réponse. Nous utilisons la méthode onStatus() pour traiter différents codes de statut et offrir une façon plus lisible de gérer les échecs :

String id = "123";
item = simpleRestClient.get()
 .uri("https://awesomeapi.com/item/{id}", id)
 .retrieve()
 .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
        throw new RuntimeException("Item not found with id: " + id);
 })
 .onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
        throw new RuntimeException("Service is currently unavailable");
 })
 .body(Item.class);

Dans cet exemple, nous effectuons une requête GET pour récupérer un objet Item avec un identifiant spécifique. Si le serveur répond avec une erreur client 4xx, nous lançons une exception avec un message indiquant que l’élément n’a pas été trouvé sur le serveur. De même, si nous obtenons une erreur serveur 5xx, nous lançons une autre RuntimeException indiquant que le service est indisponible. Cela montre comment différencier les différents types d’erreurs ; dans des scénarios réels, nous devons mettre en œuvre la logique de gestion des erreurs en fonction des exigences. Par exemple, nous pourrions avoir des erreurs temporaires qui nécessitent une logique de reprise.

6. Comprendre retrieve() vs exchange()

La méthode retrieve() simplifie les requêtes HTTP tout en couvrant la plupart des cas d’utilisation. Cependant, parfois, nous avons besoin de plus de contrôle sur la requête et la réponse HTTP. Par exemple, nous pourrions avoir besoin de lire les en-têtes de réponse qui ne s’intègrent pas dans la méthode onStatus(). Dans de tels cas, nous pouvons utiliser la méthode exchange(). Soyez conscient que l’utilisation de exchange() ne fera pas appel aux gestionnaires de statut, car elle renvoie déjà la réponse complète, nous permettant d’effectuer tout traitement des erreurs nécessaire.

Par exemple, nous pouvons implémenter le même traitement des erreurs 404 réalisé dans la section précédente en utilisant exchange() :

Item itemResponse = simpleRestClient.get()
 .uri("https://awesomeapi.com/item/{id}", id)
 .exchange((request, response) -> {

        HttpHeaders headers = response.getHeaders();
        String etag = headers.getETag();

        if (response.getStatusCode().is4xxClientError()) {
            throw new RuntimeException("Item not found with id: " + id);
 } else if(response.getStatusCode().is5xxServerError()){
            throw new RuntimeException("Service is currently unavailable");
 }

        log.info("Got request with ETAG: " + etag);
        return response.bodyTo(Item.class);
 });

Nous avons manuellement créé la logique de gestion des erreurs en utilisant des instructions if-else dans cet exemple. L’utilisation de gestionnaires réduit la lisibilité du code mais nous permet d’accéder aux en-têtes de la réponse, comme l’ETag, ce qui est autrement impossible avec la méthode retrieve().

Essayons un exemple plus pratique d’appel à une véritable API.

7. Effectuer des appels HTTP à l’API OpenAI

L’API Rest est un excellent moyen d’interagir avec un service tiers. Dans cette section, nous allons utiliser l’API OpenAI pour générer du texte en utilisant le modèle ChatGPT. Nous utiliserons le RestClient pour effectuer une requête POST à l’API OpenAI et obtenir la réponse.

Tout d’abord, nous devons obtenir une clé API depuis le site web d’OpenAI. Il est conseillé de ne pas inclure la clé API dans le code source et d’utiliser des variables d’environnement ou un moyen sécurisé pour la stocker. Pour cet exemple, nous supposerons que nous avons une variable d’environnement nommée OPENAI_API_KEY qui contient la clé API. Pour la lire, nous utilisons simplement la méthode getenv() de la classe System.

String apiKey = System.getenv("OPENAI_API_KEY");

Ensuite, nous créons quelques classes d’assistance record pour représenter les structures de requête et de réponse requises par l’API OpenAI :


record ChatRequest(String model, List<Message> messages) {} 
record Message(String role, String content) {}
record ChatResponse(List<Choice> choices) {}
record Choice(Message message) {}

Maintenant, nous créons une instance de RestClient avec les en-têtes nécessaires pour l’authentification et le type de contenu :

RestClient openAiClient = RestClient.builder()
 .baseUrl("https://api.openai.com/v1")
 .defaultHeader("Authorization", "Bearer " + apiKey)
 .defaultHeader("Content-Type", "application/json")
 .build();

Enfin, nous effectuons une requête POST à l’API OpenAI pour générer du texte en utilisant le modèle ChatGPT :

ChatRequest request = new ChatRequest(
    "gpt-4o-mini",
    List.of(new Message("user", "What is the brightest star in our galaxy?"))
);

ChatResponse response = openAiClient.post()
 .uri("/chat/completions")
 .body(request)
 .retrieve()
 .body(ChatResponse.class);

log.info("ChatGPT: " + response.choices().get(0).message().content());

Si tout se passe bien, nous verrons la réponse de ChatGPT :

INFO: ChatGPT: The brightest star in our galaxy, the Milky Way, is typically considered to be **Sagittarius A***. However, strictly speaking, Sagittarius A* is actually the supermassive black hole at the center of the Milky Way. The title of the brightest visible star from Earth is generally given to **Sirius**, which is part of the constellation Canis Major. Sirius is approximately 8.6 light-years away and is about twice as massive as the Sun. It is often referred to as the "Dog Star."

Cet exemple montre comment utiliser le RestClient pour interagir avec une API du monde réel comme celle fournie par OpenAI afin de gérer des structures JSON complexes et des exigences d’authentification de manière propre et sûre par type.

8. Conclusion

Dans cet article, nous avons découvert le nouveau RestClient introduit dans la nouvelle API RestClient de Spring Framework 6.1 pour effectuer des requêtes HTTP. Nous avons appris à créer et configurer un RestClient, à effectuer des requêtes HTTP (GET, POST, PUT, DELETE), à gérer les erreurs, et à travailler avec des APIs du monde réel comme OpenAI.

Nous pouvons consulter le code complet sur Github.

Articles connexes

Comprendre la syntaxe Cron de @Scheduled de Spring
Spring Cron Scheduling
Testcontainers dans les tests d'intégration Spring Boot
Spring Testcontainers Testing
Obtenir des valeurs définies dans le fichier de propriétés dans Spring
Spring Properties Basics