HTTP, le langage commun du Web

Quand vous ouvrez une page, cliquez sur un lien, envoyez un formulaire ou appelez une API avec fetch, votre navigateur échange des messages avec un serveur. Le protocole qui organise cet échange s’appelle HTTP, pour HyperText Transfer Protocol.

HTTP n’est pas un langage de programmation. C’est un protocole de communication : il définit la forme des messages envoyés, la manière de demander une ressource, la structure des réponses et les codes qui indiquent si tout s’est bien passé.

Dans un article précédent, nous avons vu comment fonctionne le Web entre client, serveur, navigateur et HTTP. Ici, nous allons regarder HTTP plus précisément : requêtes, réponses, méthodes, en-têtes, statuts, puis passage à HTTPS.

Une requête HTTP : demander quelque chose au serveur

Une requête HTTP est un message envoyé par le client, le plus souvent le navigateur, vers un serveur. Elle contient généralement quatre éléments importants :

  • une méthode, comme GET ou POST ;
  • une URL ou un chemin ;
  • des en-têtes, aussi appelés headers ;
  • éventuellement un corps de requête, aussi appelé body.

Exemple simplifié d’une requête :

GET /articles/qu-est-ce-qu-une-url HTTP/1.1
Host: example.com
Accept: text/html
User-Agent: Mozilla/5.0

Cette requête signifie : « Je veux récupérer la ressource située à /articles/qu-est-ce-qu-une-url sur le domaine example.com, et j’accepte de recevoir du HTML. »

Si vous avez besoin de revoir la structure d’une adresse web, l’article Qu’est-ce qu’une URL ? complète très bien ce sujet.

Les principales méthodes HTTP

Les méthodes HTTP indiquent l’intention de la requête. Les plus fréquentes sont :

  • GET : récupérer une ressource ;
  • POST : envoyer des données pour créer ou déclencher une action ;
  • PUT : remplacer une ressource ;
  • PATCH : modifier partiellement une ressource ;
  • DELETE : supprimer une ressource ;
  • HEAD : récupérer seulement les en-têtes d’une réponse ;
  • OPTIONS : demander quelles opérations sont autorisées.

Dans une API, on retrouve souvent une organisation de ce type :

async function getUser(id: string) {
  const response = await fetch(`/api/users/${id}`, {
    method: 'GET',
    headers: {
      Accept: 'application/json'
    }
  });

  if (!response.ok) {
    throw new Error(`Erreur HTTP ${response.status}`);
  }

  return response.json();
}

Ici, fetch envoie une requête GET. La réponse est ensuite vérifiée avec response.ok, qui vaut true pour les statuts compris entre 200 et 299.

Pour aller plus loin sur la fiabilisation des données reçues depuis une API, vous pouvez lire Typage des données API en TypeScript.

Une réponse HTTP : le serveur répond avec un statut et du contenu

Après avoir reçu une requête, le serveur renvoie une réponse HTTP. Elle contient généralement :

  • une version du protocole ;
  • un code de statut ;
  • des en-têtes ;
  • éventuellement un corps de réponse.

Exemple simplifié :

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=3600

<!doctype html>
<html lang="fr">
  <head>
    <title>Page exemple</title>
  </head>
  <body>
    <h1>Bonjour</h1>
  </body>
</html>

Le code 200 OK indique que la requête a réussi. L’en-tête Content-Type indique que le contenu retourné est du HTML. L’en-tête Cache-Control donne des indications au navigateur sur la durée pendant laquelle il peut conserver cette réponse en cache.

Le cache HTTP est un sujet à part entière. Il ne faut pas le confondre avec un cache applicatif côté frontend, comme expliqué dans Cache frontend : rendre vos interfaces plus rapides sans données périmées.

Comprendre les codes de statut HTTP

Les codes de statut HTTP sont regroupés par familles. Le premier chiffre donne une indication générale.

2xx : succès

Les codes 2xx indiquent que la requête a réussi.

  • 200 OK : réponse réussie classique ;
  • 201 Created : ressource créée, fréquent après un POST ;
  • 204 No Content : succès sans contenu retourné.

Un 204 est utile, par exemple, après une suppression réussie :

async function deleteProject(id: string) {
  const response = await fetch(`/api/projects/${id}`, {
    method: 'DELETE'
  });

  if (response.status !== 204) {
    throw new Error('La suppression a échoué');
  }
}

3xx : redirections

Les codes 3xx indiquent que le client doit aller ailleurs pour obtenir la ressource.

  • 301 Moved Permanently : redirection permanente ;
  • 302 Found : redirection temporaire ;
  • 304 Not Modified : la ressource n’a pas changé, le cache peut être utilisé.

Les redirections sont importantes pour le SEO, les migrations de pages et la cohérence des URLs.

4xx : erreurs côté client

Les codes 4xx signalent que la requête pose problème côté client.

  • 400 Bad Request : requête invalide ;
  • 401 Unauthorized : authentification nécessaire ou invalide ;
  • 403 Forbidden : accès refusé malgré une requête comprise ;
  • 404 Not Found : ressource introuvable ;
  • 409 Conflict : conflit d’état, par exemple une ressource déjà existante ;
  • 422 Unprocessable Content : données compréhensibles mais invalides.

Exemple de gestion d’erreur API :

async function createAccount(email: string, password: string) {
  const response = await fetch('/api/accounts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json'
    },
    body: JSON.stringify({ email, password })
  });

  if (response.status === 409) {
    return { ok: false, reason: 'EMAIL_ALREADY_USED' as const };
  }

  if (!response.ok) {
    return { ok: false, reason: 'UNKNOWN_ERROR' as const };
  }

  return { ok: true, data: await response.json() };
}

Cette approche rejoint les principes du Result pattern en TypeScript, qui consiste à représenter explicitement les succès et les erreurs au lieu de tout traiter avec des exceptions.

5xx : erreurs côté serveur

Les codes 5xx indiquent que le serveur n’a pas pu traiter correctement une requête pourtant valide.

  • 500 Internal Server Error : erreur serveur générique ;
  • 502 Bad Gateway : problème entre serveurs intermédiaires ;
  • 503 Service Unavailable : service temporairement indisponible ;
  • 504 Gateway Timeout : délai dépassé entre serveurs.

Un bon frontend doit afficher un message compréhensible, sans exposer de détails internes comme une stack trace, un chemin serveur ou une requête SQL.

Les en-têtes HTTP : des métadonnées essentielles

Les headers HTTP précisent le contexte de l’échange. Ils peuvent concerner le format, le cache, la langue, la sécurité, l’authentification ou la compression.

Exemples courants :

Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOi...
Cache-Control: no-store
Accept-Encoding: br, gzip

Content-Type décrit ce qui est envoyé. Accept décrit ce que le client préfère recevoir. Authorization transporte souvent un jeton d’authentification. Accept-Encoding permet au navigateur d’annoncer les formats de compression qu’il accepte, sujet traité plus en détail dans Compression HTTP : accélérer vos pages avec Brotli, gzip et les bons en-têtes.

Certains en-têtes renforcent directement la sécurité d’une application web : Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options ou encore Permissions-Policy. Vous pouvez approfondir ces points avec Content Security Policy et Permissions Policy.

HTTP est sans état

HTTP est dit stateless, c’est-à-dire « sans état ». Chaque requête est indépendante. Le serveur ne se souvient pas automatiquement de la requête précédente.

Pour maintenir une session utilisateur, on ajoute donc des mécanismes complémentaires :

  • cookies de session ;
  • jetons d’authentification ;
  • stockage serveur ;
  • en-têtes d’autorisation ;
  • stratégies de renouvellement de session.

Cela explique pourquoi l’authentification web ne repose pas uniquement sur HTTP, mais sur une combinaison entre protocole, navigateur, serveur, cookies, sécurité TLS et logique applicative.

HTTPS : HTTP sécurisé par TLS

HTTPS signifie HTTP over TLS. Le protocole HTTP reste présent, mais il circule dans un canal chiffré grâce à TLS, anciennement connu sous le nom SSL.

HTTPS apporte trois garanties majeures.

D’abord, la confidentialité : une personne qui intercepte le trafic réseau ne peut pas lire facilement les contenus échangés. C’est indispensable pour les mots de passe, les données personnelles, les sessions et les paiements.

Ensuite, l’intégrité : les données ne peuvent pas être modifiées silencieusement pendant le transport sans que cela soit détecté.

Enfin, l’authenticité : le certificat TLS permet au navigateur de vérifier qu’il parle bien au serveur correspondant au domaine demandé, et non à un intermédiaire malveillant.

De manière simplifiée, une connexion HTTPS se déroule ainsi :

Navigateur -> Serveur : je veux ouvrir une connexion sécurisée
Serveur -> Navigateur : voici mon certificat
Navigateur : je vérifie le certificat
Navigateur et serveur : nous négocions des clés de chiffrement
HTTP circule ensuite dans ce tunnel sécurisé

En production, HTTPS n’est plus optionnel. Les navigateurs signalent les sites non sécurisés, de nombreuses API web exigent un contexte sécurisé, et les moteurs de recherche tiennent compte de la sécurité dans l’expérience globale de la page.

Ce que HTTPS ne protège pas

HTTPS est indispensable, mais il ne rend pas une application automatiquement sûre.

Il ne protège pas contre une faille XSS dans votre code JavaScript. Il ne valide pas les données envoyées à votre API. Il n’empêche pas un mot de passe faible. Il ne remplace pas une politique d’autorisations correcte côté serveur.

Autrement dit, HTTPS sécurise le transport. Il ne sécurise pas toute l’application.

Pour une application robuste, il faut aussi valider les entrées, contrôler les permissions, limiter les capacités du navigateur, éviter les injections DOM et configurer des en-têtes de sécurité adaptés. Les articles sur Trusted Types, Passkeys et WebAuthn et Content Security Policy complètent cette approche.

Déboguer HTTP avec les DevTools

Les outils de développement du navigateur sont indispensables pour comprendre ce qui se passe réellement. Dans l’onglet Network, vous pouvez inspecter :

  • l’URL appelée ;
  • la méthode utilisée ;
  • le statut retourné ;
  • les headers de requête ;
  • les headers de réponse ;
  • le body envoyé ;
  • le contenu reçu ;
  • le temps de réponse ;
  • l’utilisation du cache.

Quand une page ne fonctionne pas, cette inspection donne souvent la réponse plus vite qu’une lecture du code. Une erreur 404 indique une route incorrecte ou absente. Une erreur 401 pointe vers l’authentification. Une erreur CORS apparaît souvent lorsqu’une API refuse les requêtes depuis l’origine de votre frontend. Une erreur 500 nécessite d’aller voir les logs serveur.

Bonnes pratiques à retenir

Utilisez les méthodes HTTP selon leur intention. Ne créez pas une ressource avec un GET, même si cela semble fonctionner. Retournez des codes de statut précis : un 404 est plus utile qu’un 200 accompagné d’un message d’erreur vague dans le JSON.

Déclarez correctement Content-Type et Accept. Évitez de mettre des informations sensibles dans les paramètres d’URL, car elles peuvent apparaître dans l’historique, les logs ou certains outils d’analyse. Préférez HTTPS partout, y compris sur les environnements de préproduction quand c’est possible.

Enfin, traitez HTTP comme un contrat entre client et serveur. Plus ce contrat est explicite, plus votre application est facile à tester, maintenir et faire évoluer.

Conclusion

HTTP organise les échanges fondamentaux du Web : le client demande, le serveur répond, et les codes de statut décrivent le résultat. HTTPS ajoute une couche de sécurité indispensable en chiffrant le transport et en vérifiant l’identité du serveur.

Comprendre ces mécanismes permet de mieux lire les erreurs, concevoir des APIs cohérentes, configurer des en-têtes utiles et construire des applications web plus fiables. Même avec des frameworks modernes comme React, Vue, Nuxt ou Next, ces bases restent incontournables : toute interface web finit toujours par envoyer et recevoir des requêtes HTTP.