02 - La requête HTTP
Ce que tu vas apprendre
- La structure exacte d'une requête HTTP (request line, headers, body)
- Comment lire une requête brute avec curl -v
- Le rôle de Content-Length et Transfer-Encoding: chunked
- La différence entre envoyer des donnees dans la query string ou dans le body
Prerequisites
Avoir lu 01 - Anatomie d'une URL pour comprendre la décomposition de la cible.
La première fois que j'ai vu une requête HTTP brute, j'ai eu une reaction bizarre : "c'est tout ?" Apres des mois a utiliser Postman et ses dizaines de boutons, découvrir que tout ce bazar se résumé a quelques lignes de texte ASCII, ca remet les choses en perspective.
La structure d'une requête
Une requête HTTP se compose de trois parties. Toujours dans le meme ordre, toujours separees de la meme manière :
METHOD SP request-target SP HTTP-version CRLF
header-field CRLF
header-field CRLF
CRLF
[body]
SP c'est un espace. CRLF c'est \r\n (retour chariot + saut de ligne). La ligne vide (double CRLF) marque la fin des headers et le début du body.
Voici une requête réelle :
POST /api/users HTTP/1.1\r\n
Host: api.example.com\r\n
Content-Type: application/json\r\n
Content-Length: 34\r\n
Authorization: Bearer eyJhbGciOi...\r\n
\r\n
{"name": "Alice", "role": "admin"}
C'est tout. Pas de magie, pas de format binaire cache. Du texte.
La request line
La première ligne contient trois informations :
POST /api/users HTTP/1.1
^^^^ ^^^^^^^^^^^ ^^^^^^^^
| | |
| | Version du protocole
| Cible de la requete (le path + query)
Methode HTTP
La cible est généralement le path de l'URL (forme "origin"). Mais dans certains cas, c'est une URL absolue (quand on passe par un proxy) ou juste * (pour les requêtes OPTIONS globales).
# Forme origin (la plus courante)
GET /api/users?page=2 HTTP/1.1
# Forme absolue (via un proxy)
GET http://example.com/api/users HTTP/1.1
# Forme asterisk (OPTIONS global)
OPTIONS * HTTP/1.1
Lire une requête brute avec curl
curl -v est l'outil que j'utilise le plus pour debugger du HTTP. Le flag -v (verbose) affiche les headers envoyes et reçus. Les lignes prefixees par > sont ce que curl envoie, celles par < sont ce que le serveur répond.
bashcurl -v -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-d '{"test": true}'
> POST /post HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.4.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 14
>
< HTTP/2 200
< content-type: application/json
< content-length: 428
<
{
"data": "{\"test\": true}",
"headers": {
"Content-Type": "application/json",
...
}
}
Quelques observations :
- curl ajoute automatiquement
Host,User-AgentetAccept Content-Lengthest calcule automatiquement à partir du body- Le body (
{"test": true}) fait bien 14 octets
Les headers de requête
Les headers sont des paires clé-valeur separees par : (deux-points espace). Ils ne sont pas sensibles a la casse (Content-Type et content-type sont équivalents), meme si la convention est d'utiliser la forme avec majuscules.
Les headers essentiels d'une requête :
Host: api.example.com # Obligatoire en HTTP/1.1
Content-Type: application/json # Type du body
Content-Length: 42 # Taille du body en octets
Accept: application/json # Ce que le client veut recevoir
Authorization: Bearer xxx # Authentification
User-Agent: curl/8.4.0 # Identification du client
Host est le seul header vraiment obligatoire en HTTP/1.1. Sans lui, le serveur ne sait pas quel site tu vises (un meme serveur peut héberger plusieurs domaines via le virtual hosting).
Content-Length : combien d'octets ?
Quand tu envoies un body, le serveur a besoin de savoir ou le body se termine. Content-Length donne la taille exacte en octets (pas en caractères -- attention avec l'UTF-8).
Content-Length: 34
{"name": "Alice", "role": "admin"}
Si tu mens sur la taille, les choses tournent mal. Trop court : le serveur tronque le body. Trop long : le serveur attend des octets qui ne viendront jamais et finit par timeout.
Un piège classique : calculer la longueur de "cafe" en UTF-8. Ca fait 5 octets (le "e" accent prend 2 octets), pas 4.
Transfer-Encoding: chunked
Parfois tu ne connais pas la taille du body a l'avance. Typiquement quand tu streams des donnees. Transfer-Encoding: chunked permet d'envoyer le body en morceaux :
POST /api/upload HTTP/1.1
Host: example.com
Transfer-Encoding: chunked
Content-Type: text/plain
1a\r\n
Voici le premier morceau\r\n
12\r\n
Et le deuxieme.\r\n
0\r\n
\r\n
Chaque chunk commence par sa taille en hexadecimal, suivie de \r\n, puis les donnees, puis \r\n. Le chunk final a une taille de 0.
1a en hexa = 26 octets. 12 en hexa = 18 octets. Le serveur lit chunk par chunk jusqu'au marqueur de fin.
Tu ne peux pas utiliser Content-Length et Transfer-Encoding: chunked en meme temps. Si les deux sont presents, Transfer-Encoding prend la priorité.
Query string vs body : ou mettre les donnees ?
La question revient tout le temps. Voici mes regles :
Dans la query string quand :
- Tu fais un GET (les GET n'ont pas de body en pratique)
- Les paramètres sont des filtres, des options de pagination, de tri
- Les donnees ne sont pas sensibles (la query string apparaît dans les logs serveur, l'historique du navigateur, les referers)
Dans le body quand :
- Tu créés ou modifies une ressource (POST, PUT, PATCH)
- Les donnees sont volumineuses ou structurees
- Les donnees sont sensibles (mots de passe, tokens)
# Filtrer des utilisateurs (GET + query string)
GET /api/users?role=admin&page=2 HTTP/1.1
Host: api.example.com
# Creer un utilisateur (POST + body)
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 42
{"name": "Bob", "role": "editor", "active": true}
Sur paltemps.fr, on a eu un bug memorables ou quelqu'un passait un mot de passe dans la query string d'un GET. Le mot de passe se retrouvait dans les logs nginx, dans Google Analytics (via le référer), et dans l'historique du navigateur. Trois fuites pour le prix d'une.
Les formats de body courants
Le header Content-Type indique au serveur comment interpréter le body :
# JSON (le plus courant pour les API)
Content-Type: application/json
{"key": "value"}
# Formulaire classique (HTML forms)
Content-Type: application/x-www-form-urlencoded
name=Alice&role=admin
# Upload de fichier
Content-Type: multipart/form-data; boundary=----FormBoundary
------FormBoundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
[donnees binaires]
------FormBoundary--
# Texte brut
Content-Type: text/plain
Juste du texte.
Résumé
- Une requête HTTP = request line + headers + ligne vide + body optionnel
- La request line contient la méthode, la cible et la version
Content-Lengthdonne la taille du body en octetsTransfer-Encoding: chunkedpermet le streaming- Query string pour les filtres, body pour les donnees structurees
curl -vmontre tout en clair
Precedent : 01 - Anatomie d'une URL Suivant : 03 - La réponse HTTP