Passer au contenu principal

📋 En-têtes HTTP

📖 Définition

Les en-têtes HTTP sont des métadonnées transmises entre le client et le serveur. Ils définissent des informations supplémentaires sur les requêtes et réponses, incluant l'authentification, le cache, le type de contenu, etc.

🎯 Comprendre par une analogie

Enveloppe postale

Message HTTP = Lettre
├─ Enveloppe (en-têtes)
│ ├─ Adresse de l'expéditeur (User-Agent)
│ ├─ Adresse du destinataire (Host)
│ ├─ Type d'envoi (Content-Type)
│ ├─ Niveau de priorité (Priority)
│ └─ Adresse de retour (Referer)
└─ Contenu de la lettre (corps)

En examinant l'enveloppe (en-têtes) :
- Identification de l'expéditeur
- Détermination du type de lettre
- Décision sur le traitement

💡 Structure des en-têtes

Format des en-têtes :
Nom-de-l'en-tête: valeur

Exemples :
Content-Type: application/json
Authorization: Bearer abc123
User-Agent: Mozilla/5.0

📬 En-têtes de requête (Request Headers)

Host

Nom d'hôte et port du serveur demandé

GET /api/users HTTP/1.1
Host: example.com
// fetch définit automatiquement l'en-tête Host
fetch('https://example.com/api/users');

Caractéristiques :

  • Obligatoire depuis HTTP/1.1
  • Permet l'hébergement de plusieurs domaines sur une même IP
  • Défini automatiquement par le navigateur

User-Agent

Informations sur l'application cliente

GET /api/users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
// Définir un User-Agent personnalisé en Node.js
fetch('https://api.example.com/users', {
headers: {
'User-Agent': 'MonApp/1.0'
}
});

Usages :

  • Identification du navigateur/OS
  • Collecte de statistiques
  • Gestion de la compatibilité
  • Détection de bots

Accept

Types de médias acceptés par le client

GET /api/users HTTP/1.1
Host: example.com
Accept: application/json, text/html
Accept-Language: fr-FR, en-US
Accept-Encoding: gzip, deflate, br
fetch('https://api.example.com/users', {
headers: {
'Accept': 'application/json',
'Accept-Language': 'fr-FR,fr;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br'
}
});

Types d'en-têtes Accept :

Accept
├─ Accept: application/json - Préfère JSON
├─ Accept: text/html - Préfère HTML
└─ Accept: */* - Accepte tous les types

Accept-Language
├─ Accept-Language: fr-FR - Préfère français
└─ Accept-Language: en-US,en;q=0.9 - Anglais acceptable (priorité moindre)

Accept-Encoding
├─ Accept-Encoding: gzip - Supporte gzip
└─ Accept-Encoding: br, gzip - Préfère Brotli, gzip possible

Accept-Charset (rarement utilisé)
└─ Accept-Charset: utf-8 - Encodage UTF-8

Authorization

Informations d'authentification

GET /api/profile HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// Authentification JWT
fetch('https://api.example.com/profile', {
headers: {
'Authorization': 'Bearer ' + token
}
});

// Authentification Basic
const credentials = btoa('utilisateur:motdepasse');
fetch('https://api.example.com/profile', {
headers: {
'Authorization': 'Basic ' + credentials
}
});

Méthodes d'authentification :

Token Bearer (le plus courant)
Authorization: Bearer <token>
├─ Utilisé avec JWT, OAuth 2.0
└─ Ex : Bearer eyJhbGci...

Authentification Basic
Authorization: Basic <credentials-base64>
├─ username:password encodé en Base64
├─ Non sécurisé (HTTPS obligatoire)
└─ Ex : Basic dXRpbGlzYXRldXI6bW90ZGVwYXNzZQ==

Clé API
Authorization: ApiKey <clé-api>
ou
X-API-Key: <clé-api>

Content-Type

Type MIME du corps de la requête

POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

{
"name": "Jean Dupont",
"email": "jean@example.com"
}
// Envoi JSON
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Jean Dupont',
email: 'jean@example.com'
})
});

// Envoi de données de formulaire
const formData = new FormData();
formData.append('name', 'Jean Dupont');
formData.append('file', inputFichier.files[0]);

fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
// Content-Type automatiquement défini (multipart/form-data)
});

Types Content-Type principaux :

application/json
├─ Données JSON
└─ Format le plus courant pour les API

application/x-www-form-urlencoded
├─ Données de formulaire (défaut)
└─ key1=value1&key2=value2

multipart/form-data
├─ Téléchargement de fichiers
└─ Composé de plusieurs parties

text/plain
├─ Texte simple
└─ Données basiques

application/xml
├─ Données XML
└─ API legacy

Cookies enregistrés par le client

GET /api/profile HTTP/1.1
Host: example.com
Cookie: sessionId=abc123; userId=456
// Le navigateur envoie automatiquement les cookies
fetch('https://example.com/api/profile', {
credentials: 'include' // Inclure les cookies
});

Referer

URL de la page précédente

GET /api/products/123 HTTP/1.1
Host: example.com
Referer: https://example.com/products

Usages :

  • Analyse du parcours
  • Validation de sécurité
  • Journalisation

Autres en-têtes de requête

Origin
├─ Origine de la requête
└─ Utilisé pour la vérification CORS

If-None-Match
├─ Comparaison avec la valeur ETag
└─ Validation de cache

If-Modified-Since
├─ Vérifier si modifié après une date
└─ Validation de cache

Range
├─ Demander uniquement une partie des données
└─ Ex : bytes=0-1023

📤 En-têtes de réponse (Response Headers)

Content-Type

Type MIME du corps de la réponse

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"id": 123,
"name": "Jean Dupont"
}
fetch('https://api.example.com/users/123')
.then(response => {
console.log(response.headers.get('Content-Type'));
// "application/json; charset=utf-8"
return response.json();
});

Content-Length

Taille du corps de la réponse (en octets)

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 256

{"id":123,"name":"Jean Dupont"}

Enregistrement de cookies côté client

HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
Set-Cookie: userId=456; Max-Age=3600
// Définir un cookie côté serveur (Node.js/Express)
res.cookie('sessionId', 'abc123', {
httpOnly: true, // Bloque l'accès JavaScript
secure: true, // HTTPS uniquement
sameSite: 'strict',
maxAge: 3600000 // 1 heure
});

Attributs des cookies :

HttpOnly
├─ Accès JavaScript impossible
└─ Protection contre les attaques XSS

Secure
├─ Transmission HTTPS uniquement
└─ Protection contre les attaques de l'homme du milieu

SameSite
├─ Strict : uniquement sur le même site
├─ Lax : autorisation partielle entre sites
└─ None : autorisation totale (Secure obligatoire)

Max-Age / Expires
├─ Max-Age: 3600 (en secondes)
└─ Expires: Mer, 21 Oct 2025 07:28:00 GMT

Domain / Path
├─ Domain: example.com (inclut les sous-domaines)
└─ Path: / (tous les chemins)

Cache-Control

Politique de mise en cache

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Mer, 26 Jan 2025 10:00:00 GMT
fetch('https://api.example.com/users')
.then(response => {
const cacheControl = response.headers.get('Cache-Control');
console.log(cacheControl); // "public, max-age=3600"
});

Directives de cache :

Cache-Control: no-cache
├─ Validation serveur avant utilisation
└─ Garantit les données les plus récentes

Cache-Control: no-store
├─ Aucune mise en cache
└─ Données sensibles (mots de passe, etc.)

Cache-Control: public, max-age=3600
├─ Autorisation du cache public
├─ Mise en cache pendant 1 heure
└─ Ressources statiques (images, CSS, etc.)

Cache-Control: private, max-age=3600
├─ Mise en cache par navigateur uniquement
└─ Données spécifiques à l'utilisateur

Cache-Control: must-revalidate
├─ Validation obligatoire à l'expiration
└─ Prévention des données obsolètes

Location

Emplacement de redirection ou de la ressource créée

HTTP/1.1 201 Created
Location: /api/users/123

HTTP/1.1 301 Moved Permanently
Location: https://example.com/nouvelle-page
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Jean Dupont' })
})
.then(response => {
if (response.status === 201) {
const location = response.headers.get('Location');
console.log('Nouvelle ressource :', location); // "/api/users/123"
}
});

Access-Control-* (CORS)

Partage de ressources inter-origines

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
// Configuration CORS côté serveur (Node.js/Express)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://example.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});

En-têtes CORS :

Access-Control-Allow-Origin
├─ Origine autorisée
├─ * : toutes les origines (attention sécurité)
└─ https://example.com : origine spécifique

Access-Control-Allow-Methods
├─ Méthodes HTTP autorisées
└─ GET, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Headers
├─ En-têtes de requête autorisés
└─ Content-Type, Authorization

Access-Control-Allow-Credentials
├─ Autorisation des cookies
└─ true (incompatible avec Origin *)

Access-Control-Max-Age
├─ Durée de cache des résultats de préflight (secondes)
└─ 86400 (24 heures)

🔐 En-têtes de sécurité

Strict-Transport-Security (HSTS)

Forçage HTTPS

HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains
// Configuration serveur (Node.js/Express)
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});

Content-Security-Policy (CSP)

Protection contre les attaques XSS

HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com

Directives CSP :

default-src 'self'
├─ Politique par défaut : même origine
└─ Valeur par défaut pour tous les types de ressources

script-src 'self' https://trusted.com
├─ Limitation des sources de scripts
└─ Protection contre les attaques XSS

style-src 'self' 'unsafe-inline'
├─ Limitation des sources de feuilles de style
└─ Autorisation des styles en ligne

img-src * data:
├─ Limitation des sources d'images
└─ Toutes les origines, URI data autorisées

frame-ancestors 'none'
├─ Blocage de l'insertion d'iframe
└─ Protection contre le clickjacking

X-Content-Type-Options

Prévention du MIME sniffing

HTTP/1.1 200 OK
X-Content-Type-Options: nosniff

X-Frame-Options

Protection contre le clickjacking

HTTP/1.1 200 OK
X-Frame-Options: DENY

Options :

DENY
├─ Blocage total de l'insertion dans un iframe
└─ Le plus sécurisé

SAMEORIGIN
├─ Autorisation uniquement sur la même origine
└─ Couramment utilisé

ALLOW-FROM https://example.com
├─ Autorisation pour une origine spécifique
└─ Compatibilité avec les navigateurs anciens

📊 En-têtes personnalisés

Préfixe X- (non standard, legacy)

GET /api/users HTTP/1.1
Host: example.com
X-API-Key: abc123xyz
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
fetch('https://api.example.com/users', {
headers: {
'X-API-Key': 'abc123xyz',
'X-Request-ID': crypto.randomUUID()
}
});

En-têtes personnalisés courants :

X-API-Key
├─ Authentification par clé API
└─ Alternative : en-tête Authorization

X-Request-ID
├─ ID unique pour le suivi de requête
└─ Journalisation, débogage

X-Forwarded-For
├─ IP d'origine via proxy
└─ Ex : X-Forwarded-For: 203.0.113.1, 198.51.100.178

X-Real-IP
├─ IP client réelle
└─ Utilisé par Nginx, etc.

X-Correlation-ID
├─ Suivi entre services dans les microservices
└─ Systèmes distribués

💡 Exemple pratique complet

Requête/réponse complète

Requête :
POST /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
Accept: application/json
Accept-Language: fr-FR,fr;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Length: 58
Origin: https://example.com
Referer: https://example.com/register

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

Réponse :
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Content-Length: 156
Location: /api/users/123
Cache-Control: no-cache, no-store, must-revalidate
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
"id": 123,
"name": "Jean Dupont",
"email": "jean@example.com",
"createdAt": "2025-01-26T10:00:00Z"
}

Lecture/écriture d'en-têtes (JavaScript)

// Configuration des en-têtes de requête
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token,
'Accept': 'application/json',
'X-Request-ID': crypto.randomUUID()
},
body: JSON.stringify({
name: 'Jean Dupont',
email: 'jean@example.com'
})
});

// Lecture des en-têtes de réponse
const contentType = response.headers.get('Content-Type');
const location = response.headers.get('Location');
const requestId = response.headers.get('X-Request-ID');

console.log('Content-Type :', contentType);
console.log('Location :', location);
console.log('ID de requête :', requestId);

// Affichage de tous les en-têtes
response.headers.forEach((value, key) => {
console.log(`${key}: ${value}`);
});

Configuration des en-têtes côté serveur (Node.js/Express)

app.post('/api/users', (req, res) => {
// Création d'un nouvel utilisateur
const newUser = {
id: 123,
name: req.body.name,
email: req.body.email
};

// Configuration des en-têtes de réponse
res.status(201);
res.set({
'Content-Type': 'application/json',
'Location': `/api/users/${newUser.id}`,
'Cache-Control': 'no-cache, no-store, must-revalidate',
'X-Request-ID': req.headers['x-request-id'] || generateId(),
'Strict-Transport-Security': 'max-age=31536000',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY'
});

// Configuration des cookies
res.cookie('sessionId', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000
});

res.json(newUser);
});

🤔 Questions fréquentes

Q1. Règles de nommage des en-têtes personnalisés ?

R :

Passé (préfixe X-) :
X-API-Key: abc123
X-Custom-Header: value

Actuellement (recommandé) :
API-Key: abc123
Custom-Header: value

Raisons :
- Préfixe X- déconseillé par la RFC 6648
- Prévention des confusions lors de la standardisation
- Quelques en-têtes X- largement utilisés restent valides

Exceptions :
X-Forwarded-For - Très largement utilisé
X-Real-IP - Standard Nginx
X-Request-ID - Standard de journalisation/suivi

Q2. Limites de taille des en-têtes ?

R :

Limites courantes :
├─ Apache : 8 Ko (défaut)
├─ Nginx : 4-8 Ko
├─ Node.js : 80 Ko (http.maxHeaderSize)
└─ Cloudflare : 32 Ko

En cas de dépassement :
- 431 Champs d'en-tête de requête trop volumineux
- 413 Entité de requête trop volumineuse

Solutions :
1. Utiliser le corps au lieu des en-têtes
2. Réduire la longueur des tokens
3. Modifier la configuration serveur (avec précaution)

❌ Mauvais exemple :
Authorization: Bearer <token JWT de 5 Ko>

✅ Bon exemple :
- Tokens courts
- ID de session + stockage côté serveur

Q3. Distinction majuscules/minuscules ?

R :

Noms d'en-têtes : non sensible à la casse
├─ Content-Type = content-type = CONTENT-TYPE
└─ HTTP/2 convertit tout en minuscules

Valeurs d'en-têtes : sensible à la casse
├─ Content-Type: application/json (OK)
├─ Content-Type: Application/JSON (KO)
└─ La valeur respecte la casse

Recommandations :
- Noms d'en-têtes : première lettre de chaque mot en majuscule (Content-Type)
- Valeurs : selon la spécification

Q4. Est-il sûr de placer des informations sensibles dans les en-têtes ?

R :

✅ Utilisation sécurisée des en-têtes :
Authorization: Bearer <token>
├─ HTTPS obligatoire
├─ Masquage des logs
└─ Durée de vie courte

❌ Utilisation risquée :
Authorization: Bearer <token> (HTTP)
├─ Transmission en texte clair
└─ Vulnérable aux attaques de l'homme du milieu

X-API-Key: <secret>
├─ Risque d'exposition dans les URL
└─ Présent dans l'historique du navigateur

Précautions :
1. HTTPS obligatoire
2. Exclure les en-têtes sensibles des logs
3. Visible dans les outils de développement du navigateur
4. Transmis aux proxies, CDN

Q5. Qu'est-ce qu'une requête Preflight ?

R :

Preflight = Requête préliminaire CORS

Quand se produit-elle ?
├─ Méthodes autres que GET, POST, HEAD
├─ Utilisation d'en-têtes personnalisés
└─ Content-Type spécifique (hors application/json, etc.)

Processus :
1. Requête OPTIONS (Preflight)
OPTIONS /api/users HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

2. Réponse serveur
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

3. Requête réelle
POST /api/users HTTP/1.1
Content-Type: application/json
Authorization: Bearer token
...

Optimisation :
- Mise en cache via Access-Control-Max-Age (24 heures)
- Respect des conditions de requête simple (éviter Preflight)

🎓 Mise en pratique

1. Débogage des en-têtes

// Journalisation de tous les en-têtes de requête/réponse
async function debugFetch(url, options = {}) {
console.log('=== Requête ===');
console.log('URL :', url);
console.log('Méthode :', options.method || 'GET');
console.log('En-têtes :', options.headers || {});

const response = await fetch(url, options);

console.log('=== Réponse ===');
console.log('Statut :', response.status, response.statusText);
console.log('En-têtes :');
response.headers.forEach((value, key) => {
console.log(` ${key}: ${value}`);
});

return response;
}

// Utilisation
await debugFetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ name: 'Jean Dupont' })
});

2. Middleware d'en-têtes (Express)

// Configuration automatique des en-têtes de sécurité
function securityHeaders(req, res, next) {
res.set({
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Content-Security-Policy': "default-src 'self'"
});
next();
}

// Configuration CORS
function corsHeaders(req, res, next) {
res.set({
'Access-Control-Allow-Origin': req.headers.origin || '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
});

if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

next();
}

app.use(securityHeaders);
app.use(corsHeaders);

🔗 Documents associés

🎬 Conclusion

Les en-têtes HTTP sont un moyen crucial de transmettre des métadonnées entre clients et serveurs. L'utilisation appropriée des en-têtes améliore la sécurité, les performances et la compatibilité !

Prochaine étape : Lisez Cookies et sessions pour comprendre comment surmonter la nature sans état de HTTP.