HTTP, méthodes, status codes, Flask et sécurité — le protocole du web maîtrisé.
Une API REST (Representational State Transfer) est une interface qui permet à deux systèmes de communiquer via HTTP. Le client envoie une requête, le serveur renvoie une réponse — généralement en JSON.
REST n'est pas un protocole mais un style d'architecture basé sur 6 contraintes : stateless, client-serveur, cacheable, interface uniforme, système en couches, code on demand (optionnel).
Imagine un jeu MMO. Ton personnage (client) envoie une action au serveur de jeu (serveur API). Le serveur traite la requête, met à jour le monde, et te renvoie l'état actuel. Chaque requête est indépendante — le serveur ne se souvient pas de ta session précédente (stateless). Comme un système de matchmaking : tu envoies ta requête, tu reçois ton lobby.
HTTP (HyperText Transfer Protocol) est le protocole de communication du web. HTTPS = HTTP + chiffrement TLS/SSL.
Structure d'une requête : méthode + URL + headers + body (optionnel).
Les méthodes définissent l'action à effectuer sur une ressource :
GET lire · POST créer · PUT remplacer · PATCH modifier · DELETE supprimer
Le serveur répond avec un code à 3 chiffres :
2xx succès · 3xx redirection · 4xx erreur client · 5xx erreur serveur
JSON (JavaScript Object Notation) est le format d'échange standard des APIs REST. C'est du texte lisible par les humains et parsable par les machines.
// Structure JSON type
{
"id": 42,
"name": "Adam",
"tags": ["dev", "holberton"]
}
Trois mécanismes principaux pour sécuriser une API :
API Key — clé secrète dans le header · JWT — token signé · OAuth 2.0 — délégation d'accès
Authorization, jamais dans l'URL.Un endpoint est une URL qui représente une ressource. Convention REST :
/users — collection · /users/42 — ressource unique · /users/42/posts — sous-ressource
/getUser mais /users/42 avec GET.La façon la plus directe de tester une API depuis le terminal.
# GET — récupérer une ressource
curl https://jsonplaceholder.typicode.com/users/1
# GET avec headers et pretty print
curl -s -H "Accept: application/json" \
https://jsonplaceholder.typicode.com/posts | python3 -m json.tool
# POST — créer une ressource
curl -X POST \
-H "Content-Type: application/json" \
-d '{"title": "Boss Fight", "userId": 1}' \
https://jsonplaceholder.typicode.com/posts
# DELETE — supprimer
curl -X DELETE https://jsonplaceholder.typicode.com/posts/1
# Avec API Key dans le header
curl -H "Authorization: Bearer TON_TOKEN" \
https://api.exemple.com/protected
-v pour voir les headers complets (verbose). -i inclut les headers de réponse.La bibliothèque requests rend la consommation d'API élégante en Python.
import requests
# GET simple
response = requests.get("https://jsonplaceholder.typicode.com/users/1")
print(response.status_code) # 200
print(response.json()) # dict Python
# GET avec paramètres de query
params = {"userId": 1, "_limit": 3}
r = requests.get("https://jsonplaceholder.typicode.com/posts",
params=params)
# POST avec JSON body
data = {"title": "New Quest", "body": "Kill the dragon", "userId": 1}
r = requests.post("https://jsonplaceholder.typicode.com/posts",
json=data) # json= encode auto en JSON
# Avec headers d'auth
headers = {"Authorization": "Bearer MON_TOKEN"}
r = requests.get("https://api.exemple.com/me", headers=headers)
# Gestion d'erreur robuste
if r.status_code == 200:
print(r.json())
else:
print(f"Erreur {r.status_code}: {r.text}")
Pour un serveur HTTP minimal sans dépendances externes — idéal pour comprendre les bases.
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
class SimpleAPI(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/users':
data = [{"id": 1, "name": "Adam"}]
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
length = int(self.headers['Content-Length'])
body = json.loads(self.rfile.read(length))
self.send_response(201) # 201 Created
self.end_headers()
HTTPServer(('', 8080), SimpleAPI).serve_forever()
Flask rend le développement d'API REST simple et lisible avec un système de routing élégant.
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
# Données en mémoire (remplacer par DB en prod)
users = [{"id": 1, "name": "Adam"}]
# GET /api/users — liste tous les users
@app.route('/api/users', methods=['GET'])
def get_users():
return jsonify(users), 200
# GET /api/users/<id> — un seul user
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = next((u for u in users if u["id"] == user_id), None)
if not user:
abort(404)
return jsonify(user), 200
# POST /api/users — créer un user
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data or 'name' not in data:
abort(400) # Bad Request
new_user = {"id": len(users) + 1, "name": data["name"]}
users.append(new_user)
return jsonify(new_user), 201 # 201 Created
# DELETE /api/users/<id>
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
global users
users = [u for u in users if u["id"] != user_id]
return '', 204 # 204 No Content
if __name__ == '__main__':
app.run(debug=True, port=5000)
flask run en dev. En prod, passe par gunicorn ou uWSGI.| MÉTHODE | ACTION | BODY | IDEMPOTENT |
|---|---|---|---|
| GET | Lire | Non | Oui |
| POST | Créer | Oui | Non |
| PUT | Remplacer | Oui | Oui |
| PATCH | Modifier | Oui | Non |
| DELETE | Supprimer | Non | Oui |
| CODE | SIGNIFICATION |
|---|---|
| 200 | OK — succès |
| 201 | Created — ressource créée |
| 204 | No Content — succès sans body |
| 301 | Moved Permanently |
| 400 | Bad Request — données invalides |
| 401 | Unauthorized — non authentifié |
| 403 | Forbidden — pas les droits |
| 404 | Not Found — ressource absente |
| 429 | Too Many Requests — rate limit |
| 500 | Internal Server Error |
Utilise curl pour interroger l'API JSONPlaceholder et affiche le résultat formaté.
# 1. Récupère tous les posts de l'user 1
curl -s "https://jsonplaceholder.typicode.com/posts?userId=1" \
| python3 -m json.tool
# 2. Affiche seulement les titres avec Python
python3 -c "
import requests
posts = requests.get('https://jsonplaceholder.typicode.com/posts', params={'userId': 1}).json()
for p in posts:
print(f\" {p['id']}. {p['title']}\")
"
Implémente une API Flask complète avec GET, POST et DELETE sur une liste de joueurs.
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
players = []
@app.route('/players', methods=['GET'])
def list_players():
return jsonify(players)
@app.route('/players', methods=['POST'])
def add_player():
body = request.get_json() or {}
if 'name' not in body:
abort(400)
player = {"id": len(players) + 1, "name": body["name"], "score": 0}
players.append(player)
return jsonify(player), 201
# Test avec curl :
# curl -X POST -H "Content-Type: application/json"
# -d '{"name": "Adam"}' http://localhost:5000/players
Ajoute une authentification par API key à ton serveur Flask — accès refusé sans token valide.
from flask import Flask, jsonify, request, abort
import os
app = Flask(__name__)
API_KEY = os.environ.get("API_KEY", "dev-secret-key")
def require_auth():
"""Vérifie le Bearer token dans le header Authorization."""
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
abort(401) # Unauthorized
token = auth[7:] # retire "Bearer "
if token != API_KEY:
abort(403) # Forbidden
@app.route('/api/secret')
def secret_endpoint():
require_auth()
return jsonify({"message": "Access granted. Welcome, elite player."})
# Tester :
# API_KEY=mykey flask run
# curl -H "Authorization: Bearer mykey" http://localhost:5000/api/secret
# Tester sans token (doit retourner 401) :
# curl http://localhost:5000/api/secret
API_KEY depuis un fichier .env — ne committe jamais les secrets.# ❌ Verbes dans les endpoints
GET /getUsers
POST /createUser
DELETE /deleteUser/1
# ❌ Token hardcodé dans le code
API_KEY = "sk-123abc-secret"
# ❌ Pas de gestion d'erreur
r = requests.get(url)
data = r.json() # crash si 404 !
# ❌ Mauvais status code
return jsonify({"error": "not found"}), 200
# → doit être 404
# ✅ Noms de ressources (nouns)
GET /users
POST /users
DELETE /users/1
# ✅ Variable d'environnement
import os
API_KEY = os.environ.get("API_KEY")
# ✅ Gestion d'erreur propre
r = requests.get(url)
r.raise_for_status() # lève si 4xx/5xx
data = r.json()
# ✅ Status code sémantique
return jsonify({"error": "not found"}), 404
# ❌ Oublier Content-Type sur POST
curl -X POST -d '{"name":"test"}' /api/users
# → Flask reçoit None dans get_json()
# ❌ Confondre -d et --data-binary
# -d encode le body comme form data
# --data-binary envoie les données telles quelles
# ❌ Guillemets simples sur Windows
# Windows CMD ne supporte pas ' dans -d
# → utiliser PowerShell ou fichier JSON
# ✅ Header Content-Type obligatoire
curl -X POST \
-H "Content-Type: application/json" \
-d '{"name": "test"}' \
http://localhost:5000/api/users
# ✅ Fichier JSON pour éviter les issues
curl -X POST \
-H "Content-Type: application/json" \
-d @data.json \
http://localhost:5000/api/users
# ✅ -v pour déboguer les headers
curl -v http://localhost:5000/api/users
Content-Type: application/json, Flask ne parse pas le body. C'est l'erreur #1 des débutants.
os.environ.get() ou python-dotenv. Jamais dans le code, jamais dans les URLs.
/users, /posts, /orders. Jamais de verbes comme /getUsers ou /deletePost.
══════════════════════════════════════════════════════════════
RESTFUL API — CARTE MENTALE
══════════════════════════════════════════════════════════════
CLIENT ──────── HTTP REQUEST ────────► SERVER
│ │
│ Méthodes : │ Logique :
│ GET → lire une ressource │ Valide le body
│ POST → créer │ Authentifie
│ PUT → remplacer │ Interroge la DB
│ PATCH → modifier partiellement │ Retourne JSON
│ DELETE → supprimer │
│ │
◄─────────── HTTP RESPONSE ───────────┘
Status code + JSON body
──────────────────────────────────────────────────────────────
STATUS CODES (à retenir absolument) :
200 OK ← GET/PUT réussi
201 Created ← POST réussi (nouvelle ressource créée)
204 No Content ← DELETE réussi
400 Bad Request ← body invalide ou champ manquant
401 Unauthorized ← pas de token
403 Forbidden ← token valide mais pas les droits
404 Not Found ← ressource inexistante
500 Server Error ← bug côté serveur
──────────────────────────────────────────────────────────────
CONSOMMER (Python requests) :
r = requests.get(url, headers={"Authorization": "Bearer ..."})
r.raise_for_status() # lève si 4xx ou 5xx
data = r.json() # parse le JSON en dict Python
──────────────────────────────────────────────────────────────
CRÉER (Flask) :
@app.route('/resource', methods=['GET', 'POST'])
def handle():
if request.method == 'GET':
return jsonify(data), 200
body = request.get_json() or abort(400)
# créer + return jsonify(new), 201
══════════════════════════════════════════════════════════════