Statuts HTTP
Les codes de statut HTTP indiquent le résultat d'une requête. Bien les utiliser est essentiel pour concevoir une API compréhensible et facile à déboguer.
Vue d'ensemble des familles de statuts
| Famille | Signification | Exemple |
|---|---|---|
| 1xx | Informatif (lié aux WebSockets) | 101 Switching Protocols |
| 2xx | Requête acceptée et terminée | 200 OK, 201 Created |
| 3xx | Redirection | 301 Moved Permanently |
| 4xx | Erreur côté client | 400 Bad Request, 404 Not Found |
| 5xx | Erreur côté serveur | 500 Internal Server Error |
Requête acceptée (2xx)
Ces codes indiquent que la requête a été correctement traitée.
200 - OK
Code générique de succès. Utilisable avec GET, PUT, PATCH et DELETE. La réponse contient un body.
from http import HTTPStatus
@books_bp.route('/<int:book_id>', methods=['GET'])
def get_book(book_id: int):
book = book_service.get_by_id(book_id)
if not book:
return jsonify({"error": "Livre non trouvé"}), HTTPStatus.NOT_FOUND
return jsonify(book), HTTPStatus.OK # 200
201 - Created
À utiliser avec POST lors de la création d'une ressource. Acceptable dans certains cas de PUT (si le PUT accepte la création à la volée).
@books_bp.route('', methods=['POST'])
def create_book():
data = request.get_json()
dto = CreateBookDTO(**data)
new_book = book_service.create(dto)
return jsonify(new_book), HTTPStatus.CREATED # 201
204 - No Content
À utiliser lorsque la requête réussit mais qu'il n'y a aucun contenu à renvoyer. Fréquent avec DELETE ou certains PUT/PATCH.
@books_bp.route('/<int:book_id>', methods=['DELETE'])
def delete_book(book_id: int):
book_service.delete(book_id)
return '', HTTPStatus.NO_CONTENT # 204
HTTPStatus en Python
Plutôt que de coder en dur les valeurs numériques (200, 404, etc.), utilisez le module http.HTTPStatus de Python. Le code est plus lisible et moins sujet aux erreurs :
from http import HTTPStatus
HTTPStatus.OK # 200
HTTPStatus.CREATED # 201
HTTPStatus.NO_CONTENT # 204
HTTPStatus.BAD_REQUEST # 400
HTTPStatus.NOT_FOUND # 404
Quel code de statut est le plus approprié pour la réponse d'un POST qui crée une ressource ?
Erreurs client (4xx)
Ces codes indiquent que la requête envoyée par le client est incorrecte ou non autorisée.
400 - Bad Request
La requête est mal formée : le body, les params ou la query ne passent pas la validation.
from pydantic import ValidationError
@books_bp.route('', methods=['POST'])
def create_book():
data = request.get_json()
try:
dto = CreateBookDTO(**data)
except ValidationError as e:
return jsonify({"error": "Données invalides", "details": e.errors()}), HTTPStatus.BAD_REQUEST
new_book = book_service.create(dto)
return jsonify(new_book), HTTPStatus.CREATED
401 - Unauthorized
Le client doit s'authentifier pour accéder à la ressource. Également utilisable pour un échec de login.
@books_bp.route('', methods=['POST'])
def create_book():
token = request.headers.get('Authorization')
if not token:
return jsonify({"error": "Authentification requise"}), HTTPStatus.UNAUTHORIZED
# ...
403 - Forbidden
Le client est authentifié mais n'a pas les droits pour accéder à la ressource.
@admin_bp.route('/users', methods=['GET'])
def list_users():
current_user = get_current_user(request)
if current_user.role != 'admin':
return jsonify({"error": "Accès interdit"}), HTTPStatus.FORBIDDEN
# ...
401 vs 403
La distinction est importante :
- 401 : "Qui êtes-vous ?" → le client n'est pas identifié
- 403 : "Je sais qui vous êtes, mais vous n'avez pas le droit" → le client est identifié mais pas autorisé
404 - Not Found
La ressource demandée n'existe pas.
@books_bp.route('/<int:book_id>', methods=['GET'])
def get_book(book_id: int):
book = book_service.get_by_id(book_id)
if not book:
return jsonify({"error": "Livre non trouvé"}), HTTPStatus.NOT_FOUND
return jsonify(book), HTTPStatus.OK
405 - Method Not Allowed
L'endpoint existe, mais la méthode HTTP utilisée n'est pas supportée.
Par exemple, tenter un DELETE sur un endpoint qui n'accepte que GET et POST.
429 - Too Many Requests
Le client a envoyé trop de requêtes dans un intervalle de temps donné. C'est le mécanisme de rate limiting.
# Avec flask-limiter
from flask_limiter import Limiter
limiter = Limiter(app, default_limits=["100 per minute"])
@books_bp.route('', methods=['GET'])
@limiter.limit("50 per minute")
def get_books():
# Maximum 50 requêtes par minute par client
return jsonify(books), HTTPStatus.OK
Un utilisateur connecté tente d'accéder à une page d'administration sans être administrateur. Quel code de statut renvoyer ?
Erreurs serveur (5xx)
Ces codes indiquent un problème côté serveur qui empêche le traitement de la requête.
500 - Internal Server Error
Code générique : le code backend a planté. C'est souvent une exception non gérée.
@books_bp.route('/<int:book_id>', methods=['GET'])
def get_book(book_id: int):
try:
book = book_service.get_by_id(book_id)
return jsonify(book), HTTPStatus.OK
except Exception as e:
# Logger l'erreur pour le débogage
app.logger.error(f"Erreur interne: {e}")
return jsonify({"error": "Erreur interne du serveur"}), HTTPStatus.INTERNAL_SERVER_ERROR
Ne jamais exposer les détails d'une 500
En production, ne renvoyez jamais le détail de l'erreur au client (stack trace, message d'exception). Cela expose des informations sensibles sur votre code. Loggez l'erreur côté serveur et renvoyez un message générique au client.
503 - Service Unavailable
Le serveur ne répond pas ou est temporairement indisponible (maintenance, surcharge).
504 - Gateway Timeout
Le serveur a mis trop de temps à répondre. Fréquent avec les providers cloud lors d'un déploiement raté ou d'une surcharge.
Récapitulatif des statuts les plus utilisés
| Code | Nom | Usage |
|---|---|---|
| 200 | OK | Succès générique (GET, PUT, PATCH) |
| 201 | Created | Ressource créée (POST) |
| 204 | No Content | Succès sans body (DELETE) |
| 400 | Bad Request | Requête mal formée |
| 401 | Unauthorized | Authentification requise |
| 403 | Forbidden | Droits insuffisants |
| 404 | Not Found | Ressource introuvable |
| 405 | Method Not Allowed | Mauvaise méthode HTTP |
| 429 | Too Many Requests | Rate limit dépassé |
| 500 | Internal Server Error | Erreur côté serveur |
À retenir
Points clés
- Les codes 2xx indiquent un succès : utilisez 201 pour POST et 204 pour DELETE sans body
- Les codes 4xx signalent une erreur du client : distinguez bien 401 (non authentifié) de 403 (non autorisé)
- Les codes 5xx indiquent un problème serveur : ne jamais exposer les détails en production
- Utilisez
http.HTTPStatusen Python pour un code lisible et maintenable - Chaque endpoint doit renvoyer le code de statut le plus spécifique possible