Aller au contenu principal

HTTP et serveurs

Progression

#HTTP et serveurs

Exposez une API claire: des routes explicites, des statuts cohérents (200/201/204/400/401/403/404/409/422/500) et des erreurs JSON structurées. Une requête se termine toujours proprement, même en cas d’exception: le client reçoit un corps stable avec un code de statut adapté et un identifiant de corrélation pour l’investigation.

Exemple en pseudo‑code:

tsts
1app.post('/signup', validate(SignupSchema), async (req, res) => {2  const { email, password } = req.body3  const hash = await hashPassword(password)4  const user = await db.users.insert({ email, password_hash: hash })5  const session = await createSession(user.id)6  res.cookie('sid', session.id, { httpOnly: true, secure: true, sameSite: 'Lax' })7  res.status(201).json({ id: user.id })8})

Mini‑exercice: ajoutez un middleware d’erreurs qui transforme toute exception en JSON {error: code, message, correlationId} et journalise avec le même correlationId. Ajoutez un arrêt gracieux du serveur (SIGTERM) qui termine les connexions en cours avant de quitter.

Un serveur fiable gère les délais et la pression. Les timeouts protègent des clients lents; un limiteur de débit protège des abus; des logs structurés avec un identifiant de requête simplifient le suivi. La fermeture gracieuse évite de rompre des écritures en vol et limite la corruption de données.

#Animation: vie d’une requête

1
Client
POST /signup (JSON)
2
API
Valider; hacher le mot de passe; créer l’utilisateur
3
DB
INSERT users ; renvoyer id
4
API → Client
201 Created + Set‑Cookie sid sécurisé

#Diagramme: timeouts et arrêt gracieux

Client
Proxy
API
appel async/retour activation fragments
OPT: Timeouts (connect/read)
REGION: Arrêt gracieux
1. POST /signup

#Atelier interactif : cycle requête–réponse

Cycle requête-réponse API
Scénario:
1
Client → API
2
Middleware
3
Validation
4
Contrôleur
5
DB
6
Réponse 201
7
Proxy → Client
Client → APIÉtape 1 / 7
POST /signup (JSON) via proxy/load balancer
Pas de réponse à cette étape.
Vitesse1000ms
Bonnes pratiques: identifiant de requête, erreurs JSON stables, cookies sécurisés, fermeture gracieuse.

Bonnes pratiques à observer durant l’animation :

  • Toujours générer un identifiant de requête et le propager dans les logs et la réponse (X‑Request‑ID).
  • Réponses d’erreur stables en JSON (code machine + message humain + correlationId).
  • Cookies de session sécurisés : HttpOnly, Secure, SameSite=Lax, durée raisonnable.
  • Arrêt gracieux : cesser d’accepter, drainer les connexions, puis quitter.

#Cache HTTP : HIT, MISS, 304

Simulateur de cache HTTP (navigateur/CDN simple)
Politique de réponse
ETag présent
État côté cache
0s
If-None-Match
HITfrais (< max-age=600s)
Règle: no-store → MISS ; frais → HIT ; sinon ETag+If-None-Match → 304 ; à défaut → MISS

Conseils :

  • Préférez ETag + If‑None‑Match pour la revalidation.
  • Cache-Control: no-store désactive le cache (utile pour données sensibles).
  • Un max-age court + revalidation conditionnelle → bonne latence et cohérence.

#Explorateur d’en‑têtes HTTP

Explorateur d’en-têtes HTTP (réponse)
Bonnes pratiquesCacheCookiesTraçabilité
Contenu & format
Traçabilité
Cache
Cookie de session
SameSite
Impact
Cacheable (public, max-age=600s)Cookie sécurisé (HttpOnly, Secure, SameSite OK)Traçabilité: X-Request-ID présent
Astuce: pour des APIs, préférez JSON stable et incluez un identifiant de requête. Cookies de session doivent être HttpOnly + Secure, SameSite=Lax/Strict selon le cas.
Aperçu des en-têtes
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=600
ETag: W/"abc123"
X-Request-ID: b3b0d2b1c45e
Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000

Conseils pratiques :

  • Réponses API en JSON avec Content-Type: application/json; charset=utf-8.
  • Ajoutez X-Request-ID et exposez‑le côté navigateur via Access-Control-Expose-Headers.
  • Cookies de session: HttpOnly; Secure; SameSite=Lax/Strict; Path=/; Max-Age=....
  • Cache: combinez max-age raisonnable + ETag pour 304 efficaces.

#Limiteur de débit (Token Bucket)

Limiteur de débit — Seau à jetons (Token Bucket)
RPS limiteBurstTrafic
Limite (tokens/s)
20
Capacité (burst)
40
Trafic entrant (req/s)
50
Fenêtre5s
Total250
Admis138 (55.2%)
Bloqués112 (44.8%)
Chronologie (100ms par colonne)
admis
bloqués
Capacité actuelle (tokens) dernière colonne: 0.0
Conseils: dimensionnez limitRPS pour le nominal, ajustez le burst pour absorber des pics courts. Combinez avec files d'attente côté client/proxy, retours 429 avec Retry-After.

Bonnes pratiques :

  • Placez un limiteur au bord (proxy/API gateway) et au niveau de l’application.
  • En dépassement, répondez 429 Too Many Requests et indiquez Retry-After.
  • Combinez avec des files d’attente côté proxy et une montée/descente de trafic progressive.
  • Sur APIs sensibles, limitez par clé client, IP et éventuellement par route.

#Arrêt gracieux (SIGTERM)

Arrêt gracieux (SIGTERM) — simulation
Acceptation ONAucune deadline
Trafic entrant (req/s)
30
Durée moyenne requête (s)
2.5
Deadline d’arrêt (s)
10
In‑flight0
Terminées0
Interrompues0
Rejetées (post‑SIGTERM)0
Total0
Requêtes en cours (barres = temps restant)
Aucune requête en cours
Bonnes pratiques: sur SIGTERM, cessez d’accepter, exposez une deadline raisonnable (env/param), drainez les connexions, et forcez l’arrêt à l’échéance pour éviter des écritures en vol indéfinies. Couplé à un orchestrateur (systemd/K8s) pour rediriger le trafic.

Points clés :

  • Sur SIGTERM : cessez d’accepter, drainez les requêtes en cours, arrêtez à l’échéance.
  • Rendez vos traitements idempotents et transactionnels pour éviter les demi‑écritures.
  • Exposez un délai configurable (env/param) cohérent avec l’orchestrateur (systemd/K8s).
  • Couplé au load‑balancer pour retirer le nœud du pool avant l’arrêt.

#CORS : origins, credentials et preflight

Explorateur CORS (Origin, Credentials, Preflight)
Preflight: OKRequête: AUTORISÉE
Client (navigateur)
Origin
Méthode
Content-Type
Serveur (API)
Politique
Allow-Credentials
Allow-Methods
Allow-Headers
Expose-Headers
Preflight (OPTIONS)
Preflight: OK
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Auth-Token
Credentials: include
Réponse préflight
Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Auth-Token
Vary: Origin
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Réponse finale
Requête: AUTORISÉE
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-ID
Rappel: avec credentials, ne pas utiliser `Access-Control-Allow-Origin: *`. Faites écho de l’origine autorisée et ajoutez Vary: Origin.
Conseils: préférez listes blanches par origin, exposez les en-têtes utiles via Expose-Headers, et gardez les règles cohérentes côté proxy/API et app. Les en-têtes de requête personnalisés déclenchent un preflight.

Bonnes pratiques :

  • N’utilisez JAMAIS Access-Control-Allow-Origin: * avec des credentials ; faites écho de l’origin autorisé et ajoutez Vary: Origin.
  • Préférez une liste blanche stricte d’origins, ou un wildcard de sous‑domaines contrôlé (ex. *.example.com).
  • Exposez uniquement les en‑têtes utiles (Access-Control-Expose-Headers: X-Request-ID, ETag, …).
  • Un preflight est requis si la requête n’est pas « simple » (méthodes non simples, en‑têtes personnalisés, Content-Type non simple).

#Retry et backoff exponentiel avec jitter

Retry + backoff exponentiel avec jitter
Succès à l’essai #3 (406ms)Attempts: 3Temps total: 406ms
Délais
Base (ms)200
Facteur2.0
Max (ms)4000
Stratégie
Max essais6
Jitter
Jitter recommandé: full ou decorrelated, pour éviter la synchronisation de retries (thundering herd).
Succès probabiliste
P(succès)40%
Seed42
Modélise un service flaky: chaque essai a une chance de réussite indépendante.
Chronologie des essais
#1
#2
#3
Barres verticales: instants des tentatives. Vert = succès; Rouge = échec. Les délais sont calculés selon la stratégie sélectionnée.
Bonnes pratiques: limiter le nombre d’essais, utiliser jitter, arrêter après erreurs non-retryables (4xx), et propager un idempotency-key si nécessaire.

Conseils :

  • Limitez le nombre d’essais et appliquez un backoff exponentiel avec jitter (full ou decorrelated).
  • Arrêtez les retries sur erreurs non‑retryables (4xx), et utilisez des idempotency-key pour éviter les doublons.
  • Combinez avec un limiteur et un circuit‑breaker en amont pour protéger le service.

#Exemples de code pratiques

#Middleware d’erreurs (Problem Details) + corrélation

tsts
1import type { Request, Response, NextFunction } from 'express'2import { randomUUID } from 'node:crypto'3import pino from 'pino'4 5export const logger = pino({ level: process.env.LOG_LEVEL || 'info' })6 7export function withRequestId(req: Request, res: Response, next: NextFunction) {8  const traceId = req.get('x-request-id') || randomUUID()9  res.locals.traceId = traceId10  res.setHeader('X-Request-ID', traceId)11  ;(req as any).logger = logger.child({ traceId })12  next()13}14 
Ordre des middlewares

Montez d’abord les middlewares techniques (request id, parseurs), puis vos routes, et enfin le error handler pour capter toutes les erreurs.

#Timeouts côté Node/Express

tsts
1import http from 'node:http'2const server = http.createServer(app)3 4// Évitez les clients lents: timeouts raisonnables5server.headersTimeout = 5_000       // délai pour recevoir les en‑têtes6server.keepAliveTimeout = 5_000     // durée d’inactivité sur connexions keep‑alive7;(server as any).requestTimeout = 10_000 // (Node >=18) délai total d’une requête8 9// Timeouts côté client (fetch/undici) avec AbortController10async function getWithTimeout(url: string, ms = 5000) {11  const ac = new AbortController()12  const t = setTimeout(() => ac.abort(), ms)13  try {14    const res = await fetch(url, { signal: ac.signal })

#Limiteur de débit (token bucket simple)

tsts
1type Bucket = { tokens: number; last: number }2const buckets = new Map<string, Bucket>()3 4export function rateLimit({ rate = 5, perMs = 1000, burst = 10 } = {}) {5  return (req, res, next) => {6    const key = req.ip // En prod: combinez clé API, IP, route7    const now = Date.now()8    const b: Bucket = buckets.get(key) || { tokens: burst, last: now }9    // Refill10    const refill = ((now - b.last) / perMs) * rate11    b.tokens = Math.min(burst, b.tokens + refill)12    b.last = now13    if (b.tokens < 1) {14      res.setHeader('Retry-After', '1')
IP et proxies

Ne faites confiance à X‑Forwarded‑For que si votre serveur connaît les proxies en amont (app.set('trust proxy', true)) et qu’ils sont sous votre contrôle.

#Arrêt gracieux (SIGTERM)

tsts
1import type { Socket } from 'node:net'2 3const PORT = process.env.PORT ? Number(process.env.PORT) : 30004const server = app.listen(PORT, () => logger.info({ PORT }, 'listening'))5 6const sockets = new Set<Socket>()7server.on('connection', (s: Socket) => {8  sockets.add(s)9  s.on('close', () => sockets.delete(s))10})11 12let shuttingDown = false13process.on('SIGTERM', () => {14  if (shuttingDown) return

#Endpoints de santé

tsts
1app.get('/healthz', (_req, res) => res.status(200).json({ ok: true }))2app.get('/readyz', async (_req, res) => {3  try {4    await db.query('SELECT 1')5    res.json({ ready: true })6  } catch {7    res.status(503).json({ ready: false })8  }9})

#Quiz rapide

Après une création réussie d’utilisateur avec session, quel statut et format recommandez‑vous ?
Après une création réussie d’utilisateur avec session, quel statut et format recommandez‑vous ?