HTTP et requests.get : interroger /health
Lire un code de statut HTTP, distinguer un serveur sain, en panne ou injoignable, et interroger une adresse avec requests.get : status_code, json(), text, elapsed et raise_for_status().
Mis à jour le
À recouper avec le cours. Cette leçon a été écrite à partir du plan du chapitre, avant relecture avec les supports. En cas de doute, le cours fait foi.
Pourquoi on voit ça
Jusqu’ici, l’outil du fil rouge lisait un inventaire dans un fichier. Maintenant, il va poser la question aux serveurs eux-mêmes : « est-ce que tu vas bien ? ». C’est le cœur d’un outil de surveillance : interroger l’adresse /health de chaque serveur du parc et alerter quand l’un tombe.
Pour ça, il faut deux choses : comprendre ce qu’un serveur répond (HTTP et ses codes), puis savoir poser la question en Python (le module requests). Au contrôle, on te donnera un extrait et un code de statut, et tu devras dire ce qui s’affiche.
HTTP en bref
HTTP est un dialogue en deux temps :
- le client envoie une requête : une méthode (
GETpour lire) et une adresse ; - le serveur renvoie une réponse : un code de statut (un nombre à trois chiffres) et un corps (le contenu).
Le premier chiffre du code donne sa famille.
| Famille | Sens | Qui est en cause |
|---|---|---|
2xx |
réussite | personne |
3xx |
redirection : « va voir ailleurs » | personne |
4xx |
erreur côté client : la demande est mauvaise | celui qui demande |
5xx |
erreur côté serveur : la demande était bonne, le serveur a échoué | celui qui répond |
Quatre codes à connaître par cœur :
| Code | Sens |
|---|---|
200 |
OK, tout va bien |
404 |
l’adresse demandée n’existe pas |
500 |
le serveur a planté en traitant la demande |
503 |
service indisponible (surchargé, en maintenance, en cours de démarrage) |
L’adresse /health
Par convention, beaucoup de services exposent une adresse /health faite pour les outils de surveillance. Quand tout va bien, elle répond 200 avec un petit corps en JSON, par exemple :
{"status": "ok", "version": "1.4.2"}
Sain, en panne ou injoignable
Un serveur peut se trouver dans trois situations. Les deux dernières sont toutes les deux mauvaises, mais ce n’est pas la même panne.
| État | Ce qui se passe | Ce que tu reçois |
|---|---|---|
| Sain | il répond, et dit que ça va | un code 200 |
| En panne | il répond, mais par une erreur | un code d’erreur, 500 ou 503 par exemple |
| Injoignable | il ne répond pas du tout | aucun code : il n’y a pas de réponse |
Une analogie : tu téléphones à quelqu’un. Sain : il décroche et dit « ça va ». En panne : il décroche et dit « je suis malade ». Injoignable : ça sonne dans le vide.
Retiens surtout la dernière ligne : un serveur injoignable ne renvoie pas de code de statut. En Python, cette situation devient une exception. C’est le sujet de la leçon suivante.
requests.get
requests n’est pas livré avec Python : il s’installe dans le venv avec pip install requests. Ensuite, une ligne suffit pour envoyer une requête GET.
import requests
r = requests.get("http://web-1.local/health", timeout=3)
print(r.status_code)
print(r.text)
200
{"status": "ok", "version": "1.4.2"}
r est la réponse. Voici ce qu’elle contient :
| Écriture | Ce que c’est | Type |
|---|---|---|
r.status_code |
le code de statut | int : 200, pas "200" |
r.text |
le corps brut, tel qu’il est arrivé | str |
r.json() |
le corps JSON converti en objet Python | dict ou list |
r.elapsed.total_seconds() |
le temps de réponse, en secondes | float |
Le timeout=3 fixe un délai d’attente. On voit pourquoi il est indispensable dans la leçon suivante.
r.text ou r.json()
r.text est une chaîne : tu peux l’afficher, pas y lire une clé. r.json() est une méthode (avec des parenthèses) qui convertit le corps JSON en dictionnaire.
data = r.json()
print(data["status"])
print(r.json()["version"])
ok
1.4.2
Écrire r.text["status"] lève TypeError : une chaîne ne se lit qu’avec des positions entières.
r.elapsed : le temps de réponse
r.elapsed mesure le temps écoulé entre l’envoi de la requête et l’arrivée de la réponse. total_seconds() le donne en secondes.
print(r.elapsed.total_seconds())
0.25
Ici le serveur a répondu en 250 millisecondes. Ce nombre servira à repérer un serveur qui répond, mais trop lentement.
raise_for_status() et HTTPError
Voici le piège de la leçon : requests.get ne lève pas d’exception quand le serveur répond 404 ou 500. De son point de vue, une réponse est arrivée, donc tout s’est bien passé.
r = requests.get("http://web-2.local/health", timeout=3)
print("reçu", r.status_code)
reçu 503
Aucune erreur, aucun traceback. Si tu ne regardes pas le code, tu crois que tout va bien.
r.raise_for_status() sert à transformer un code d’erreur en exception :
- code
4xxou5xx: il lèverequests.HTTPError; - sinon : il ne fait rien, et le programme continue.
r = requests.get("http://web-2.local/health", timeout=3)
r.raise_for_status()
print("sain")
requests.exceptions.HTTPError: 503 Server Error: Service Unavailable for url: http://web-2.local/health
print("sain") n’est jamais atteint. Pour ne pas arrêter le programme, on attrape l’exception :
try:
r = requests.get("http://web-2.local/health", timeout=3)
r.raise_for_status()
print("sain")
except requests.HTTPError:
print("en panne :", r.status_code)
en panne : 503
HTTPError correspond donc exactement à l’état « en panne » : le serveur a répondu, mais par une erreur.
À retenir
- Familles :
2xxréussite,3xxredirection,4xxerreur du client,5xxerreur du serveur.200OK,404adresse inconnue,500plantage,503indisponible. - Sain : répond
200. En panne : répond, mais par une erreur. Injoignable : ne répond pas, donc aucun code. r.status_codeest unint,r.textunstr,r.json()undict(ou unelist).r.elapsed.total_seconds(): le temps de réponse, en secondes.requests.getne lève rien sur un404ou un500. C’estr.raise_for_status()qui lèverequests.HTTPErrorpour un code4xxou5xx.
Entraînement
12 questions, dont 3 à compléter. Tu réponds, on passe à la suivante. La note arrive à la fin.
Ta note
Cartes · 7
Réviser les cartes ici
Pourquoi