Chapitre 222 minIntermédiaire
Le protocole HTTP : requête, réponse, méthodes et en-têtes
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- décomposer une URL et lire une requête et une réponse HTTP ;
- choisir la bonne méthode et dire si elle est sûre ou idempotente ;
- distinguer PUT et PATCH ;
- utiliser les en-têtes courants, le cache et les requêtes conditionnelles.
5 exercices corrigés1 schéma
HTTP (HyperText Transfer Protocol) est le protocole qui transporte les échanges entre un client et un serveur web : le client envoie une requête (une méthode, une adresse, des en-têtes, parfois un corps), le serveur renvoie une réponse (un code de statut, des en-têtes, souvent un corps). Toute API REST repose sur lui. Ce chapitre décortique une requête et une réponse, les méthodes et leurs propriétés (sûre, idempotente), les en-têtes utiles, le cache et HTTPS.
L'anatomie d'une URL
Avant d'envoyer une requête, le client a besoin d'une adresse. Une URL d'API se découpe ainsi :
https://api.gsb.example:443/v1/fiches?mois=2026-10&etat=CL#haut
└─┬─┘ └──────┬──────┘ └┬┘└───┬────┘└─────────┬─────────┘└┬─┘
schéma hôte port chemin chaîne de requête fragment
| Partie | Rôle | Dans l'exemple |
|---|---|---|
| Schéma | Le protocole : http ou https | https |
| Hôte | Le nom du serveur, résolu par le DNS en adresse IP | api.gsb.example |
| Port | Facultatif : 443 par défaut en HTTPS, 80 en HTTP | 443 (on ne l'écrit jamais en pratique) |
| Chemin | La ressource demandée | /v1/fiches |
| Chaîne de requête (query string) | Des paramètres nom=valeur séparés par &, après le ? | mois=2026-10 et etat=CL |
| Fragment | Une position dans le document, après le # ; jamais envoyé au serveur | #haut |
Certains caractères ne peuvent pas figurer tels quels dans une URL : l'espace, &, ?, #, les lettres accentuées. On les encode en pourcentage (percent-encoding) : un espace devient %20, é devient %C3%A9 (ses deux octets en UTF-8). Une recherche sur « note de frais » s'écrit donc ?q=note%20de%20frais. En JavaScript, encodeURIComponent('note de frais') fait la conversion ; en PHP, rawurlencode() ou http_build_query().
La requête : quatre parties
Voici la requête qu'envoie le front d'AppliFrais pour lire la fiche n° 12, telle qu'elle circule sur le réseau en HTTP/1.1 :
GET /v1/fiches/12 HTTP/1.1
Host: api.gsb.example
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3Iiwicm9sZSI6InZpc2l0ZXVyIiwiaWF0IjoxNzkzNjA2NDAwLCJleHAiOjE3OTM2MDczMDB9.a8ZshHZGh7E-8WGbfhx71TRtZuYh4itGWIpPi0G4Psg
User-Agent: AppliFrais-Web/3.2
- La ligne de requête : la méthode (
GET), la cible (/v1/fiches/12, le chemin et la chaîne de requête) et la version du protocole. - Les en-têtes (headers) : une ligne
Nom: valeurpar information.Hostest obligatoire en HTTP/1.1 : un même serveur héberge souvent plusieurs sites, et c'est cet en-tête qui dit lequel est visé. - Une ligne vide, qui marque la fin des en-têtes.
- Le corps (body), facultatif. Une requête
GETn'en a pas ; une requêtePOST,PUTouPATCHen a presque toujours un, accompagné de l'en-têteContent-Typequi dit son format.
La réponse : trois parties
Le serveur répond :
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 74
ETag: "1434c386cc94c088"
Cache-Control: private, max-age=0, must-revalidate
{"id":12,"visiteurId":7,"mois":"2026-10","etat":"CR","montantValide":null}
- La ligne de statut : la version, le code de statut (
200) et un libellé (OK) destiné aux humains. Les programmes ne lisent que le code (chapitre 3). - Les en-têtes : ici, le format du corps (
Content-Type), sa taille en octets (Content-Length), une empreinte de la version de la ressource (ETag) et les règles de cache. - Le corps, séparé des en-têtes par une ligne vide : la représentation de la fiche en JSON.
Un vrai échange, en direct
L'API Découpage administratif de l'État est ouverte à tous. La commande curl -v affiche ce qu'elle envoie (lignes >) et ce qu'elle reçoit (lignes <) :
curl -v "https://geo.api.gouv.fr/communes?codePostal=69001&fields=nom,population" -H "Accept: application/json"
> GET /communes?codePostal=69001&fields=nom,population HTTP/2
> Host: geo.api.gouv.fr
> User-Agent: curl/8.6.0
> Accept: application/json
>
< HTTP/2 200
< content-type: application/json; charset=utf-8
< content-length: 51
< etag: W/"33-74JZfSXbfoh39sF2mu87OzRkc1M"
< cache-control: public, max-age=3600, immutable
<
[{"nom":"Lyon","population":519127,"code":"69123"}]
(Quelques en-têtes du serveur sont omis ; la population et l'empreinte changent avec les mises à jour des données.) Deux différences avec l'exemple précédent : la connexion se fait en HTTP/2, et les noms d'en-têtes arrivent en minuscules. Ce n'est pas un problème : les noms d'en-têtes ne sont pas sensibles à la casse, Content-Type et content-type désignent le même en-tête.
HTTP/1.1, HTTP/2 et HTTP/3
Trois versions de HTTP circulent aujourd'hui. Elles ne changent pas le sens des échanges : méthodes, codes de statut et en-têtes sont les mêmes partout, et ils sont définis une fois pour toutes par la RFC 9110 (HTTP Semantics, 2022). Ce qui change, c'est la façon de les transporter.
| Version | Transport | Ce qu'elle apporte |
|---|---|---|
| HTTP/1.1 (1997, révisé en 2022) | Texte sur TCP | Lisible à l'œil nu ; une requête à la fois par connexion |
| HTTP/2 (2015) | Binaire sur TCP, presque toujours avec TLS | Plusieurs requêtes en parallèle sur une seule connexion (multiplexage), en-têtes compressés |
| HTTP/3 (2022) | Binaire sur QUIC, au-dessus d'UDP | Connexion plus rapide, pas de blocage de toutes les requêtes quand un paquet se perd |
Pour écrire ou appeler une API, tu n'as pas à t'en soucier : le navigateur, curl et les bibliothèques HTTP négocient la version tout seuls. Les exemples du cours sont écrits en HTTP/1.1, plus lisible.
Les méthodes HTTP
La méthode dit ce que le client veut faire de la ressource désignée par l'URI. Une API REST en utilise cinq au quotidien :
| Méthode | Usage dans une API REST | Corps de requête | Réponse typique |
|---|---|---|---|
GET | Lire une ressource ou une liste | Non | 200 OK et la ressource |
POST | Créer une ressource dans une collection, ou déclencher un traitement | Oui | 201 Created et l'en-tête Location |
PUT | Remplacer entièrement une ressource (ou la créer à une URI choisie par le client) | Oui, la ressource complète | 200 OK ou 204 No Content |
PATCH | Modifier une partie d'une ressource | Oui, les seuls champs à changer | 200 OK et la ressource modifiée |
DELETE | Supprimer une ressource | Non | 204 No Content |
Trois autres méthodes servent en coulisses :
HEAD: commeGET, mais le serveur ne renvoie que les en-têtes, sans le corps. Utile pour savoir si une ressource existe ou a changé sans la télécharger ;OPTIONS: demande au serveur quelles méthodes sont permises sur une URI. Les navigateurs l'envoient d'eux-mêmes avant certaines requêtes vers une autre origine : c'est la requête de pré-vérification de CORS (chapitre 10) ;TRACEetCONNECT: diagnostic et tunnels à travers un proxy. Une API ne les expose pas.
Méthode sûre, méthode idempotente
La RFC 9110 donne deux propriétés aux méthodes. Elles comptent pour la sécurité, pour le cache et pour savoir si un client peut renvoyer une requête qui a échoué.
Une méthode sûre (safe) ne modifie rien sur le serveur : elle sert seulement à lire. GET, HEAD et OPTIONS sont sûres. Conséquence pratique : un robot d'indexation, un navigateur qui précharge des liens ou un antivirus qui teste une URL peuvent appeler un GET sans danger. Une route GET /v1/fiches/12/supprimer est donc une faute de conception grave : le premier robot qui suit le lien supprime la fiche.
Une méthode idempotente produit le même effet sur le serveur qu'on l'envoie une fois ou dix fois. Toutes les méthodes sûres le sont, ainsi que PUT et DELETE :
PUT /v1/fiches/12avec le même corps : après le premier envoi, la fiche contient ce corps ; les envois suivants la remplacent par la même chose. L'état final est identique ;DELETE /v1/fiches/12: le premier envoi supprime la fiche ; le deuxième ne trouve plus rien et renvoie404. La réponse diffère, mais l'état du serveur est le même : la fiche n'existe pas. C'est cet état qui définit l'idempotence ;POST /v1/fichesn'est pas idempotent : chaque envoi crée une nouvelle fiche. Un double clic sur « Créer », ou un réseau mobile qui renvoie la requête après une coupure, crée un doublon.
| Méthode | Sûre | Idempotente | Réponse pouvant être mise en cache |
|---|---|---|---|
GET | Oui | Oui | Oui |
HEAD | Oui | Oui | Oui |
OPTIONS | Oui | Oui | Non |
POST | Non | Non | Rarement (seulement si le serveur l'indique explicitement) |
PUT | Non | Oui | Non |
PATCH | Non | Non garantie | Non |
DELETE | Non | Oui | Non |
PUT ou PATCH : remplacer ou modifier
PUT remplace toute la ressource par celle qu'envoie le client. PATCH applique des modifications partielles. La différence se voit quand le client n'envoie qu'un champ. Partons de ce visiteur :
{ "id": 7, "nom": "Martin", "prenom": "Lucas", "telephone": "0601020304", "region": "Rhône" }
Le client veut changer le téléphone et envoie { "telephone": "0611223344" }.
- Avec
PATCH /v1/visiteurs/7, seul le téléphone change ; le nom, le prénom et la région restent. - Avec
PUT /v1/visiteurs/7, le corps devient la nouvelle ressource : une API qui respecte la sémantique dePUTeffacerait le nom, le prénom et la région, ou plus raisonnablement refuserait la requête avec422parce que des champs obligatoires manquent.
Pourquoi PATCH n'est-il pas garanti idempotent ? Parce que le corps peut décrire une opération relative. PATCH avec { "telephone": "0611223344" } est idempotent ; PATCH avec { "operation": "ajouter", "montant": 10 } ajoute 10 à chaque envoi. Le format standard JSON Merge Patch (RFC 7396, type application/merge-patch+json) décrit la forme simple : on envoie les champs à remplacer, null pour en supprimer un. Le format JSON Patch (RFC 6902, type application/json-patch+json) décrit une liste d'opérations (add, remove, replace, test…), plus puissante mais plus rare.
Les en-têtes à connaître
HTTP définit des dizaines d'en-têtes. Une API en utilise une quinzaine régulièrement.
Dans la requête
| En-tête | Rôle | Exemple |
|---|---|---|
Host | Le serveur visé (obligatoire en HTTP/1.1) | Host: api.gsb.example |
Accept | Les formats de réponse que le client sait lire | Accept: application/json |
Content-Type | Le format du corps envoyé | Content-Type: application/json |
Authorization | La preuve d'identité du client | Authorization: Bearer eyJhbGci… |
User-Agent | Le logiciel client, utile dans les journaux | User-Agent: AppliFrais-Mobile/2.0 (Android 16) |
If-None-Match | « Renvoie la ressource seulement si son ETag a changé » | If-None-Match: "1434c386cc94c088" |
If-Match | « Modifie seulement si l'ETag est toujours celui-ci » | If-Match: "1434c386cc94c088" |
Origin | L'origine de la page qui fait l'appel (ajouté par le navigateur) | Origin: https://frais.gsb.example |
Idempotency-Key | Un identifiant unique pour rejouer un POST sans doublon | Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324 |
Dans la réponse
| En-tête | Rôle | Exemple |
|---|---|---|
Content-Type | Le format du corps renvoyé | Content-Type: application/json; charset=utf-8 |
Location | L'URI de la ressource créée, après un 201 | Location: /v1/fiches/13 |
ETag | L'empreinte de la version de la ressource | ETag: "1434c386cc94c088" |
Cache-Control | Ce que les caches ont le droit de faire | Cache-Control: private, max-age=60 |
Allow | Les méthodes acceptées, avec un 405 | Allow: GET, POST |
WWW-Authenticate | Comment s'authentifier, avec un 401 | WWW-Authenticate: Bearer realm="api" |
Retry-After | Dans combien de temps réessayer, avec un 429 ou un 503 | Retry-After: 30 |
Access-Control-Allow-Origin | Les origines autorisées à lire la réponse (CORS) | Access-Control-Allow-Origin: https://frais.gsb.example |
La négociation de contenu
Le client dit ce qu'il envoie avec Content-Type et ce qu'il accepte en retour avec Accept. Le serveur s'adapte, ou refuse avec un code précis :
- le client envoie du XML à une API qui n'accepte que du JSON :
415 Unsupported Media Type; - le client demande
Accept: text/csvà une API qui ne sait produire que du JSON :406 Not Acceptable. En pratique, beaucoup d'API ignorentAcceptet renvoient du JSON quoi qu'il arrive.
Un type de contenu (media type, anciennement « type MIME ») s'écrit type/sous-type, avec des paramètres éventuels : application/json, application/problem+json (une erreur au format de la RFC 9457, chapitre 6), application/xml, text/csv, multipart/form-data (un formulaire avec fichiers), application/x-www-form-urlencoded (un formulaire HTML classique).
Sans état : chaque requête se suffit à elle-même
HTTP est un protocole sans état : le serveur traite chaque requête indépendamment des précédentes. Il ne « se souvient » pas que Lucas s'est connecté il y a cinq minutes. Pour que l'utilisateur reste connecté, il faut donc que chaque requête transporte une preuve d'identité :
- un cookie de session, envoyé automatiquement par le navigateur, qui renvoie à une session stockée sur le serveur : c'est le modèle des sites PHP classiques (sessions et cookies en PHP) ;
- un jeton dans l'en-tête
Authorization, ajouté par le code du client : c'est le modèle habituel des API (chapitre 9).
Une API REST pousse ce principe au bout : le serveur ne stocke aucun contexte de conversation. Le bénéfice est concret : n'importe quel serveur d'un groupe peut traiter n'importe quelle requête, ce qui permet d'en ajouter quand la charge augmente.
Le cache et les requêtes conditionnelles
Relire une ressource qui n'a pas changé gaspille de la bande passante, surtout sur mobile. HTTP prévoit deux mécanismes.
Cache-Control dit combien de temps la réponse reste fraîche. L'API Découpage administratif renvoie public, max-age=3600 : n'importe quel cache (le navigateur, un proxy) peut la réutiliser pendant une heure sans redemander. Une fiche de frais, personnelle et changeante, reçoit plutôt private (seul le navigateur de l'utilisateur la garde) ou no-store (personne ne la garde) pour une donnée sensible.
L'ETag est une empreinte de la version de la ressource. Le client la garde et la renvoie dans If-None-Match à la requête suivante. Si la ressource n'a pas changé, le serveur répond 304 Not Modified, sans corps : le client réutilise sa copie.
GET /v1/fiches/12 HTTP/1.1
Host: api.gsb.example
Authorization: Bearer eyJhbGci…
If-None-Match: "1434c386cc94c088"
HTTP/1.1 304 Not Modified
ETag: "1434c386cc94c088"
L'ETag sert aussi à éviter que deux personnes s'écrasent mutuellement leurs modifications, avec If-Match et le code 412 : c'est le verrouillage optimiste, présenté au chapitre 6. Un ETag préfixé par W/ (comme celui de geo.api.gouv.fr) est dit faible : il signale une représentation équivalente, pas forcément identique octet pour octet.
HTTPS : HTTP dans un tunnel chiffré
HTTPS, c'est HTTP transporté dans une connexion TLS. TLS apporte trois garanties : la confidentialité (personne sur le chemin ne lit la requête, ni le jeton qu'elle contient), l'intégrité (personne ne la modifie en route) et l'authentification du serveur (son certificat prouve que tu parles au vrai api.gsb.example). Une API qui transporte des jetons ou des données personnelles doit être servie en HTTPS, avec TLS 1.2 au minimum et TLS 1.3 de préférence. Le fonctionnement de la poignée de main TLS est expliqué dans le chapitre TLS 1.3 et HTTPS du cours de cryptographie.
Exercices
Exercice 1 sur 5 Décomposer une URL Réussi À revoir
Décompose l'URL https://api.gsb.example/v1/fiches/12/frais-hors-forfait?tri=-date&page=2#justificatif : schéma, hôte, port, chemin, paramètres de la chaîne de requête, fragment. Quelle partie n'arrive jamais au serveur ?
Voir la réponseMasquer la réponse de l'exercice 1
- Schéma :
https - Hôte :
api.gsb.example - Port : non écrit, donc 443, le port par défaut de HTTPS
- Chemin :
/v1/fiches/12/frais-hors-forfait - Paramètres :
trivaut-dateetpagevaut2 - Fragment :
justificatif
Le fragment reste dans le navigateur : il n'est jamais envoyé au serveur. Il ne peut donc pas servir à transmettre un paramètre à une API.
Tu avais trouvé ?
Exercice 2 sur 5 Lire un échange HTTP Réussi À revoir
Voici un échange capturé dans l'onglet Réseau :
POST /v1/fiches HTTP/1.1
Host: api.gsb.example
Authorization: Bearer eyJhbGci…
Content-Type: application/json
Content-Length: 18
{"mois":"2026-11"}
HTTP/1.1 201 Created
Location: /v1/fiches/13
Content-Type: application/json; charset=utf-8
{"id":13,"visiteurId":7,"mois":"2026-11","etat":"CR","montantValide":null}
- Que demande le client ?
- La demande a-t-elle réussi ? Comment le sais-tu ?
- Quelle est l'adresse de la nouvelle ressource ?
- À quoi correspond la valeur 18 de
Content-Length? - Le client renvoie exactement la même requête par erreur. Que risque-t-il de se passer ?
Voir la réponseMasquer la réponse de l'exercice 2
- La création d'une fiche de frais pour novembre 2026 (
POSTsur la collection/v1/fiches), au nom du visiteur authentifié par le jeton. - Oui : le code
201 Createdsignifie qu'une ressource a été créée. /v1/fiches/13, donnée par l'en-têteLocation(et l'id13 du corps).- La taille du corps de la requête en octets :
{"mois":"2026-11"}compte 18 caractères, tous codés sur un octet en UTF-8. POSTn'est pas idempotent : sans protection, une deuxième fiche de novembre serait créée. Ici, la règle de gestion « une seule fiche par visiteur et par mois » permet à l'API de refuser le doublon avec un409 Conflict. Sans règle de ce genre, il faudrait une clé d'idempotence.
Tu avais trouvé ?
Exercice 3 sur 5 Sûre, idempotente ou ni l'un ni l'autre ? Réussi À revoir
Pour chaque requête, dis si elle est sûre, idempotente, ou ni l'un ni l'autre, puis si un client peut la renvoyer sans risque après une coupure réseau.
GET /v1/fiches?etat=CLDELETE /v1/frais-hors-forfait/58POST /v1/fiches/12/frais-hors-forfaitavec un repas de 23,50 €PUT /v1/visiteurs/7avec le visiteur completPATCH /v1/fiches/12avec{"etat":"CL"}
Voir la réponseMasquer la réponse de l'exercice 3
- Sûre et idempotente : une lecture. Renvoi sans risque.
- Idempotente, pas sûre : la suppression modifie le serveur, mais la répéter laisse le même état (le frais n'existe plus ; le second appel reçoit
404). Renvoi sans risque pour les données. - Ni sûre ni idempotente : chaque envoi ajoute un frais hors forfait. Un renvoi crée un doublon de 23,50 €, sauf clé d'idempotence.
- Idempotente, pas sûre : le visiteur est remplacé par le même contenu à chaque envoi. Renvoi sans risque.
- Idempotente en pratique, mais pas par définition de
PATCH: ce corps fixe une valeur absolue, donc le répéter ne change rien de plus. Un second envoi recevrait sans doute409(la fiche est déjà clôturée), sans dommage.
Tu avais trouvé ?
Exercice 4 sur 5 PUT ou PATCH ? Réussi À revoir
Le visiteur 7 vaut {"id":7,"nom":"Martin","prenom":"Lucas","telephone":"0601020304","region":"Rhône"}.
- Que contient la ressource après
PATCH /v1/visiteurs/7avec{"region":"Isère"}? - Quel est le risque si le front envoie
{"region":"Isère"}avecPUTà une API qui applique strictement la sémantique dePUT? - Le front veut supprimer le numéro de téléphone avec JSON Merge Patch. Que doit-il envoyer ?
Voir la réponseMasquer la réponse de l'exercice 4
{"id":7,"nom":"Martin","prenom":"Lucas","telephone":"0601020304","region":"Isère"}: seule la région change.PUTremplace toute la ressource : le nom, le prénom et le téléphone disparaîtraient. Une API correcte refuse la requête avec422parce que les champs obligatoires manquent ; une API mal écrite les met ànull.PATCH /v1/visiteurs/7avecContent-Type: application/merge-patch+jsonet le corps{"telephone":null}: en Merge Patch,nullsignifie « supprimer ce champ ».
Tu avais trouvé ?
Exercice 5 sur 5 Requête conditionnelle Réussi À revoir
L'application mobile a reçu hier la fiche 12 avec l'en-tête ETag: "1434c386cc94c088". Ce matin, elle la redemande.
- Quel en-tête doit-elle ajouter à sa requête, avec quelle valeur ?
- Que répond le serveur si la fiche n'a pas changé ? Et si elle a changé ?
- Quel est l'intérêt pour un visiteur en déplacement ?
Voir la réponseMasquer la réponse de l'exercice 5
If-None-Match: "1434c386cc94c088".- Fiche inchangée :
304 Not Modified, sans corps ; l'application réutilise sa copie. Fiche modifiée :200 OKavec la nouvelle version et un nouvelETag. - Moins de données transférées sur un réseau mobile lent ou limité, et un affichage plus rapide : la réponse
304ne contient que quelques en-têtes.
Tu avais trouvé ?