Jour 16 Day 16 · jeudi 20 août 2026 Thursday 20 August 2026 Web Fondamental

HTTP de A à Z HTTP from A to Z

Méthodes, codes de statut, cookies, HTTP/2 et 3 : le protocole que vous utilisez cent fois par jour — et sur lequel un recruteur peut creuser pendant vingt minutes. Methods, status codes, cookies, HTTP/2 and 3: the protocol you use a hundred times a day — and one an interviewer can dig into for twenty minutes.

L’essentiel

HTTP (HyperText Transfer Protocol) est le protocole requête/réponse du web : un client (navigateur, curl, appli mobile) envoie une requête, un serveur renvoie une réponse, et la conversation s’arrête là. Il est stateless : chaque requête est autonome, le serveur n’a aucune mémoire de la précédente — tout ce qui ressemble à une « session » est reconstruit par-dessus (cookies, tokens).

Une requête tient en trois parties : une ligne de requête (méthode + chemin + version, ex. GET /users/42 HTTP/1.1), des headers (paires clé-valeur : Host, Accept, Authorization…), et un body optionnel. La réponse est symétrique : ligne de statut (HTTP/1.1 200 OK), headers, body.

Les méthodes portent une sémantique : GET lit (safe, sans effet de bord), POST crée ou déclenche, PUT remplace intégralement, PATCH modifie partiellement, DELETE supprime, HEAD = GET sans body, OPTIONS interroge les capacités (c’est lui derrière le preflight CORS). Une méthode est idempotente si la rejouer N fois produit le même état qu’une fois : GET, PUT, DELETE le sont ; POST ne l’est pas — d’où le danger de re-soumettre un paiement.

Les codes de statut à connaître par cœur, par familles :

CodeSignificationLe réflexe
200OKSuccès générique
201CreatedCréation réussie (+ header Location)
204No ContentSuccès sans body (DELETE typique)
301Moved PermanentlyRedirection définitive, cachée par le navigateur
304Not ModifiedCache encore valide, pas de body renvoyé
400Bad RequestRequête malformée (syntaxe, JSON invalide)
401UnauthorizedNon authentifié (mal nommé !)
403ForbiddenAuthentifié mais pas les droits
404Not FoundRessource inexistante
409ConflictConflit d’état (ex. email déjà pris)
422Unprocessable EntitySyntaxe OK, validation métier KO
429Too Many RequestsRate limiting (+ Retry-After)
500Internal Server ErrorBug côté serveur
502Bad GatewayLe reverse proxy n’a pas eu de réponse valide du backend
503Service UnavailableServeur surchargé ou en maintenance

Comment ça marche

Un aller-retour complet, tel que curl -v le montre :

Client                                    Serveur
  │  GET /users/42 HTTP/1.1                  │
  │  Host: api.example.com                   │
  │  Accept: application/json                │
  │─────────────────────────────────────────▶│
  │                                          │ routing,
  │                                          │ contrôleur, DB
  │  HTTP/1.1 200 OK                         │
  │  Content-Type: application/json          │
  │  Cache-Control: max-age=60               │
  │                                          │
  │  {"id": 42, "name": "Ada"}               │
  │◀─────────────────────────────────────────│
  │        (connexion TCP gardée ouverte     │
  │         → keep-alive, requête suivante)  │

Sous le capot, HTTP/1.1 circule sur TCP. Ouvrir une connexion TCP coûte un aller-retour (et un handshake TLS en HTTPS), donc depuis HTTP/1.1 la connexion est persistante par défaut (keep-alive) : plusieurs requêtes réutilisent le même tuyau. Mais en 1.1, les requêtes sur une connexion sont séquentielles : une réponse lente bloque toutes les suivantes — c’est le head-of-line blocking. Les navigateurs contournent en ouvrant ~6 connexions par domaine.

HTTP/2 règle ça avec le multiplexing : un seul tube TCP, mais des dizaines de streams binaires entrelacés — plus de blocage au niveau HTTP, plus de compression des headers (HPACK). Reste un HOL blocking au niveau TCP : un paquet perdu bloque tous les streams le temps de la retransmission. HTTP/3 supprime ce dernier verrou en remplaçant TCP par QUIC (sur UDP) : chaque stream est indépendant face à la perte de paquets, TLS 1.3 est intégré au handshake, et la connexion survit à un changement de réseau (Wi-Fi → 4G).

HTTPS = HTTP dans un tunnel TLS. En deux phrases : le client et le serveur négocient version et algorithmes, le serveur prouve son identité avec un certificat signé par une autorité de confiance, et un échange de clés (Diffie-Hellman éphémère) établit une clé de session symétrique. Tout le trafic HTTP est ensuite chiffré et authentifié avec cette clé — en TLS 1.3, le handshake tient en un seul aller-retour.

🎤 En entretien — « HTTP/1.1 vs 2 vs 3 » se résume en une phrase par version : 1.1 = une requête à la fois par connexion ; 2 = multiplexing sur un TCP, mais TCP re-bloque en cas de perte ; 3 = QUIC sur UDP, streams vraiment indépendants. Dire ça calmement vaut tous les détails.

Concepts clés à maîtriser

  • Négociation de contenu : le client annonce ce qu’il accepte (Accept: application/json, Accept-Language: fr, Accept-Encoding: gzip, br), le serveur répond avec ce qu’il a choisi (Content-Type, Content-Encoding) — ou 406 Not Acceptable.
  • Cookies : le serveur envoie Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax, le navigateur le renvoie automatiquement sur chaque requête vers ce domaine. HttpOnly = invisible pour JavaScript (anti-XSS), Secure = HTTPS uniquement, SameSite = protection CSRF. C’est LE mécanisme qui fabrique de l’état sur un protocole stateless.
  • Cache : Cache-Control: max-age=3600 (fraîcheur), puis revalidation avec ETag/If-None-Match → réponse 304 sans body. Énorme levier de perf, souvent oublié des candidats.
  • Idempotence & safety : safe = ne modifie rien (GET, HEAD) ; idempotent = rejouable sans changer le résultat (GET, PUT, DELETE). Les proxies et les clients HTTP s’appuient dessus pour retenter automatiquement — retenter un POST, c’est risquer un doublon.
  • Host et les reverse proxies : le header Host (obligatoire en 1.1) permet à un seul serveur/IP de servir plusieurs domaines. C’est ce que fait Traefik ou nginx pour router vers le bon backend — et pourquoi un backend mal configuré renvoie 502/503.

À décortiquer soi-même avec curl :

curl -v https://api.github.com/users/octocat
# > GET /users/octocat HTTP/2        ← ligne de requête (curl a négocié h2)
# > Host: api.github.com             ← quel site sur cette IP
# > Accept: */*                      ← négociation de contenu
# <                                  ← (lignes '>' = envoyé, '<' = reçu)
# < HTTP/2 200                       ← ligne de statut
# < content-type: application/json; charset=utf-8
# < etag: W/"a1b2c3"                 ← pour revalider le cache ensuite
# < x-ratelimit-remaining: 59        ← rate limiting annoncé en header
# < {"login":"octocat", ...}         ← le body JSON

💡 Réflexe à montrer — face à un bug d’API, dégainer curl -v (ou l’onglet Network) plutôt que relire le code : la moitié des problèmes se lisent dans les headers (mauvais Content-Type, cookie absent, redirect inattendu, CORS).

En entretien

« Quelle différence entre 401 et 403 ? » — 401 Unauthorized signifie en réalité non authentifié : le serveur ne sait pas qui vous êtes (token absent/expiré), réponse accompagnée de WWW-Authenticate. 403 Forbidden : le serveur sait qui vous êtes, mais vous n’avez pas les droits. Se réauthentifier corrige un 401, jamais un 403.

« PUT vs PATCH ? » — PUT remplace la ressource entière (ce qui n’est pas envoyé est effacé) et est idempotent par définition. PATCH applique une modification partielle ; il n’est pas idempotent par contrat (même si en pratique il l’est souvent). Bonus : PUT peut créer la ressource à une URL connue du client.

« Pourquoi POST n’est-il pas idempotent, et quelles conséquences ? » — Rejouer un POST recrée une ressource ou re-déclenche l’action (double commande, double paiement). Conséquences : les clients/proxies ne le retentent pas automatiquement, le navigateur affiche « re-soumettre le formulaire ? », et les API sérieuses proposent une clé d’idempotence (Idempotency-Key, comme Stripe) pour rendre le retry sûr.

« Que change HTTP/2 par rapport à HTTP/1.1 ? » — Protocole binaire, multiplexing de streams sur une seule connexion TCP (fini les 6 connexions par domaine et le HOL blocking HTTP), compression des headers (HPACK), priorisation. Limite restante : un paquet TCP perdu bloque tous les streams — ce que HTTP/3/QUIC résout.

« Comment un serveur garde-t-il une session sur un protocole stateless ? » — Un cookie de session (ID opaque → état stocké côté serveur) ou un token auto-porteur type JWT (état signé côté client, envoyé dans Authorization: Bearer). Comparer : cookie = envoyé automatiquement (attention CSRF), token = à joindre soi-même (attention au stockage XSS-able).

Pièges & idées reçues

⚠️ Piège classique — répondre 200 OK avec {"error": "not found"} dans le body. Les caches, monitorings, retrys et clients générés se basent sur le code de statut, pas sur le body : un vrai 404/422 n’est pas un détail cosmétique.

  • « 401 = pas les droits » — non, c’est 403. Le nom Unauthorized de 401 est un accident historique : lisez-le « Unauthenticated ».
  • « HTTPS cache tout » — le contenu et le chemin sont chiffrés, mais l’IP de destination reste visible, et le nom de domaine fuite via le SNI du handshake TLS (sauf ECH, encore peu déployé). HTTPS ≠ anonymat.
  • GET avec un body : techniquement toléré, mais les proxies et caches l’ignorent ou le rejettent. Une recherche complexe → POST (ou le récent QUERY, encore en draft).
  • Un 301 est mis en cache agressivement par le navigateur : une redirection permanente erronée peut « coller » très longtemps. Tester avec 302/307, promouvoir en 301 ensuite.
  • CORS n’est pas une sécurité serveur : c’est le navigateur qui bloque la lecture cross-origin. curl ou un backend ignorent totalement CORS — ne jamais s’en servir comme contrôle d’accès.

Pour aller plus loin

The essentials

HTTP (HyperText Transfer Protocol) is the request/response protocol of the web: a client (browser, curl, mobile app) sends a request, a server returns a response, and the conversation ends there. It is stateless: every request stands alone, the server has no memory of the previous one — anything that looks like a “session” is rebuilt on top (cookies, tokens).

A request has three parts: a request line (method + path + version, e.g. GET /users/42 HTTP/1.1), headers (key-value pairs: Host, Accept, Authorization…), and an optional body. The response mirrors it: a status line (HTTP/1.1 200 OK), headers, body.

Methods carry semantics: GET reads (safe, no side effects), POST creates or triggers, PUT replaces entirely, PATCH modifies partially, DELETE removes, HEAD = GET without a body, OPTIONS asks about capabilities (it’s the method behind CORS preflight). A method is idempotent if replaying it N times produces the same state as once: GET, PUT, DELETE are; POST is not — hence the danger of resubmitting a payment.

The status codes to know by heart, by family:

CodeMeaningThe reflex
200OKGeneric success
201CreatedSuccessful creation (+ Location header)
204No ContentSuccess without a body (typical DELETE)
301Moved PermanentlyPermanent redirect, cached by the browser
304Not ModifiedCache still valid, no body returned
400Bad RequestMalformed request (syntax, invalid JSON)
401UnauthorizedNot authenticated (badly named!)
403ForbiddenAuthenticated but no permission
404Not FoundResource doesn’t exist
409ConflictState conflict (e.g. email already taken)
422Unprocessable EntitySyntax OK, business validation failed
429Too Many RequestsRate limiting (+ Retry-After)
500Internal Server ErrorServer-side bug
502Bad GatewayReverse proxy got no valid answer from the backend
503Service UnavailableServer overloaded or in maintenance

How it works

One full round trip, as curl -v shows it:

Client                                    Server
  │  GET /users/42 HTTP/1.1                  │
  │  Host: api.example.com                   │
  │  Accept: application/json                │
  │─────────────────────────────────────────▶│
  │                                          │ routing,
  │                                          │ controller, DB
  │  HTTP/1.1 200 OK                         │
  │  Content-Type: application/json          │
  │  Cache-Control: max-age=60               │
  │                                          │
  │  {"id": 42, "name": "Ada"}               │
  │◀─────────────────────────────────────────│
  │        (TCP connection kept open         │
  │         → keep-alive, next request)      │

Under the hood, HTTP/1.1 rides on TCP. Opening a TCP connection costs a round trip (plus a TLS handshake for HTTPS), so since HTTP/1.1 connections are persistent by default (keep-alive): several requests reuse the same pipe. But in 1.1, requests on one connection are sequential: one slow response blocks all the following ones — that’s head-of-line blocking. Browsers work around it by opening ~6 connections per domain.

HTTP/2 fixes this with multiplexing: a single TCP pipe, but dozens of interleaved binary streams — no more HTTP-level blocking, plus header compression (HPACK). One HOL blocking remains at the TCP level: one lost packet stalls every stream until retransmission. HTTP/3 removes that last lock by replacing TCP with QUIC (over UDP): each stream is independent under packet loss, TLS 1.3 is built into the handshake, and the connection survives a network change (Wi-Fi → 4G).

HTTPS = HTTP inside a TLS tunnel. In two sentences: the client and server negotiate version and algorithms, the server proves its identity with a certificate signed by a trusted authority, and a key exchange (ephemeral Diffie-Hellman) establishes a symmetric session key. All HTTP traffic is then encrypted and authenticated with that key — in TLS 1.3 the handshake fits in a single round trip.

🎤 In an interview — “HTTP/1.1 vs 2 vs 3” boils down to one sentence per version: 1.1 = one request at a time per connection; 2 = multiplexing over one TCP, but TCP re-blocks on loss; 3 = QUIC over UDP, truly independent streams. Saying that calmly is worth all the details.

Key concepts to master

  • Content negotiation: the client announces what it accepts (Accept: application/json, Accept-Language: fr, Accept-Encoding: gzip, br), the server answers with what it picked (Content-Type, Content-Encoding) — or 406 Not Acceptable.
  • Cookies: the server sends Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax, and the browser automatically sends it back on every request to that domain. HttpOnly = invisible to JavaScript (anti-XSS), Secure = HTTPS only, SameSite = CSRF protection. This is THE mechanism that manufactures state on a stateless protocol.
  • Caching: Cache-Control: max-age=3600 (freshness), then revalidation with ETag/If-None-Match → a 304 response with no body. A huge performance lever candidates often forget.
  • Idempotence & safety: safe = modifies nothing (GET, HEAD); idempotent = replayable without changing the outcome (GET, PUT, DELETE). Proxies and HTTP clients rely on this to retry automatically — retrying a POST risks a duplicate.
  • Host and reverse proxies: the Host header (mandatory in 1.1) lets a single server/IP serve several domains. That’s what Traefik or nginx use to route to the right backend — and why a misconfigured backend returns 502/503.

Dissect it yourself with curl:

curl -v https://api.github.com/users/octocat
# > GET /users/octocat HTTP/2        ← request line (curl negotiated h2)
# > Host: api.github.com             ← which site on this IP
# > Accept: */*                      ← content negotiation
# <                                  ← ('>' lines = sent, '<' = received)
# < HTTP/2 200                       ← status line
# < content-type: application/json; charset=utf-8
# < etag: W/"a1b2c3"                 ← to revalidate the cache later
# < x-ratelimit-remaining: 59        ← rate limiting announced in a header
# < {"login":"octocat", ...}         ← the JSON body

💡 Reflex to show — facing an API bug, reach for curl -v (or the Network tab) before rereading the code: half the problems are readable in the headers (wrong Content-Type, missing cookie, unexpected redirect, CORS).

In an interview

“What’s the difference between 401 and 403?” — 401 Unauthorized actually means not authenticated: the server doesn’t know who you are (missing/expired token), and the response carries WWW-Authenticate. 403 Forbidden: the server knows who you are but you lack permission. Re-authenticating fixes a 401, never a 403.

“PUT vs PATCH?” — PUT replaces the whole resource (anything not sent is wiped) and is idempotent by definition. PATCH applies a partial modification; it is not idempotent by contract (even if in practice it often is). Bonus: PUT can create the resource at a URL the client knows.

“Why isn’t POST idempotent, and what are the consequences?” — Replaying a POST re-creates a resource or re-triggers the action (double order, double payment). Consequences: clients/proxies don’t auto-retry it, the browser asks “resubmit the form?”, and serious APIs offer an idempotency key (Idempotency-Key, like Stripe) to make retries safe.

“What does HTTP/2 change compared to HTTP/1.1?” — Binary protocol, multiplexing of streams over a single TCP connection (no more 6 connections per domain and HTTP HOL blocking), header compression (HPACK), prioritization. Remaining limit: one lost TCP packet stalls all streams — which HTTP/3/QUIC solves.

“How does a server keep a session on a stateless protocol?” — A session cookie (opaque ID → state stored server-side) or a self-contained token like a JWT (signed state on the client, sent in Authorization: Bearer). Compare: cookie = sent automatically (watch out for CSRF), token = attached manually (watch out for XSS-able storage).

Pitfalls & misconceptions

⚠️ Classic trap — returning 200 OK with {"error": "not found"} in the body. Caches, monitoring, retries and generated clients rely on the status code, not the body: a real 404/422 is not a cosmetic detail.

  • “401 = no permission” — no, that’s 403. The Unauthorized name of 401 is a historical accident: read it as “Unauthenticated”.
  • “HTTPS hides everything” — content and path are encrypted, but the destination IP stays visible, and the domain name leaks through the TLS handshake’s SNI (unless ECH, still barely deployed). HTTPS ≠ anonymity.
  • GET with a body: technically tolerated, but proxies and caches ignore or reject it. A complex search → POST (or the recent QUERY method, still a draft).
  • A 301 is cached aggressively by the browser: a wrong permanent redirect can “stick” for a very long time. Test with 302/307, promote to 301 afterwards.
  • CORS is not server security: it’s the browser that blocks cross-origin reads. curl or a backend ignore CORS entirely — never use it as access control.

Going further

S'entraîner sur ce sujet → Practice this topic →