HTTP en profondeur - 02 - La requête HTTP

Anatomie complète d'une requête HTTP : request line, headers, body, et comment lire tout ca avec curl -v.

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-Agent et Accept
  • Content-Length est 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-Length donne la taille du body en octets
  • Transfer-Encoding: chunked permet le streaming
  • Query string pour les filtres, body pour les donnees structurees
  • curl -v montre tout en clair

Precedent : 01 - Anatomie d'une URL Suivant : 03 - La réponse HTTP

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.