Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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 :

text
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
PartieRôleDans l'exemple
SchémaLe protocole : http ou httpshttps
HôteLe nom du serveur, résolu par le DNS en adresse IPapi.gsb.example
PortFacultatif : 443 par défaut en HTTPS, 80 en HTTP443 (on ne l'écrit jamais en pratique)
CheminLa 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
FragmentUne 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 :

http
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

  1. 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.
  2. Les en-têtes (headers) : une ligne Nom: valeur par information. Host est 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é.
  3. Une ligne vide, qui marque la fin des en-têtes.
  4. Le corps (body), facultatif. Une requête GET n'en a pas ; une requête POST, PUT ou PATCH en a presque toujours un, accompagné de l'en-tête Content-Type qui dit son format.

La réponse : trois parties

Le serveur répond :

http
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}
  1. 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).
  2. 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.
  3. Le corps, séparé des en-têtes par une ligne vide : la représentation de la fiche en JSON.
Requête du clientRéponse de l'APIligne de requêteGET /v1/fiches/12 HTTP/1.1en-têtesHost: api.gsb.exampleAccept: application/jsonAuthorization: Bearer eyJ…ligne vide : fin des en-têtescorpsaucun corps pour un GETligne de statutHTTP/1.1 200 OKen-têtesContent-Type: application/jsonContent-Length: 74ETag: "1434c386cc94c088"ligne vide : fin des en-têtescorps (JSON){"id":12,"visiteurId":7, "mois":"2026-10","etat":"CR",…}
Une requête et sa réponse : ligne de départ, en-têtes, ligne vide, corps

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 <) :

bash
curl -v "https://geo.api.gouv.fr/communes?codePostal=69001&fields=nom,population" -H "Accept: application/json"
text
> 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.

VersionTransportCe qu'elle apporte
HTTP/1.1 (1997, révisé en 2022)Texte sur TCPLisible à l'œil nu ; une requête à la fois par connexion
HTTP/2 (2015)Binaire sur TCP, presque toujours avec TLSPlusieurs requêtes en parallèle sur une seule connexion (multiplexage), en-têtes compressés
HTTP/3 (2022)Binaire sur QUIC, au-dessus d'UDPConnexion 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éthodeUsage dans une API RESTCorps de requêteRéponse typique
GETLire une ressource ou une listeNon200 OK et la ressource
POSTCréer une ressource dans une collection, ou déclencher un traitementOui201 Created et l'en-tête Location
PUTRemplacer entièrement une ressource (ou la créer à une URI choisie par le client)Oui, la ressource complète200 OK ou 204 No Content
PATCHModifier une partie d'une ressourceOui, les seuls champs à changer200 OK et la ressource modifiée
DELETESupprimer une ressourceNon204 No Content

Trois autres méthodes servent en coulisses :

  • HEAD : comme GET, 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) ;
  • TRACE et CONNECT : 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/12 avec 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 renvoie 404. 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/fiches n'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éthodeSûreIdempotenteRéponse pouvant être mise en cache
GETOuiOuiOui
HEADOuiOuiOui
OPTIONSOuiOuiNon
POSTNonNonRarement (seulement si le serveur l'indique explicitement)
PUTNonOuiNon
PATCHNonNon garantieNon
DELETENonOuiNon

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 :

json
{ "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 de PUT effacerait le nom, le prénom et la région, ou plus raisonnablement refuserait la requête avec 422 parce 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êteRôleExemple
HostLe serveur visé (obligatoire en HTTP/1.1)Host: api.gsb.example
AcceptLes formats de réponse que le client sait lireAccept: application/json
Content-TypeLe format du corps envoyéContent-Type: application/json
AuthorizationLa preuve d'identité du clientAuthorization: Bearer eyJhbGci…
User-AgentLe logiciel client, utile dans les journauxUser-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"
OriginL'origine de la page qui fait l'appel (ajouté par le navigateur)Origin: https://frais.gsb.example
Idempotency-KeyUn identifiant unique pour rejouer un POST sans doublonIdempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324

Dans la réponse

En-têteRôleExemple
Content-TypeLe format du corps renvoyéContent-Type: application/json; charset=utf-8
LocationL'URI de la ressource créée, après un 201Location: /v1/fiches/13
ETagL'empreinte de la version de la ressourceETag: "1434c386cc94c088"
Cache-ControlCe que les caches ont le droit de faireCache-Control: private, max-age=60
AllowLes méthodes acceptées, avec un 405Allow: GET, POST
WWW-AuthenticateComment s'authentifier, avec un 401WWW-Authenticate: Bearer realm="api"
Retry-AfterDans combien de temps réessayer, avec un 429 ou un 503Retry-After: 30
Access-Control-Allow-OriginLes 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 ignorent Accept et 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.

http
GET /v1/fiches/12 HTTP/1.1
Host: api.gsb.example
Authorization: Bearer eyJhbGci…
If-None-Match: "1434c386cc94c088"
http
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 : tri vaut -date et page vaut 2
  • 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 :

http
POST /v1/fiches HTTP/1.1
Host: api.gsb.example
Authorization: Bearer eyJhbGci…
Content-Type: application/json
Content-Length: 18

{"mois":"2026-11"}
http
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}
  1. Que demande le client ?
  2. La demande a-t-elle réussi ? Comment le sais-tu ?
  3. Quelle est l'adresse de la nouvelle ressource ?
  4. À quoi correspond la valeur 18 de Content-Length ?
  5. 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
  1. La création d'une fiche de frais pour novembre 2026 (POST sur la collection /v1/fiches), au nom du visiteur authentifié par le jeton.
  2. Oui : le code 201 Created signifie qu'une ressource a été créée.
  3. /v1/fiches/13, donnée par l'en-tête Location (et l'id 13 du corps).
  4. 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.
  5. POST n'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 un 409 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.

  1. GET /v1/fiches?etat=CL
  2. DELETE /v1/frais-hors-forfait/58
  3. POST /v1/fiches/12/frais-hors-forfait avec un repas de 23,50 €
  4. PUT /v1/visiteurs/7 avec le visiteur complet
  5. PATCH /v1/fiches/12 avec {"etat":"CL"}
Voir la réponseMasquer la réponse de l'exercice 3
  1. Sûre et idempotente : une lecture. Renvoi sans risque.
  2. 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.
  3. 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.
  4. Idempotente, pas sûre : le visiteur est remplacé par le même contenu à chaque envoi. Renvoi sans risque.
  5. 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 doute 409 (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"}.

  1. Que contient la ressource après PATCH /v1/visiteurs/7 avec {"region":"Isère"} ?
  2. Quel est le risque si le front envoie {"region":"Isère"} avec PUT à une API qui applique strictement la sémantique de PUT ?
  3. 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
  1. {"id":7,"nom":"Martin","prenom":"Lucas","telephone":"0601020304","region":"Isère"} : seule la région change.
  2. PUT remplace toute la ressource : le nom, le prénom et le téléphone disparaîtraient. Une API correcte refuse la requête avec 422 parce que les champs obligatoires manquent ; une API mal écrite les met à null.
  3. PATCH /v1/visiteurs/7 avec Content-Type: application/merge-patch+json et le corps {"telephone":null} : en Merge Patch, null signifie « 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.

  1. Quel en-tête doit-elle ajouter à sa requête, avec quelle valeur ?
  2. Que répond le serveur si la fiche n'a pas changé ? Et si elle a changé ?
  3. Quel est l'intérêt pour un visiteur en déplacement ?
Voir la réponseMasquer la réponse de l'exercice 5
  1. If-None-Match: "1434c386cc94c088".
  2. Fiche inchangée : 304 Not Modified, sans corps ; l'application réutilise sa copie. Fiche modifiée : 200 OK avec la nouvelle version et un nouvel ETag.
  3. Moins de données transférées sur un réseau mobile lent ou limité, et un affichage plus rapide : la réponse 304 ne contient que quelques en-têtes.

Tu avais trouvé ?

Chapitre terminé !

Fais les 5 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Codes de statut HTTP : 200, 201, 400, 401, 403, 404, 500Revoir le sommaire du cours