1. Algorithme d’analyse d’un appel HTTP
À ce stade, on voit déjà quatre sources différentes de douleur : la requête peut ne pas être autosuffisante, l’opération peut modifier la ressource, la réponse peut être absente, et la réponse reçue n’est pas encore obligée de correspondre au contrat. Il est maintenant utile de rassembler ces morceaux dans un ordre d’analyse unique, afin de ne pas diagnostiquer HTTP par intuition et de ne pas réparer le mauvais problème. L’outil pourra ensuite cacher une partie du code répétitif, mais les causes de panne se répartissent toujours selon les mêmes questions.
Quand quelque chose ne va pas, le cerveau d’un débutant fait généralement un bond héroïque au milieu de l’histoire : « Oh ! Dans le body, il y a une chaîne bizarre », « Oh ! J’ai reçu un 404 – le serveur est mort », « Oh ! Une exception – ça doit être un bug dans la logique métier ». Le problème, c’est que ce n’est pas de l’analyse, mais de la divination, juste avec des termes plus beaux.
La pensée backend aime l’ordre, car l’ordre fait gagner du temps. Si vous analysez les problèmes avec le même schéma à chaque fois, vous trouverez la cause plus vite, vous vous plaindrez moins du fournisseur (parfois à tort), et vous « réparerez » moins souvent ce qui n’est pas en cause. C’est particulièrement important pour ReadLater Starter : dès que l’application accède à un catalogue externe de livres, il faut distinguer « le service a répondu avec une erreur » de « nous n’avons pas reçu de réponse » et de « la réponse est arrivée, mais ce n’est pas ce que le contrat promettait ».
En bref : l’algorithme n’est pas nécessaire parce que vous n’êtes pas intelligents, mais parce que vous êtes des êtres humains. Et les êtres humains ont besoin de check-lists, sinon ils commencent à déboguer en lisant dans les astres.
Trois questions : réponse, statut, contrat
Il est utile d’avoir en tête trois questions à poser strictement dans l’ordre. Cet ordre est plus important qu’il n’y paraît : il protège contre l’erreur la plus fréquente – « on a d’abord regardé le body, puis on a découvert qu’il n’y avait pas de réponse du tout ». Le schéma est simple, presque comme « vérifier si l’ordinateur est branché », mais en HTTP.
La première question : y a-t-il eu une réponse ? S’il n’y a pas eu de réponse, vous ne pouvez pas analyser le code de statut par définition – il n’existe pas. Deuxième question : s’il y a une réponse, quel est le statut ? Le statut est un indicateur rapide du sens : succès, erreur de requête, ressource absente, conflit, erreur interne. Troisième question : si le statut est un succès, la réponse respecte-t-elle le contrat ? Car 200 OK n’est pas encore une garantie que vous avez reçu ce qui a été convenu.
Voici un arbre de décision très simple :
flowchart TD
A["Appel HTTP problématique"] --> B{Réponse obtenue ?}
B -- Non --> C["Panne réseau / pas de réponse"]
B -- Oui --> D{"Code de statut"}
D -- 4xx/5xx --> E["Réponse d’erreur reçue"]
D -- 2xx --> F{Contrat respecté ?}
F -- Non --> G["Violation du contrat"]
F -- Oui --> H["Succès"]
Remarquez un point psychologique important : le 404 se trouve dans la branche « réponse obtenue ». Cela signifie que le serveur est vivant, le réseau a acheminé la requête et a renvoyé une réponse. C’est déjà une information utile, et elle est radicalement différente de la situation « pas de réponse du tout ».
2. Étape 1 — Vérifier l’existence d’une réponse
L’analyse doit commencer par la question la plus ennuyeuse mais la plus salvatrice : « Y a-t-il eu une réponse ? » Ennuyeuse, car on veut tout de suite lire le body et chercher « ce qui ne va pas ». Salvatrice, car parfois il n’existe aucune réponse HTTP, et vous essayez d’analyser un fantôme.
S’il n’y a pas de réponse, il est inutile de discuter au niveau des statuts : vous n’avez ni 404, ni 500, ni en-têtes, ni corps. Les causes peuvent être diverses – service indisponible, adresse incorrecte, panne réseau, connexion non établie – mais la branche de diagnostic est la même : d’abord reconnaître qu’aucune réponse n’a été reçue, puis chercher pourquoi.
Pour concrétiser cette idée avec un exemple simple en Java, on peut représenter le résultat d’un appel soit par une réponse, soit par une erreur. Nous ne faisons pas encore de vrai code HTTP (cela viendra plus tard), mais nous pouvons modéliser la forme du résultat – c’est utile pour la réflexion.
package com.example.readlater;
// Résultat de l'appel : soit une réponse HTTP, soit une erreur survenue sur le chemin (réseau, DNS, timeout, etc.)
public record CallOutcome(RemoteResponse response, Exception error) {
// L'essentiel dans cet objet : on sépare "pas de réponse" de "réponse avec mauvais statut".
public boolean hasResponse() {
// Si response == null, il n'y a pas eu de réponse HTTP en tant que classe (rien à analyser en statut/en-têtes).
return response != null;
}
}
Si hasResponse() renvoie false, il ne faut pas chercher un « bon statut » ni surtout analyser le body : vous avez une autre branche de problème. C’est ici qu’on détermine s’il y a eu une panne réseau, si le host/port/path est erroné, ou si on a buté sur une infrastructure externe.
Et il est utile de se rappeler la sémantique de l’opération. Pour une lecture, l’absence de réponse signifie généralement un résultat perdu. Pour une requête modificatrice, le problème est plus embarrassant : le client ne sait plus s’il n’a simplement pas attendu la réponse ou si le serveur a eu le temps de modifier quelque chose.
3. Étape 2 — Le statut comme classe de problème
Une fois que vous avez confirmé qu’une réponse a bien été reçue, il est temps de faire ce que beaucoup négligent : regarder le code de statut et le percevoir comme une « classe de situation ». Ce n’est pas juste un chiffre, mais un signal convenu entre le client et le serveur : « j’ai réussi », « il y a un problème dans ta requête », « la ressource n’existe pas », « conflit », « j’ai une erreur interne ».
Un point important : une réponse d’erreur est aussi le résultat d’un contrat. Si le serveur répond par 400 ou 404, c’est déjà une « forme normale de communication » : il a pu traiter votre requête au point de renvoyer une réponse structurée (même avec une erreur). Cela diffère de la situation où il n’y a pas de réponse du tout.
Pour « concrétiser » cela en petit code, nous avons besoin d’un objet réponse minimal. Faisons un record avec le statut, le content-type et le body. Oui, body est encore une chaîne : pour ce diagnostic, il est plus important de distinguer le statut et la forme de la réponse que d’analyser le JSON champ par champ.
package com.example.readlater;
// Modèle minimal de réponse HTTP pour le diagnostic.
// Ce qui importe : le statut, le Content-Type et le corps (pour l'instant en chaîne, sans parsing JSON).
public record RemoteResponse(int status, String contentType, String body) {
// 2xx — classe "succès" : le serveur considère la requête traitée.
public boolean is2xx() {
return status >= 200 && status < 300;
}
}
On peut maintenant extraire une vérification très simple : « le statut est-il une erreur ou non ».
package com.example.readlater;
public class Statuses {
// 4xx/5xx — c'est une réponse d'erreur : la réponse existe, mais le serveur signale une erreur.
public static boolean isError(int status) {
return status >= 400 && status < 600;
}
}
Ici, il suffit de retenir un point d’appui : les codes 404 et 500 appartiennent déjà à la branche « réponse reçue ». Ce sont des problèmes différents, mais tous deux se distinguent de la situation où il n’y a pas de statut du tout.
Pour garder cela en tête plus facilement, voici un petit tableau d’interprétation (sans transformer la leçon en manuel RFC) :
| Ce que vous observez | C’est… | Ce qu’il NE faut PAS faire en premier |
|---|---|---|
| Pas de réponse (exception/coupure) | Panne réseau / indisponibilité | Chercher le « bon statut » et analyser le body |
| Réponse avec statut 4xx | Erreur de requête / problème client | Déclarer « le service est tombé » (il a justement répondu) |
| Réponse avec statut 5xx | Erreur côté serveur | Penser que « vous avez absolument tout envoyé correctement » (parfois vous avez provoqué le problème) |
| Réponse avec statut 2xx | Réponse réussie | Considérer que le contrat est forcément respecté (c’est l’étape suivante de vérification) |
Dans le contexte du projet ReadLater Starter, cela sera particulièrement utile lorsque nous commencerons à accéder au catalogue externe de livres. Le fournisseur externe peut honnêtement répondre 404 sur un mauvais chemin (par exemple, si vous avez fait une faute de frappe dans l’URL), répondre 400 sur un paramètre invalide, ou répondre 500 parce qu’il a des problèmes. Et ces trois cas sont des réponses, pas une « absence de réponse ».
4. Étape 3 — Vérification du contrat
Et voilà que tout se rassemble enfin : après avoir vérifié la réponse et le statut, il reste à vérifier si le « succès » reçu ressemble bien à ce qui avait été convenu.
Le moment qui vient casse souvent la vision romantique du 200 OK : parfois le serveur répond 200, mais il le fait « bizarrement ». Et ce n’est ni simplement une « erreur de requête » ni une « erreur interne » – c’est souvent le signe que le contrat a été violé (ou que nous l’avons mal compris).
Le contrat n’est pas seulement les « champs du JSON ». C’est aussi le Content-Type, la forme attendue de la réponse, et les statuts autorisés pour ce point d’accès. Par exemple, vous attendez du JSON, et vous recevez une page HTML avec le texte « Service temporairement indisponible » – et oui, cela arrive parfois avec un statut 200. Ou vous recevez un body vide là où vous attendiez un objet. Ou les en-têtes sont tels que le client ne peut pas comprendre comment interpréter la réponse.
Montrons cela avec une vérification très terre-à-terre : nous considérons que le « contrat ressemble à du JSON » si le Content-Type est application/json et que le body contient au moins un marqueur attendu. Ce n’est pas une « validation correcte », mais une illustration pédagogique de l’idée : même un succès doit être confronté aux attentes.
package com.example.readlater;
public class ContractChecks {
// Vérification très pédagogique : est-ce que cela ressemble à une réponse JSON attendue.
// Ce n’est PAS un "bon" parseur JSON. L'important est l'idée : même avec 200, le contrat peut être cassé.
public static boolean looksLikeJson(RemoteResponse r) {
return "application/json".equals(r.contentType())
&& r.body() != null
// Marqueur grossier : dans un vrai projet, vous analyseriez le JSON, pas chercher "{".
&& r.body().contains("{");
}
}
Imaginez maintenant deux réponses.
La première ressemble à ce que nous voulons :
// Réponse "normale" : JSON et structure attendue.
RemoteResponse ok = new RemoteResponse(
200,
"application/json",
"""
{"items":[],"count":0}
"""
);
La seconde est « réussie » mais étrange :
// Réponse "bizarre" : statut 200, mais Content-Type et corps ne sont pas du JSON API.
RemoteResponse weird = new RemoteResponse(
200,
"text/html",
"""
<html><body>Oops</body></html>
"""
);
Les deux sont formellement 200, mais la seconde viole l’attente : le client pensait avoir affaire à une API JSON, et on lui a envoyé du HTML. C’est une situation typique où le service est derrière un proxy/une page d’erreur, ou lorsque vous avez atterri au mauvais endroit (par exemple, sur un site « humain » au lieu de l’API). Et votre analyse doit pouvoir dire : « Le statut est un succès, mais le contrat ne correspond pas ». C’est une catégorie distincte, très utile car elle évite de perdre du temps à chercher « pourquoi le JSON ne se parse pas » alors qu’en réalité il n’y avait pas de JSON.
Pour l’instant, il nous suffit de constater la non-conformité de la forme. Même lorsque le corps de la réponse ne sera plus une simple chaîne mais un ensemble de champs concrets, l’ordre de pensée restera le même : d’abord la réponse, puis le statut, puis la correspondance avec le contrat.
Un autre point important : le contrat peut être violé non seulement par la forme du body, mais aussi par le statut. Par exemple, vous attendiez que GET /books/search retourne toujours 200 (même si rien n’est trouvé – liste vide), et tout à coup le service commence à renvoyer 404. Du point de vue HTTP, c’est un « comportement possible », mais du point de vue de votre contrat, c’est un changement des règles du jeu. Et il faut le voir comme « le contrat a dérivé », pas comme « bon, encore un 404, tout est perdu ».
5. Mini-diagnostic en code
Nous allons maintenant faire un petit pas mais très significatif : transformer notre schéma en code qui classifie la cause d’un appel problématique. Ce n’est ni une « bibliothèque prête », ni un « framework », ni même un vrai client HTTP – c’est une structure pédagogique de réflexion, que vous remplirez plus tard avec de vraies données (quand le véritable appel existera).
Commençons par l’énumération des résultats possibles. Quatre suffisent :
package com.example.readlater;
public enum CallVerdict {
// Pas de réponse HTTP (connexion non établie, timeout, DNS, coupure, etc.)
NETWORK_FAILURE,
// Réponse HTTP présente, mais statut 4xx/5xx (le serveur a renvoyé une erreur protocolaire).
HTTP_ERROR_RESPONSE,
// La réponse HTTP ne "ressemble pas" à ce qui était convenu (statuts/types/corps).
CONTRACT_VIOLATION,
// Tout va bien : réponse présente, statut de succès, contrat respecté.
SUCCESS
}
Maintenant, assemblons l’analyseur. Il appliquera exactement l’ordre que nous avons discuté : d’abord erreur/absence de réponse, puis statut, puis contrat.
package com.example.readlater;
public class CallAnalyzer {
public CallVerdict analyze(CallOutcome outcome) {
// 1) La première question, la plus importante : y a-t-il eu une réponse ?
if (!outcome.hasResponse()) return CallVerdict.NETWORK_FAILURE;
RemoteResponse r = outcome.response();
// 2) Si le statut est 4xx/5xx — c'est une "réponse d'erreur" : le serveur a répondu mais signale une erreur.
if (Statuses.isError(r.status())) return CallVerdict.HTTP_ERROR_RESPONSE;
// 3) Tout ce qui n'est ni 2xx ni 4xx/5xx, pour ce schéma pédagogique, est considéré comme une branche de statut inattendu.
// Ici, nous ne traitons délibérément pas les histoires de redirection et autres cas spéciaux.
if (!r.is2xx()) return CallVerdict.CONTRACT_VIOLATION;
// 4) Même en 2xx, vérifier que la réponse correspond aux attentes (contrat).
if (!ContractChecks.looksLikeJson(r)) return CallVerdict.CONTRACT_VIOLATION;
return CallVerdict.SUCCESS;
}
}
Il y a une simplification intentionnelle : tout ce qui ne tombe pas dans la branche attendue du succès ou d’une erreur explicite est temporairement considéré comme inattendu pour le contrat actuel. Ce qui compte maintenant, c’est l’ordre du diagnostic lui-même, sans analyse séparée de tous les cas spéciaux du protocole.
Remarquez la simplicité voulue. Nous ne faisons pas une belle hiérarchie d’exceptions, nous ne construisons pas d’abstractions universelles, nous n’ajoutons pas de retries. Notre objectif est de fixer dans votre esprit l’ordre des questions. Ce code peut se lire presque comme un texte : « s’il n’y a pas de réponse – panne réseau ; si le statut est erroné – réponse d’erreur ; si le statut est étrange – contrat ; si la forme de la réponse n’est pas bonne – contrat ; sinon succès ».
Maintenant, montrons à quoi cela ressemble dans une mini-démo. Oui, ce sera un « jouet » où nous créons nous-mêmes un outcome, mais il illustre parfaitement la classification.
package com.example.readlater;
public class ReadLaterApplication {
public static void main(String[] args) {
CallAnalyzer analyzer = new CallAnalyzer();
// Simulation d'une réponse réussie : réponse présente, 200, corps de type JSON.
CallOutcome ok = new CallOutcome(
new RemoteResponse(200, "application/json", """
{"items":[],"count":0}
"""),
null
);
System.out.println(analyzer.analyze(ok)); // SUCCESS
}
}
Et un autre exemple – 404 (réponse présente, mais statut d’erreur) :
package com.example.readlater;
public class Demo404 {
public static void main(String[] args) {
// 404 n'est PAS "pas de réponse", c'est "réponse présente, mais le serveur signale une erreur de requête/ressource".
CallVerdict v = new CallAnalyzer().analyze(
new CallOutcome(
new RemoteResponse(404, "application/json", """
{"message":"not found"}
"""),
null
)
);
System.out.println(v); // HTTP_ERROR_RESPONSE
}
}
Et un exemple « pas de réponse du tout » :
package com.example.readlater;
public class DemoNetworkFailure {
public static void main(String[] args) {
// response == null : pas de réponse HTTP, seulement une exception côté client/réseau.
CallOutcome out = new CallOutcome(null, new IllegalStateException("Connection refused"));
System.out.println(new CallAnalyzer().analyze(out)); // NETWORK_FAILURE
}
}
Du point de vue de l’évolution de ReadLater Starter, c’est utile car plus tard nous aurons un vrai « catalogue externe de livres ». Et lorsque vous déboguerez l’appel « recherche de livre », une simple habitude vous sera utile : « Qu’est-ce que c’est ? Pas de réponse ? Réponse avec statut ? Succès mais contrat cassé ? » C’est bien mieux que « quelque chose ne va pas, imprimons tout en vrac ».
6. Erreurs typiques lors de l’analyse des appels
Erreur n°1 : l’analyse commence par le body.
Le piège le plus courant : vous lisez le body immédiatement, vous essayez de comprendre « pourquoi ce n’est pas ce qu’on attend », puis vous découvrez que le body n’est pas du tout ce que vous pensiez, ou qu’il n’existe pas. L’ordre correct est strict : d’abord déterminer si la réponse existe, puis le statut, et ensuite seulement le body.
Erreur n°2 : 404 est considéré comme « service indisponible ».
404 est une réponse d’un service vivant. Oui, elle ne vous plaît pas, mais elle signifie que le réseau et le serveur ont fonctionné suffisamment bien pour renvoyer un signal structuré. Si vous appelez cela « indisponibilité », vous commencerez automatiquement à chercher un problème « dans l’infrastructure », alors que ce n’est souvent qu’un mauvais chemin ou une ressource manquante.
Erreur n°3 : toute exception est considérée comme un 500 côté serveur.
Une exception côté client (ou dans votre application) signifie très souvent « pas de réponse », pas « le serveur a renvoyé 500 ». Un 500 a un signe concret : vous avez reçu une réponse et vous avez vu le statut. S’il n’y a pas de réponse, c’est une autre classe de problèmes, qu’il faut séparer.
Erreur n°4 : 200 OK signifie automatiquement « tout va bien ».
200 signifie que le serveur a dit « succès », mais ne garantit pas que vous et le serveur comprenez le format de la réponse de la même manière. Si le Content-Type n’est pas le bon, si le body est vide, si le format « a dérivé », cela peut être un problème contractuel. Et il est particulièrement gênant car il ressemble à un succès, mais casse le client.
Erreur n°5 : on répète aussi facilement les requêtes de lecture que celles de modification de données.
En cas de problème réseau, il est psychologiquement plus facile de répéter un GET : la lecture ne devrait pas modifier l’état. Mais répéter des requêtes « modificatrices » (création/mise à jour/suppression) demande plus de prudence, car lors d’une panne réseau, vous ne savez pas toujours ce que le serveur a eu le temps de faire. Aujourd’hui, nous ne construisons pas de système de répétition, mais la prudence appropriée doit déjà apparaître.
GO TO FULL VERSION