03 - La réponse HTTP
Ce que tu vas apprendre
- La structure exacte d'une réponse HTTP (status line, headers, body)
- Comment le client sait quand le body se termine
- Ce qu'est le chunked transfer encoding cote réponse
- Pourquoi le content sniffing est un problème de sécurité
Prerequisites
Avoir lu 02 - La requête HTTP pour comprendre la structure symetrique requête/réponse.
Si la requête est une question, la réponse est la conversation complète : un statut, des metadonnees, et optionnellement un corps. On passe nos journees a consommer des réponses HTTP sans les regarder. Comme ouvrir des lettres en ne lisant que le contenu sans jamais regarder l'enveloppe.
Et parfois, l'enveloppe contient l'information la plus utile.
La structure d'une réponse
Exactement comme la requête, trois parties dans un ordre fixe :
HTTP-version SP status-code SP reason-phrase CRLF
header-field CRLF
header-field CRLF
CRLF
[body]
Exemple concret :
HTTP/1.1 200 OK\r\n
Content-Type: application/json\r\n
Content-Length: 27\r\n
Date: Sat, 29 Mar 2026 10:30:00 GMT\r\n
\r\n
{"status": "ok", "count": 42}
La status line
La première ligne de la réponse contient trois éléments :
HTTP/1.1 404 Not Found
^^^^^^^^ ^^^ ^^^^^^^^^
| | |
| | Phrase explicative (informelle, ignoree par les clients modernes)
| Code de statut (le chiffre qui compte)
Version du protocole
La reason phrase ("Not Found", "OK", "Internal Server Error") est purement informative. En HTTP/2, elle a ete complètement supprimee. Le code numérique est la seule information fiable.
Un détail que beaucoup ignorent : le serveur peut mettre n'importe quoi dans la reason phrase. HTTP/1.1 200 Tout roule ma poule est techniquement valide. Certains serveurs de jeu le font pour amuser la galerie.
Lire une réponse brute
Avec curl -v, les lignes de réponse sont prefixees par < :
bashcurl -v https://httpbin.org/get
< HTTP/2 200
< content-type: application/json
< content-length: 256
< date: Sat, 29 Mar 2026 10:30:00 GMT
< server: gunicorn/19.9.0
<
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/8.4.0"
},
"origin": "203.0.113.42",
"url": "https://httpbin.org/get"
}
Pour voir uniquement les headers de réponse sans le body, utilise -I (HEAD request) ou -D - :
bash# Envoie un HEAD (pas de body dans la reponse)
curl -I https://example.com
# Envoie un GET normal mais affiche les headers sur stdout
curl -D - -o /dev/null -s https://example.com
Comment le client sait quand le body se termine
C'est une question moins triviale qu'il n'y parait. Il y a quatre mecanismes :
1. Content-Length
Le serveur annonce la taille exacte du body :
Content-Length: 1234
Le client lit exactement 1234 octets puis considéré la réponse terminee.
2. Transfer-Encoding: chunked
Le body arrive en morceaux, chacun prefixe par sa taille en hexa :
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain
1c\r\n
Premiere partie du contenu\r\n
19\r\n
Deuxieme partie ici.\r\n
0\r\n
\r\n
Le chunk de taille 0 signale la fin. C'est le mecanisme le plus utilise pour le streaming de réponses.
3. Connection: close
Le serveur ferme la connexion TCP quand le body est termine. Le client lit jusqu'a la deconnexion. C'est le mecanisme le plus ancien et le moins efficace (on perd la connexion pour les requêtes suivantes).
HTTP/1.1 200 OK
Connection: close
Content-Type: text/html
<html>...</html>
[connexion fermee par le serveur]
4. Pas de body
Certaines réponses n'ont jamais de body : 204 No Content, 304 Not Modified, et les réponses aux requêtes HEAD.
Chunked encoding en détail
Le chunked transfer encoding merite qu'on s'y attarde parce qu'on le croise partout. Chaque fois qu'un serveur commence a envoyer la réponse avant de connaître la taille totale, il utilise chunked.
Cas typiques :
- Pages HTML générées dynamiquement
- Réponses d'API qui streament des résultats de base de donnees
- Server-Sent Events
- Réponses compressees a la volee
Voici le format exact :
chunk-size (en hexadecimal) CRLF
chunk-data CRLF
chunk-size CRLF
chunk-data CRLF
0 CRLF
CRLF
Un détail : apres le chunk final (0\r\n), on peut ajouter des "trailers" -- des headers envoyes apres le body. En theorie. En pratique, presque personne ne les utilise. La spec les prevoit pour des choses comme les checksums qu'on ne peut calculer qu'apres avoir envoye tout le contenu.
Les headers de réponse courants
HTTP/1.1 200 OK
Date: Sat, 29 Mar 2026 10:30:00 GMT # Horodatage du serveur
Content-Type: text/html; charset=utf-8 # Type et encoding du body
Content-Length: 4523 # Taille du body
Server: nginx/1.25.3 # Identification du serveur
Cache-Control: max-age=3600 # Directive de cache
ETag: "abc123" # Identifiant de version
Set-Cookie: session=xyz; HttpOnly # Cookie a stocker
Connection: keep-alive # Garder la connexion ouverte
Le header Date est obligatoire sur les réponses des serveurs d'origine (pas les proxies). Il donne l'heure du serveur au moment de la réponse. C'est utile pour le calcul de l'age du cache.
Server revele souvent trop d'informations. nginx/1.25.3 dit a un attaquant exactement quelle version chercher dans les bases de vulnérabilités. La bonne pratique est de le supprimer ou de le remplacer par quelque chose de générique.
Content sniffing : quand le navigateur fait le malin
Si le serveur ne met pas de Content-Type, ou met Content-Type: application/octet-stream, le navigateur va essayer de deviner le type du contenu. C'est le content sniffing (ou MIME sniffing).
Le problème : un attaquant uploade un fichier .jpg qui contient du HTML avec du JavaScript. Le serveur le sert avec Content-Type: image/jpeg. Le navigateur sniffe le contenu, détecté du HTML, et exécuté le JavaScript. Faille XSS.
La protection :
X-Content-Type-Options: nosniff
Ce header dit au navigateur : "fais confiance au Content-Type, ne sniffe pas." Mets-le sur toutes tes réponses. Tous les navigateurs modernes le respectent.
Sur paltemps.fr, on ajoute X-Content-Type-Options: nosniff sur chaque réponse via la configuration du reverse proxy. Ca ne coûte rien et ca ferme un vecteur d'attaque.
Le body peut etre n'importe quoi
Le body d'une réponse HTTP peut contenir n'importe quel type de donnees. Le Content-Type dit au client comment l'interpréter :
# Du JSON
Content-Type: application/json
{"users": []}
# Du HTML
Content-Type: text/html; charset=utf-8
<html><body>Hello</body></html>
# Une image
Content-Type: image/png
[octets binaires PNG]
# Un PDF
Content-Type: application/pdf
[octets binaires PDF]
# Du texte brut
Content-Type: text/plain
Juste du texte.
Le charset=utf-8 dans text/html; charset=utf-8 est fondamental. Sans lui, le navigateur utilise un encoding par défaut qui peut varier selon la locale du système. Résultat : des caractères accentues casses. Mets toujours le charset.
Résumé
- Une réponse HTTP = status line + headers + ligne vide + body optionnel
- La status line contient la version, le code de statut et une reason phrase
- Le client détecté la fin du body via Content-Length, chunked encoding, ou la fermeture de connexion
- Le content sniffing est un risque de sécurité -- utilise
X-Content-Type-Options: nosniff - Specifie toujours le charset dans Content-Type pour les contenus textuels
Precedent : 02 - La requête HTTP Suivant : 04 - Les méthodes HTTP