🚦 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);
}
}
}