Tom Daluzeau
← Parcours Python · système
Python · système · leçon 5 / 8Ch. 5 · API REST et requests12 minTerminée

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 :

  1. le client envoie une requête : une méthode (GET pour lire) et une adresse ;
  2. le serveur renvoie une réponse : un code de statut (un nombre à trois chiffres) et un corps (le contenu).
fleetcheck le client requests.get(…) web-1 le serveur /health GET /health requête : une méthode et une adresse 200 {"status": "ok"} réponse : un code de statut et un corps
Une requête part, une réponse revient : un code à trois chiffres et un corps.

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.

une réponse arrive ? non oui code en 2xx ? oui non Sain 200 il répond que ça va En panne 500, 503… il répond par une erreur Injoignable aucun code pas de réponse du tout raise_for_status() : rien raise_for_status() : HTTPError une exception, sans code
D'abord : une réponse est-elle arrivée ? Ensuite seulement : que dit son code ?

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
la réponse HTTP 200 OK {"status": "ok", "version": "1.4.2"} durée : 250 ms entre envoi et réception r.status_code int : 200, pas "200" r.text str : le corps brut r.json() dict : data["status"] vaut "ok" r.elapsed.total_seconds() float : 0.25 (secondes)
Chaque morceau de la réponse a son attribut, et chaque attribut son type.

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 4xx ou 5xx : il lève requests.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 : 2xx réussite, 3xx redirection, 4xx erreur du client, 5xx erreur du serveur. 200 OK, 404 adresse inconnue, 500 plantage, 503 indisponible.
  • Sain : répond 200. En panne : répond, mais par une erreur. Injoignable : ne répond pas, donc aucun code.
  • r.status_code est un int, r.text un str, r.json() un dict (ou une list).
  • r.elapsed.total_seconds() : le temps de réponse, en secondes.
  • requests.get ne lève rien sur un 404 ou un 500. C’est r.raise_for_status() qui lève requests.HTTPError pour un code 4xx ou 5xx.

Entraînement

12 questions, dont 3 à compléter. Tu réponds, on passe à la suivante. La note arrive à la fin.

Cartes · 7

Réviser les cartes ici

Espace : retourner