Passer au contenu principal

🚦 Codes de statut HTTP

📖 Définition

Les codes de statut HTTP sont des nombres à trois chiffres indiquant comment le serveur a traité la requête du client. Ils sont classés en 5 groupes selon le premier chiffre.

🎯 Comprendre par une analogie

Système de commande au restaurant

2xx (Succès) = "Voici votre commande !"
├─ 200 : Plat prêt, bon appétit
├─ 201 : Nouveau plat ajouté au menu
└─ 204 : Table débarrassée

3xx (Redirection) = "Je vais vous orienter"
├─ 301 : Restaurant définitivement déplacé
├─ 302 : Temporairement servi ailleurs
└─ 304 : Vous avez déjà ce plat (cache)

4xx (Erreur client) = "C'est de votre faute"
├─ 400 : Je ne comprends pas votre commande
├─ 401 : Réservé aux membres
├─ 403 : Ce plat n'est pas disponible
├─ 404 : Ce plat n'existe pas
└─ 429 : Trop de commandes

5xx (Erreur serveur) = "C'est notre faute"
├─ 500 : Problème en cuisine
├─ 502 : Connexion à la cuisine impossible
├─ 503 : Trop occupés pour prendre des commandes
└─ 504 : La cuisine ne répond pas

💡 Groupes de codes de statut

┌─────┬─────────────┬────────────────────┐
│ Code│ Catégorie │ Signification │
├─────┼─────────────┼────────────────────┤
│ 1xx │ Information │ Traitement en cours│
│ 2xx │ Succès │ Requête réussie │
│ 3xx │ Redirection │ Action additionnelle │
│ 4xx │ Client │ Erreur client │
│ 5xx │ Serveur │ Erreur serveur │
└─────┴─────────────┴────────────────────┘

✅ 2xx - Succès

200 OK

Réponse de succès la plus courante

Requête :
GET /api/users/123 HTTP/1.1

Réponse :
HTTP/1.1 200 OK
Content-Type: application/json

{
"id": 123,
"name": "Jean Dupont",
"email": "jean@example.com"
}
fetch('https://api.example.com/users/123')
.then(response => {
if (response.status === 200) {
return response.json();
}
})
.then(data => console.log(data));

Cas d'utilisation :

  • Requête GET réussie
  • Requêtes PUT, PATCH réussies
  • Toutes les réponses de succès contenant des données

201 Created

Création de ressource réussie

Requête :
POST /api/users HTTP/1.1
Content-Type: application/json

{
"name": "Jean Dupont",
"email": "jean@example.com"
}

Réponse :
HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json

{
"id": 123,
"name": "Jean Dupont",
"email": "jean@example.com"
}
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Jean Dupont',
email: 'jean@example.com'
})
})
.then(response => {
if (response.status === 201) {
console.log('Utilisateur créé !');
// Vérifier l'URL du nouvel utilisateur dans l'en-tête Location
console.log(response.headers.get('Location'));
return response.json();
}
});

204 No Content

Succès sans contenu à retourner

Requête :
DELETE /api/users/123 HTTP/1.1

Réponse :
HTTP/1.1 204 No Content
fetch('https://api.example.com/users/123', {
method: 'DELETE'
})
.then(response => {
if (response.status === 204) {
console.log('Suppression réussie !');
// Pas de corps de réponse
}
});

Cas d'utilisation :

  • Requête DELETE réussie
  • Après PUT, PATCH sans données à retourner
  • Opération réussie sans contenu à transmettre

Autres codes 2xx

202 Accepted
├─ Requête acceptée, traitement asynchrone en cours
└─ Ex : traitement de fichiers volumineux, envoi d'email

206 Partial Content
├─ Retourne uniquement une partie du contenu
└─ Ex : streaming vidéo, téléchargement de fichiers volumineux

🔀 3xx - Redirection

301 Moved Permanently

Ressource déplacée définitivement

Requête :
GET /old-page HTTP/1.1

Réponse :
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
fetch('https://api.example.com/old-endpoint')
.then(response => {
if (response.status === 301) {
console.log('Déplacement permanent :', response.headers.get('Location'));
}
});

Cas d'utilisation :

  • Changement d'adresse de site
  • Changement permanent de point de terminaison d'API
  • SEO : Permettre aux moteurs de recherche d'enregistrer la nouvelle URL

302 Found (Redirection temporaire)

Ressource temporairement déplacée

Réponse :
HTTP/1.1 302 Found
Location: https://example.com/temp-page

Cas d'utilisation :

  • Redirection vers une page temporaire
  • Redirection vers la page de connexion
  • Tests A/B

304 Not Modified

Utilisation possible des données en cache

Requête :
GET /api/users/123 HTTP/1.1
If-None-Match: "abc123"

Réponse :
HTTP/1.1 304 Not Modified
ETag: "abc123"
fetch('https://api.example.com/users/123')
.then(response => {
if (response.status === 304) {
console.log('Utilisation du cache');
}
});

Avantages :

  • Économie de bande passante
  • Amélioration de la vitesse de réponse
  • Réduction de la charge serveur

Autres codes 3xx

303 See Other
├─ Redirection vers une autre URL via GET
└─ Ex : Accès à la page de résultats après un POST

307 Temporary Redirect
├─ Similaire à 302 mais garantit le maintien de la méthode
└─ Ex : POST → POST

308 Permanent Redirect
├─ Similaire à 301 mais garantit le maintien de la méthode
└─ Ex : Redirection permanente POST → POST

❌ 4xx - Erreurs client

400 Bad Request

Format de requête incorrect

Requête :
POST /api/users HTTP/1.1
Content-Type: application/json

{
"name": "", // Nom vide
"email": "email-invalide" // Format d'email incorrect
}

Réponse :
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
"error": "Erreur de validation",
"message": "Données de requête invalides",
"details": [
{
"field": "name",
"message": "Le nom est requis"
},
{
"field": "email",
"message": "Format d'email incorrect"
}
]
}
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: '',
email: 'email-invalide'
})
})
.then(response => {
if (response.status === 400) {
return response.json();
}
})
.then(error => {
console.error('Échec de la validation :', error);
});

401 Unauthorized

Authentification requise

Requête :
GET /api/profile HTTP/1.1

Réponse :
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"
Content-Type: application/json

{
"error": "Non autorisé",
"message": "Authentification requise"
}
fetch('https://api.example.com/profile', {
headers: {
'Authorization': 'Bearer VOTRE_TOKEN'
}
})
.then(response => {
if (response.status === 401) {
console.error('Authentification requise - Redirection vers la page de connexion');
window.location.href = '/login';
}
});

Cas d'utilisation :

  • Utilisateur non connecté
  • Token expiré
  • Informations d'authentification incorrectes

403 Forbidden

Pas de permissions

Requête :
DELETE /api/users/999 HTTP/1.1
Authorization: Bearer token_utilisateur

Réponse :
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
"error": "Interdit",
"message": "Vous n'avez pas la permission de supprimer cet utilisateur"
}
fetch('https://api.example.com/admin/users/999', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer TOKEN_UTILISATEUR'
}
})
.then(response => {
if (response.status === 403) {
console.error('Pas de permissions');
alert('Seuls les administrateurs peuvent supprimer');
}
});

401 vs 403 :

401 Unauthorized (Échec d'authentification)
├─ "Je ne sais pas qui vous êtes"
├─ Pas de connexion
├─ Pas de token/token expiré
└─ Solution : Connexion requise

403 Forbidden (Échec d'autorisation)
├─ "Je sais qui vous êtes mais vous n'avez pas les droits"
├─ Connecté
├─ Droits insuffisants
└─ Solution : Demander les droits à l'administrateur

404 Not Found

Ressource introuvable

Requête :
GET /api/users/999999 HTTP/1.1

Réponse :
HTTP/1.1 404 Not Found
Content-Type: application/json

{
"error": "Non trouvé",
"message": "Utilisateur avec ID 999999 non trouvé"
}
fetch('https://api.example.com/users/999999')
.then(response => {
if (response.status === 404) {
console.error('Utilisateur introuvable');
// Afficher page 404
}
return response.json();
});

Cas d'utilisation :

  • Page inexistante
  • Ressource supprimée
  • URL incorrecte

Autres codes 4xx

405 Method Not Allowed
├─ Méthode HTTP non autorisée
└─ Ex : POST sur un point de terminaison GET uniquement

409 Conflict
├─ Conflit avec l'état actuel du serveur
└─ Ex : Tentative d'inscription avec un email existant

422 Unprocessable Entity
├─ Syntaxe correcte mais traitement impossible
└─ Ex : Date correcte mais dans le futur

429 Too Many Requests
├─ Dépassement de la limite de requêtes
└─ Ex : Limitation des appels API

Gestion de 429 Too Many Requests

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);

if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`Trop de requêtes. Nouvelle tentative dans ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}

return response;
}

throw new Error('Nombre maximal de tentatives dépassé');
}

🔥 5xx - Erreurs serveur

500 Internal Server Error

Erreur interne du serveur

Requête :
GET /api/users HTTP/1.1

Réponse :
HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
"error": "Erreur interne du serveur",
"message": "Une erreur inattendue s'est produite"
}
fetch('https://api.example.com/users')
.then(response => {
if (response.status === 500) {
console.error('Erreur serveur survenue');
alert('Erreur temporaire. Veuillez réessayer.');
}
});

Causes :

  • Bogue dans le code
  • Exception non gérée
  • Erreur de base de données
  • Problème de configuration serveur

502 Bad Gateway

Erreur de passerelle

Réponse :
HTTP/1.1 502 Bad Gateway
Content-Type: text/html

<html>
<body>
<h1>502 Bad Gateway</h1>
<p>Le serveur a reçu une réponse invalide du serveur en amont</p>
</body>
</html>

Causes :

  • Échec de communication entre le serveur proxy et le serveur backend
  • Serveur backend hors ligne
  • Problèmes réseau

503 Service Unavailable

Service non disponible

Réponse :
HTTP/1.1 503 Service Unavailable
Retry-After: 3600
Content-Type: application/json

{
"error": "Service indisponible",
"message": "Serveur en maintenance",
"retryAfter": 3600
}
fetch('https://api.example.com/users')
.then(response => {
if (response.status === 503) {
const retryAfter = response.headers.get('Retry-After');
console.log(`Service en maintenance. Réessayez dans ${retryAfter} secondes`);
}
});

Causes :

  • Maintenance du serveur
  • Surcharge
  • Interruption temporaire

504 Gateway Timeout

Timeout de passerelle

Réponse :
HTTP/1.1 504 Gateway Timeout
Content-Type: application/json

{
"error": "Timeout de passerelle",
"message": "Le serveur n'a pas répondu à temps"
}

Causes :

  • Délai de réponse du serveur backend
  • Latence réseau
  • Timeout de requête de base de données

📊 Aide-mémoire des codes de statut

┌─────┬────────────────────┬─────────────────────────┐
│ Code│ Nom │ Quand l'utiliser │
├─────┼────────────────────┼─────────────────────────┤
│ 200 │ OK │ Succès (général) │
│ 201 │ Created │ Création réussie │
│ 204 │ No Content │ Succès (sans réponse) │
├─────┼────────────────────┼─────────────────────────┤
│ 301 │ Moved Permanently │ Déplacement permanent │
│ 302 │ Found │ Déplacement temporaire │
│ 304 │ Not Modified │ Utilisation du cache │
├─────┼────────────────────┼─────────────────────────┤
│ 400 │ Bad Request │ Requête incorrecte │
│ 401 │ Unauthorized │ Authentification requise│
│ 403 │ Forbidden │ Pas de permissions │
│ 404 │ Not Found │ Ressource inexistante │
│ 429 │ Too Many Requests │ Limite de requêtes │
├─────┼────────────────────┼─────────────────────────┤
│ 500 │ Internal Error │ Erreur serveur │
│ 502 │ Bad Gateway │ Erreur de passerelle │
│ 503 │ Service Unavailable│ Service indisponible │
│ 504 │ Gateway Timeout │ Timeout de passerelle │
└─────┴────────────────────┴─────────────────────────┘

🛠️ Gestion des erreurs en pratique

Gestion des erreurs de base

async function fetchData(url) {
try {
const response = await fetch(url);

// Gestion par code de statut
switch (response.status) {
case 200:
return await response.json();

case 401:
console.error('Authentification requise');
window.location.href = '/login';
break;

case 403:
console.error('Pas de permissions');
alert('Vous n\'avez pas accès');
break;

case 404:
console.error('Ressource inexistante');
return null;

case 500:
case 502:
case 503:
case 504:
console.error('Erreur serveur');
alert('Erreur temporaire. Veuillez réessayer.');
break;

default:
console.error('Erreur inconnue :', response.status);
}
} catch (error) {
console.error('Erreur réseau :', error);
}
}

Logique de reprise

async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);

// Succès ou erreur client (pas de reprise nécessaire)
if (response.ok || response.status < 500) {
return response;
}

// Erreur serveur - reprise
if (i < retries - 1) {
console.log(`Erreur serveur. Tentative ${i + 1}...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}

Gestionnaire d'erreurs global

// Intercepteur axios
axios.interceptors.response.use(
response => response,
error => {
const status = error.response?.status;

switch (status) {
case 401:
// Actualisation du token ou redirection vers connexion
refreshToken().catch(() => {
window.location.href = '/login';
});
break;

case 403:
alert('Vous n\'avez pas les permissions');
break;

case 404:
console.error('Ressource introuvable');
break;

case 429:
alert('Trop de requêtes. Réessayez plus tard');
break;

case 500:
case 502:
case 503:
case 504:
alert('Erreur serveur survenue');
break;

default:
console.error('Erreur inconnue :', status);
}

return Promise.reject(error);
}
);

🤔 Questions fréquentes

Q1. Quelle est la différence entre 200 et 201 ?

R :

200 OK
├─ Succès général
├─ Principalement utilisé pour GET, PUT, PATCH
└─ Ex : Lecture de données réussie

201 Created
├─ Création de ressource réussie
├─ Principalement utilisé pour POST
├─ URL de la nouvelle ressource dans l'en-tête Location
└─ Ex : Inscription réussie

Exemples concrets :
GET /api/users/123 → 200 OK
POST /api/users → 201 Created
PUT /api/users/123 → 200 OK

Q2. Quand utiliser 401 et 403 ?

R :

401 Unauthorized (Problème d'authentification)
├─ Non connecté
├─ Pas de token
├─ Token expiré
└─ Solution : Connexion requise

403 Forbidden (Problème d'autorisation)
├─ Connecté
├─ Utilisateur standard tentant d'accéder à une fonction admin
├─ Compte suspendu/bloqué
└─ Solution : Obtenir les permissions

Flux :
1. Token présent ? → Non → 401
2. Token valide ? → Expiré → 401
3. Permissions ? → Non → 403
4. Tout est OK → 200

Q3. Différence entre 404 et 410 ?

R :

404 Not Found
├─ Ressource introuvable
├─ Peut être temporaire ou permanent
└─ Ex : URL incorrecte, ID inexistant

410 Gone
├─ Ressource supprimée définitivement
├─ Suppression intentionnelle
├─ Définitivement inutilisable
└─ Ex : Promotion expirée, compte supprimé

En pratique, 404 est le plus utilisé :
- Plus générique
- 410 uniquement pour signifier explicitement une suppression permanente

Q4. Différence entre 500 et 503 ?

R :

500 Internal Server Error
├─ Erreur serveur inattendue
├─ Bogue de code, exception non gérée
├─ Reprise peu probable
└─ Nécessite intervention du développeur

503 Service Unavailable
├─ Service temporairement indisponible
├─ Maintenance serveur, surcharge
├─ Reprise possible
├─ Peut inclure un en-tête Retry-After
└─ Rétablissement prévu

Quand les utiliser ?
500 : Bogue de code
503 : Interruption intentionnelle, surcharge

Q5. Conséquences d'une mauvaise utilisation des codes de statut ?

R :

❌ Mauvais exemple :

// Retourner tous les erreurs avec 200
HTTP/1.1 200 OK
{
"success": false,
"error": "Utilisateur non trouvé"
}

Problèmes :
├─ Nuit à la mise en cache HTTP
├─ Rend le suivi des erreurs difficile
├─ Complique le traitement côté client
└─ Violation des standards HTTP

✅ Bon exemple :

// Utilisation appropriée des codes de statut
HTTP/1.1 404 Not Found
{
"error": "Non trouvé",
"message": "Utilisateur non trouvé"
}

Avantages :
├─ Respect de la sémantique HTTP
├─ Mise en cache et journalisation automatisées
├─ Traitement côté client simplifié
└─ Débogage facilité

🎓 Mise en pratique

1. Gestion de tous les codes de statut

async function handleResponse(response) {
// 2xx
if (response.ok) {
if (response.status === 204) {
return null; // No Content
}
return await response.json();
}

// 3xx
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('Location');
console.log('Redirection :', location);
return null;
}

// 4xx
if (response.status >= 400 && response.status < 500) {
const error = await response.json();
throw new Error(error.message || 'Erreur client');
}

// 5xx
if (response.status >= 500) {
throw new Error('Erreur serveur');
}
}

2. Test des codes de statut

// Utilisation de httpstat.us pour les tests
async function testStatusCodes() {
const codes = [200, 201, 204, 400, 401, 403, 404, 500, 503];

for (const code of codes) {
try {
const response = await fetch(`https://httpstat.us/${code}`);
console.log(`${code}: ${response.statusText}`);
} catch (error) {
console.error(`${code}: Erreur`, error);
}
}
}

🔗 Documents associés

🎬 Conclusion

Les codes de statut HTTP clarifient la communication entre le client et le serveur. Utiliser des codes de statut appropriés facilite le débogage, simplifie la gestion des erreurs et permet de créer des API conformes aux standards HTTP !

Prochaine étape : Lisez En-têtes HTTP pour comprendre Content-Type, Authorization, etc.