@nodefony/http 10.0.0-alpha.9 → 10.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +12 -3
  2. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateParam.js +1 -1
  5. package/dist/index.js +6 -4
  6. package/dist/nodefony/command/assetsPublishCommand.js +1 -1
  7. package/dist/nodefony/command/certificatesCommand.js +1 -2
  8. package/dist/nodefony/command/networkCommand.js +1 -1
  9. package/dist/nodefony/command/proxyGenerateCommand.js +1 -1
  10. package/dist/nodefony/config/config.js +26 -4
  11. package/dist/nodefony/interfaces/IRouting.js +1 -0
  12. package/dist/nodefony/interfaces/ISecurity.js +1 -0
  13. package/dist/nodefony/service/ProfilerAdminApi.js +2 -1
  14. package/dist/nodefony/service/audit-logger.js +16 -13
  15. package/dist/nodefony/service/certificates.js +87 -144
  16. package/dist/nodefony/service/error-renderer.js +11 -8
  17. package/dist/nodefony/service/http-kernel.js +342 -165
  18. package/dist/nodefony/service/pretty-request-logger.js +3 -10
  19. package/dist/nodefony/service/servers/clientError.js +2 -2
  20. package/dist/nodefony/service/servers/server-http.js +23 -23
  21. package/dist/nodefony/service/servers/server-https.js +41 -42
  22. package/dist/nodefony/service/servers/server-static.js +13 -17
  23. package/dist/nodefony/service/servers/server-websocket-secure.js +11 -10
  24. package/dist/nodefony/service/servers/server-websocket.js +11 -10
  25. package/dist/nodefony/service/servers/wsHeartbeat.js +2 -2
  26. package/dist/nodefony/service/sessions/sessions-service.js +22 -15
  27. package/dist/nodefony/service/trace.js +3 -2
  28. package/dist/nodefony/service/upload/upload-service.js +11 -10
  29. package/dist/nodefony/service/x509.js +324 -0
  30. package/dist/nodefony/src/assets/prebuiltUi.js +1 -1
  31. package/dist/nodefony/src/context/Context.js +34 -19
  32. package/dist/nodefony/src/context/forwarded.js +4 -4
  33. package/dist/nodefony/src/context/http/HttpContext.js +133 -88
  34. package/dist/nodefony/src/context/http/Request.js +108 -46
  35. package/dist/nodefony/src/context/http/Response.js +134 -54
  36. package/dist/nodefony/src/context/http/parser.js +9 -10
  37. package/dist/nodefony/src/context/http/urlFastPath.js +35 -11
  38. package/dist/nodefony/src/context/http2/Response.js +53 -25
  39. package/dist/nodefony/src/context/metaData.js +1 -1
  40. package/dist/nodefony/src/context/trustProxy.js +4 -3
  41. package/dist/nodefony/src/context/websocket/Response.js +13 -6
  42. package/dist/nodefony/src/context/websocket/WebsocketContext.js +59 -26
  43. package/dist/nodefony/src/context/websocket/wsCloseReason.js +41 -0
  44. package/dist/nodefony/src/cookies/cookie.js +3 -3
  45. package/dist/nodefony/src/errors/httpError.js +6 -6
  46. package/dist/nodefony/src/errors/thrownMessage.js +27 -0
  47. package/dist/nodefony/src/profiler/FrameProfile.js +4 -3
  48. package/dist/nodefony/src/profiler/Profiler.js +4 -4
  49. package/dist/nodefony/src/servers/kernelServers.js +29 -0
  50. package/dist/nodefony/src/servers/portBinder.js +65 -23
  51. package/dist/nodefony/src/session/session.js +18 -10
  52. package/dist/nodefony/src/session/sessionStoreFailure.js +2 -1
  53. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +24 -9
  54. package/dist/types/index.d.ts +16 -0
  55. package/dist/types/nodefony/config/config.d.ts +23 -0
  56. package/dist/types/nodefony/interfaces/IContext.d.ts +24 -7
  57. package/dist/types/nodefony/interfaces/ICookie.d.ts +18 -18
  58. package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -4
  59. package/dist/types/nodefony/interfaces/IResponse.d.ts +1 -1
  60. package/dist/types/nodefony/interfaces/IRouting.d.ts +87 -0
  61. package/dist/types/nodefony/interfaces/ISecurity.d.ts +45 -0
  62. package/dist/types/nodefony/interfaces/ISession.d.ts +8 -8
  63. package/dist/types/nodefony/interfaces/IUpload.d.ts +9 -9
  64. package/dist/types/nodefony/interfaces/index.d.ts +2 -0
  65. package/dist/types/nodefony/service/audit-logger.d.ts +14 -10
  66. package/dist/types/nodefony/service/certificates.d.ts +42 -57
  67. package/dist/types/nodefony/service/http-kernel.d.ts +192 -42
  68. package/dist/types/nodefony/service/servers/server-http.d.ts +4 -1
  69. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -1
  70. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +4 -1
  71. package/dist/types/nodefony/service/servers/server-websocket.d.ts +4 -1
  72. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +6 -2
  73. package/dist/types/nodefony/service/upload/upload-service.d.ts +1 -1
  74. package/dist/types/nodefony/service/x509.d.ts +86 -0
  75. package/dist/types/nodefony/src/context/Context.d.ts +21 -10
  76. package/dist/types/nodefony/src/context/forwarded.d.ts +3 -3
  77. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +75 -18
  78. package/dist/types/nodefony/src/context/http/Request.d.ts +39 -2
  79. package/dist/types/nodefony/src/context/http/Response.d.ts +47 -7
  80. package/dist/types/nodefony/src/context/http/parser.d.ts +1 -1
  81. package/dist/types/nodefony/src/context/http2/Response.d.ts +32 -2
  82. package/dist/types/nodefony/src/context/websocket/Response.d.ts +4 -4
  83. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +11 -2
  84. package/dist/types/nodefony/src/context/websocket/wsCloseReason.d.ts +21 -0
  85. package/dist/types/nodefony/src/cookies/cookie.d.ts +9 -9
  86. package/dist/types/nodefony/src/errors/httpError.d.ts +7 -7
  87. package/dist/types/nodefony/src/errors/thrownMessage.d.ts +12 -0
  88. package/dist/types/nodefony/src/profiler/Profiler.d.ts +28 -28
  89. package/dist/types/nodefony/src/servers/kernelServers.d.ts +20 -0
  90. package/dist/types/nodefony/src/servers/portBinder.d.ts +16 -65
  91. package/dist/types/nodefony/src/session/session.d.ts +12 -12
  92. package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +1 -1
  93. package/docs/cookies.md +25 -25
  94. package/docs/index.md +4 -2
  95. package/docs/observabilite.md +27 -27
  96. package/docs/rate-limit.md +20 -20
  97. package/docs/servers.md +82 -79
  98. package/docs/session.md +74 -69
  99. package/docs/upload.md +49 -43
  100. package/package.json +9 -12
@@ -55,7 +55,7 @@ flowchart TD
55
55
  Trois idées à retenir :
56
56
 
57
57
  1. **La clé est l'IP, pas l'utilisateur.** On borne du **trafic**, pas des identités. L'IP est
58
- résolue exactement comme pour les logs et l'audit (`resolveForwarded()`, `http-kernel.ts:991`) —
58
+ résolue exactement comme pour les logs et l'audit (`resolveForwarded()`, `http-kernel.ts:1089`) —
59
59
  non falsifiable tant que `trustProxy` n'accorde pas sa confiance à un proxy.
60
60
  2. **Le verdict porte tout.** Un seul appel `hit(key)` (`IRateLimitStore.ts:79`) rend un
61
61
  `RateLimitVerdict` (`IRateLimitStore.ts:20`) qui contient déjà limite, restant, reset et
@@ -106,7 +106,7 @@ Trois choix structurent l'implémentation, et chacun est un compromis assumé.
106
106
 
107
107
  **Désactivé par défaut — opt-in explicite.** En cloud-native, le plafond par IP est souvent mieux placé
108
108
  à l'**ingress/gateway** (il voit tout le trafic, tous les pods, et rejette avant le coût TLS). Le module
109
- laisse donc `rateLimit` désarmé par défaut (`config.ts:1065`) : `null` tant qu'on ne l'active pas → **0
109
+ laisse donc `rateLimit` désarmé par défaut (`config.ts:1069`) : `null` tant qu'on ne l'active pas → **0
110
110
  coût** sur le chemin chaud. On l'active quand on n'a **pas** d'edge devant soi (bare-metal, VPS), ou en
111
111
  défense en profondeur.
112
112
 
@@ -118,7 +118,7 @@ court intervalle (`MemoryRateLimitStore.ts:29`). Acceptable pour une défense de
118
118
  _sliding window_ viendrait en option si le besoin s'en fait sentir.
119
119
 
120
120
  **Refoulé avant toute allocation.** Le verdict est rendu **avant** le contexte, la portée DI et l'ALS
121
- (`http-kernel.ts:860`) : un flood coûte un `Map.get` et rien d'autre. Le contrat `hit()` est
121
+ (`http-kernel.ts:1097`) : un flood coûte un `Map.get` et rien d'autre. Le contrat `hit()` est
122
122
  **synchrone** à dessein (`IRateLimitStore.ts:8`) — aucune `Promise`, aucune microtask sur le chemin de
123
123
  chaque requête.
124
124
 
@@ -147,7 +147,7 @@ export default defineConfig(() => ({
147
147
  ```
148
148
 
149
149
  Les trois clés `enabled` / `windowS` / `max` sont **éditables à chaud** (`runtimeMutable`) : le kernel
150
- reconstruit le compteur sans redémarrage (`configureRateLimit()`, `http-kernel.ts:416`).
150
+ reconstruit le compteur sans redémarrage (`configureRateLimit()`, `http-kernel.ts:444`).
151
151
 
152
152
  ### 2. Observer le 429 et les en-têtes
153
153
 
@@ -178,8 +178,8 @@ X-RateLimit-Reset: 1753082460
178
178
  Retry-After: 42
179
179
  ```
180
180
 
181
- - `X-RateLimit-Remaining` : requêtes restantes dans la fenêtre (`http-kernel.ts:1001`).
182
- - `X-RateLimit-Reset` : **epoch en secondes** de la fin de fenêtre (`http-kernel.ts:1003`).
181
+ - `X-RateLimit-Remaining` : requêtes restantes dans la fenêtre (`http-kernel.ts:1099`).
182
+ - `X-RateLimit-Reset` : **epoch en secondes** de la fin de fenêtre (`http-kernel.ts:1031`).
183
183
  - `Retry-After` (sur le `429` seulement) : secondes à attendre, **jamais 0** — un `Retry-After: 0`
184
184
  relancerait un client bien élevé immédiatement (`MemoryRateLimitStore.ts:80`).
185
185
 
@@ -203,12 +203,12 @@ flowchart TD
203
203
 
204
204
  Autour de ce cœur, le kernel orchestre le cycle de vie :
205
205
 
206
- - **Construction / reconfiguration** : `configureRateLimit()` (`http-kernel.ts:417`) instancie le store
207
- depuis la config (`windowMs = windowS × 1000`, `http-kernel.ts:417`) et arme un `GcScheduler`
208
- (`http-kernel.ts:422`) qui **purge les fenêtres expirées** hors du chemin chaud.
209
- - **Émission HTTP** : sous le quota, les en-têtes `X-RateLimit-*` sont posés (`http-kernel.ts:1000`) et
210
- la requête continue ; au-delà, `Retry-After` (`http-kernel.ts:1007`) puis `writeHead(429)`
211
- (`http-kernel.ts:1012`) — corps vide, on ne journalise pas chaque rejet (amplificateur sous flood).
206
+ - **Construction / reconfiguration** : `configureRateLimit()` (`http-kernel.ts:444`) instancie le store
207
+ depuis la config (`windowMs = windowS × 1000`, `http-kernel.ts:454`) et arme un `GcScheduler`
208
+ (`http-kernel.ts:460`) qui **purge les fenêtres expirées** hors du chemin chaud.
209
+ - **Émission HTTP** : sous le quota, les en-têtes `X-RateLimit-*` sont posés (`http-kernel.ts:1098`) et
210
+ la requête continue ; au-delà, `Retry-After` (`http-kernel.ts:1105`) puis `writeHead(429)`
211
+ (`http-kernel.ts:1110`) — corps vide, on ne journalise pas chaque rejet (amplificateur sous flood).
212
212
  - **Borne mémoire** : au cap `maxTracked`, le store purge les expirées puis évince en **FIFO**
213
213
  (`#evict`, `MemoryRateLimitStore.ts:169`) — la mémoire ne dérive jamais.
214
214
 
@@ -242,19 +242,19 @@ Et un réglage **séparé**, propre au WebSocket, à la racine du module :
242
242
  Un WebSocket ne peut **pas** recevoir un `429` : au moment où le rate-limit décide, le `101 Switching
243
243
  Protocols` est déjà parti sur le fil (émis par la bibliothèque `ws`). Le refoulement se fait donc par
244
244
  une **fermeture RFC 6455 `1013 Try Again Later`**, décidée dans `onWebsocketRequest()`
245
- (`http-kernel.ts:1549`) — **avant** `enterScope`, l'ALS et le pipeline, comme le `429` HTTP.
245
+ (`http-kernel.ts:1848`) — **avant** `enterScope`, l'ALS et le pipeline, comme le `429` HTTP.
246
246
 
247
247
  Deux plafonds distincts, tous deux par IP forwarded-aware :
248
248
 
249
249
  | Plafond | Ce qu'il borne | Source de config | Refus |
250
250
  | ---------------------- | ---------------------------------------------- | ----------------------- | ---------------------------------------------------------- |
251
- | Débit de handshakes | Ouvertures/seconde (le **même** compteur HTTP) | `rateLimit` | close `1013` (`http-kernel.ts:1574`) |
252
- | Connexions simultanées | Sockets **ouvertes** en même temps par IP | `wsMaxConnectionsPerIp` | close `1013` — `tryAcquire` refuse (`http-kernel.ts:1583`) |
251
+ | Débit de handshakes | Ouvertures/seconde (le **même** compteur HTTP) | `rateLimit` | close `1013` (`http-kernel.ts:448`) |
252
+ | Connexions simultanées | Sockets **ouvertes** en même temps par IP | `wsMaxConnectionsPerIp` | close `1013` — `tryAcquire` refuse (`http-kernel.ts:1882`) |
253
253
 
254
254
  Le cap concurrent est porté par un compteur dédié, `WsConnectionCounter` (`WsConnectionCounter.ts:18`) :
255
255
  `tryAcquire(ip)` (`WsConnectionCounter.ts:33`) réserve un créneau à l'upgrade, `release(ip)`
256
256
  (`WsConnectionCounter.ts:45`) le rend à la fermeture — branché sur `ws.once("close", …)`
257
- (`http-kernel.ts:1536`), donc jamais de fuite de compteur, même sur un `terminate` de heartbeat.
257
+ (`http-kernel.ts:1887`), donc jamais de fuite de compteur, même sur un `terminate` de heartbeat.
258
258
 
259
259
  > [!WARNING]
260
260
  > `wsMaxConnectionsPerIp` a une **portée par process** (1 pod) : il ne voit que le trafic de son propre
@@ -280,14 +280,14 @@ Un adapter doit fournir : `hit(key)` (verdict de fenêtre), `gc()` (purge), `lis
280
280
 
281
281
  | Domaine | Norme | Ancrage |
282
282
  | ---------------------------------- | ---------------- | ------------------------------------------------- |
283
- | `429 Too Many Requests` | RFC 6585 §4 | `writeHead(429)` (`http-kernel.ts:1012`) |
284
- | `Retry-After` (delta-seconds) | RFC 9110 §10.2.3 | en-tête posé sur le `429` (`http-kernel.ts:1007`) |
285
- | IP cliente derrière proxy | RFC 7239 | `resolveForwarded()` (`http-kernel.ts:991`) |
283
+ | `429 Too Many Requests` | RFC 6585 §4 | `writeHead(429)` (`http-kernel.ts:1110`) |
284
+ | `Retry-After` (delta-seconds) | RFC 9110 §10.2.3 | en-tête posé sur le `429` (`http-kernel.ts:1105`) |
285
+ | IP cliente derrière proxy | RFC 7239 | `resolveForwarded()` (`http-kernel.ts:1089`) |
286
286
  | WebSocket — close `1013` Try Again | RFC 6455 §7.4.1 | refus d'upgrade (`http-kernel.ts:1378`) |
287
287
 
288
288
  > [!NOTE]
289
289
  > Les en-têtes émis sont la famille **de facto** `X-RateLimit-Limit/Remaining/Reset`
290
- > (`http-kernel.ts:1000`), largement déployée et lue par les clients. Le brouillon IETF
290
+ > (`http-kernel.ts:1098`), largement déployée et lue par les clients. Le brouillon IETF
291
291
  > `draft-ietf-httpapi-ratelimit-headers` (en-têtes `RateLimit` / `RateLimit-Policy`) n'est **pas** encore
292
292
  > émis — une évolution possible, pas une régression : rien ne le promet aujourd'hui.
293
293
 
package/docs/servers.md CHANGED
@@ -122,13 +122,13 @@ les probes.
122
122
 
123
123
  **Un port TLS qui parle deux protocoles.** Quand `servers.https.protocol` vaut `"2.0"` (le défaut),
124
124
  Nodefony crée un serveur HTTP/2 sécurisé avec `allowHTTP1: true` (`ServerHttps.createServerH2()`,
125
- `server-https.ts:174`) : les clients modernes négocient `h2` par ALPN, les autres restent en HTTP/1.1
125
+ `server-https.ts:187`) : les clients modernes négocient `h2` par ALPN, les autres restent en HTTP/1.1
126
126
  **sur le même port**. Le protocole effectif est relu socket par socket pour taguer le contexte
127
127
  (`server-https.ts:222`).
128
128
 
129
129
  **Le WebSocket n'est jamais un citoyen de seconde zone.** Il est adossé au serveur HTTP porteur
130
130
  (`server-websocket.ts:80`), passe par le **même** rate-limit d'IP que les requêtes HTTP — un upgrade
131
- _est_ une requête HTTP (`HttpKernel.onWebsocketRequest()`, `http-kernel.ts:1549`) —, hérite de la même
131
+ _est_ une requête HTTP (`HttpKernel.onWebsocketRequest()`, `http-kernel.ts:1848`) —, hérite de la même
132
132
  session et du même firewall, et se ferme avec le même soin qu'une réponse HTTP.
133
133
 
134
134
  > [!NOTE]
@@ -249,7 +249,7 @@ export default PingController;
249
249
 
250
250
  ### 4. Ce qu'on observe au boot
251
251
 
252
- Le kernel démarre les serveurs à la phase `onReady` (`Kernel.ts:1255`), puis affiche les URL réellement
252
+ Le kernel démarre les serveurs à la phase `onReady` (`Kernel.ts:1274`), puis affiche les URL réellement
253
253
  en écoute — le récap de développement liste HTTP, HTTP/2, WS et WSS dans cet ordre
254
254
  (`BootReporter.ts:389`) :
255
255
 
@@ -264,7 +264,7 @@ en écoute — le récap de développement liste HTTP, HTTP/2, WS et WSS dans ce
264
264
  ```
265
265
 
266
266
  Hors écran animé (production, CI, `--debug`), ce sont les bannières par serveur qui sortent
267
- (`ServerHttp.showBanner()`, `server-http.ts:232`, appelées par le kernel — `Kernel.ts:561`) :
267
+ (`ServerHttp.showBanner()`, `server-http.ts:242`, appelées par le kernel — `Kernel.ts:561`) :
268
268
 
269
269
  ```text
270
270
  Server Listen on http://127.0.0.1:5151 Family: IPv4 Protocol : 1.1
@@ -327,34 +327,34 @@ Choisir en cinq secondes :
327
327
  | `server-websocket-secure` | `wss://` — 5152 | `server-https` actif | WebSocket adossé au serveur TLS. |
328
328
  | `server-static` | (aucun port propre) | toujours enregistré | Fichiers statiques, en **repli** après le routing. |
329
329
 
330
- L'assemblage est fait par `HttpKernel.initServers()` (`http-kernel.ts:1046`) : chaque serveur est
331
- consulté sur son drapeau `active`, un serveur désactivé est **sauté**, pas créé (`http-kernel.ts:235`).
330
+ L'assemblage est fait par `HttpKernel.initServers()` (`http-kernel.ts:1128`) : chaque serveur est
331
+ consulté sur son drapeau `active`, un serveur désactivé est **sauté**, pas créé (`http-kernel.ts:1139`).
332
332
  Les serveurs WebSocket ne sont montés que si leur porteur l'a été.
333
333
 
334
334
  ### `server-http` — HTTP/1.1 en clair
335
335
 
336
336
  Le plus simple, et celui qui porte le trafic en cloud-native. `ServerHttp.createServer()`
337
- (`server-http.ts:74`) crée le `http.Server`, applique les réglages de transport, branche
337
+ (`server-http.ts:83`) crée le `http.Server`, applique les réglages de transport, branche
338
338
  `onHttpRequest`, puis écoute selon la politique de port.
339
339
 
340
340
  - **Actif** si `servers.http` n'est pas `false` — la décision est prise dès le constructeur
341
341
  (`server-http.ts:56`), et le port est lu de la config d'app (`ServerHttp.setPort()`,
342
- `server-http.ts:67`).
342
+ `server-http.ts:74`).
343
343
  - **Réglages appliqués** : `requestTimeout` (anti slow-loris), `maxHeadersCount`, `timeout` de socket
344
344
  (qui émet un événement `onTimeout`), `keepAliveTimeout`.
345
345
  - **Erreurs de protocole** : l'événement `clientError` est traité explicitement
346
- (`server-http.ts:167`) — voir la section Résilience, c'est un piège Node à part entière.
346
+ (`server-http.ts:186`) — voir la section Résilience, c'est un piège Node à part entière.
347
347
 
348
348
  ### `server-https` — TLS, et HTTP/2 par défaut
349
349
 
350
- Deux branches dans un seul service, choisies sur `servers.https.protocol` (`server-https.ts:39`) :
350
+ Deux branches dans un seul service, choisies sur `servers.https.protocol` (`server-https.ts:100`) :
351
351
 
352
352
  - **`"2.0"` (défaut)** → `http2.createSecureServer` avec `allowHTTP1: true`
353
- (`ServerHttps.createServerH2()`, `server-https.ts:174`). Les bornes anti-DoS HTTP/2 ne sont posées
353
+ (`ServerHttps.createServerH2()`, `server-https.ts:187`). Les bornes anti-DoS HTTP/2 ne sont posées
354
354
  **que si elles sont configurées**, pour ne pas écraser les défauts de Node
355
- (`maxSessionMemory`, `server-https.ts:185`).
355
+ (`maxSessionMemory`, `server-https.ts:215`).
356
356
  Les erreurs de session et de flux sont journalisées sans tuer le serveur
357
- (`sessionError`, `server-https.ts:274`).
357
+ (`sessionError`, `server-https.ts:296`).
358
358
  - **`"1.1"`** → `https.createServer` classique.
359
359
 
360
360
  Le certificat vient du service `certificates`, lu au moment de la création via
@@ -375,7 +375,7 @@ automatiquement un éventuel décalage de port.
375
375
  réglages **forcés** par Nodefony : `server` et `clientTracking: true`, requis par `broadcast()` et par
376
376
  le battement de cœur (`server-websocket.ts:80`).
377
377
  - **Keep-alive** armé à la création (`startHeartbeat()`, `server-websocket.ts:87`) et par connexion
378
- (`trackPong()`, `server-websocket.ts:107`).
378
+ (`trackPong()`, `server-websocket.ts:103`).
379
379
  - **Arrêt** : il s'inscrit en tête des écouteurs de terminaison
380
380
  (`prependOnceListener`, `server-websocket.ts:91`) — l'ordre
381
381
  compte, voir la section Arrêt gracieux.
@@ -400,7 +400,7 @@ C'est la distinction la plus utile de cette page, et celle qu'on rate le plus so
400
400
  | Question | Où ça se règle | Source |
401
401
  | ----------------------------------------- | ------------------------------ | --------------------------------------------------------- |
402
402
  | **Quels** serveurs, sur **quels ports** ? | `servers` (config d'app) | `serversSchema` (`src/nodefony/src/config/schema.ts:149`) |
403
- | **Comment** ces serveurs se comportent ? | `use("@nodefony/http", { … })` | `httpConfigSchema` (`config.ts:989`) |
403
+ | **Comment** ces serveurs se comportent ? | `use("@nodefony/http", { … })` | `httpConfigSchema` (`config.ts:993`) |
404
404
 
405
405
  Autrement dit : la **topologie** est une propriété du déploiement (elle change entre le poste du dev,
406
406
  la CI et le cluster) ; le **réglage** est une propriété de l'application.
@@ -424,16 +424,16 @@ Défauts matérialisés dans `defaultAppConfig` (`src/nodefony/src/config/defaul
424
424
  Table dérivée de `httpServerSchema` (`config.ts:272`) ; la section `https` reprend les mêmes clés et en
425
425
  ajoute une (`httpsServerSchema`, `config.ts:336`).
426
426
 
427
- | Option | Type | Défaut | Effet |
428
- | ---------------------------- | ----- | -------- | -------------------------------------------------------------------------------- |
429
- | `maxHeadersCount` | int | `2000` | Nombre maximum d'en-têtes par requête — anti _header flooding_. |
430
- | `keepAliveTimeout` | ms | `5000` | Délai de réutilisation de la socket TCP entre deux requêtes. |
431
- | `timeout` | ms | `120000` | Timeout global de socket. `0` = désactivé. |
432
- | `requestTimeout` | ms | `30000` | Délai de réception de la requête complète — **anti slow-loris**. |
433
- | `responseTimeout` | ms | `30000` | Délai d'envoi de la réponse complète (couche pipeline). |
434
- | `shutdownTimeout` | ms | `5000` | Drain au shutdown avant destruction forcée. Garder < grâce orchestrateur. |
435
- | `headers` | objet | `null` | En-têtes ajoutés à toutes les réponses. |
436
- | `rejectUnauthorized` (https) | bool | `false` | Rejette les certificats invalides. `false` en dev (auto-signés), `true` en prod. |
427
+ | Option | Type | Défaut | Effet |
428
+ | ---------------------------- | ----- | ------- | -------------------------------------------------------------------------------- |
429
+ | `maxHeadersCount` | int | `2000` | Nombre maximum d'en-têtes par requête — anti _header flooding_. |
430
+ | `keepAliveTimeout` | ms | `5000` | Délai de réutilisation de la socket TCP entre deux requêtes. |
431
+ | `timeout` | ms | `30000` | Timeout global de socket, aligné sur `responseTimeout`. `0` = désactivé. |
432
+ | `requestTimeout` | ms | `30000` | Délai de réception de la requête complète — **anti slow-loris**. |
433
+ | `responseTimeout` | ms | `30000` | Délai d'envoi de la réponse complète (couche pipeline). |
434
+ | `shutdownTimeout` | ms | `5000` | Drain au shutdown avant destruction forcée. Garder < grâce orchestrateur. |
435
+ | `headers` | objet | `null` | En-têtes ajoutés à toutes les réponses. |
436
+ | `rejectUnauthorized` (https) | bool | `false` | Rejette les certificats invalides. `false` en dev (auto-signés), `true` en prod. |
437
437
 
438
438
  Ces deux sections sont **permissives** (`z.looseObject`) : toute option supplémentaire de
439
439
  `http.Server` / `net.Server` / TLS (`insecureHTTPParser`, `ciphers`, `minVersion`…) est transmise telle
@@ -442,7 +442,7 @@ quelle à Node. C'est délibéré — un schéma strict effacerait silencieuseme
442
442
  ### Niveau 2 — HTTP/2
443
443
 
444
444
  Depuis `http2Schema` (`config.ts:353`), appliqué seulement si défini
445
- (`maxSessionMemory`, `server-https.ts:185`).
445
+ (`maxSessionMemory`, `server-https.ts:215`).
446
446
 
447
447
  | Option | Type | Défaut | Effet |
448
448
  | ---------------------- | ---- | ------ | ----------------------------------------------------------------------------- |
@@ -451,7 +451,7 @@ Depuis `http2Schema` (`config.ts:353`), appliqué seulement si défini
451
451
 
452
452
  ### Niveau 2 — WebSocket (`websocket` et `websocketSecure`)
453
453
 
454
- Depuis `websocketSchema` (`config.ts:517`). Les deux sections partagent la forme et les défauts ; le WSS
454
+ Depuis `websocketSchema` (`config.ts:521`). Les deux sections partagent la forme et les défauts ; le WSS
455
455
  lit `websocketSecure` (`config.ts:1058`).
456
456
 
457
457
  | Option | Type | Défaut | Effet |
@@ -484,7 +484,7 @@ panne, c'est une nuisance.
484
484
 
485
485
  ### La règle
486
486
 
487
- `resolvePortPolicy()` (`portBinder.ts:93`) tranche selon l'environnement, et la valeur explicite gagne
487
+ `resolvePortPolicy()` (cœur, `src/nodefony/src/service/dev/devProcess.ts:1767` — la même règle sert `nodefony doctor`) tranche selon l'environnement, et la valeur explicite gagne
488
488
  toujours :
489
489
 
490
490
  | Environnement | Défaut | Pourquoi |
@@ -504,29 +504,32 @@ WARNING Port 5151 déjà occupé → HTTP écoute sur 5153.
504
504
  Le décalage est **toujours annoncé** (`server-http.ts:130`) : jamais de dégradation silencieuse. Trois
505
505
  détails d'implémentation valent d'être connus, parce qu'ils expliquent des comportements surprenants.
506
506
 
507
- 1. **On retente au `listen()`, jamais après une sonde.** Demander « le port est-il libre ? » puis
508
- binder est une course : entre la réponse et le bind, un autre processus peut prendre le port. Le
509
- `listen()` est atomique — on retente donc sur l'échec réel (`bindWithFallback()`,
510
- `portBinder.ts:281`).
507
+ 1. **Le repli se décide au `listen()`, qui est atomique.** Une seule sonde le précède, sur le port
508
+ désiré : elle détecte un serveur déjà présent sur la boucle locale, que le noyau accorderait quand
509
+ même à un `0.0.0.0` (`detectPortConflict`). Elle est **sautée dans un worker de cluster**
510
+ (`cluster.isWorker`) : le port y est partagé par le maître, et le voisin qui répond est un frère,
511
+ pas un intrus. La course résiduelle retombe sur `EADDRINUSE` (`bindWithFallback()`,
512
+ `portBinder.ts:263`).
511
513
  2. **Le port de l'autre serveur est réservé.** Si HTTP est chassé de 5151, incrémenter naïvement le
512
514
  ferait voler 5152 à HTTPS, qui se décalerait à son tour. Les ports convoités par les autres serveurs
513
- sont sautés d'emblée (`buildBindPlan()`, `portBinder.ts:123`, réservation `portBinder.ts:123`).
515
+ sont sautés d'emblée (`buildBindPlan()`, `portBinder.ts:105`, réservation `portBinder.ts:105`).
514
516
  3. **Le gestionnaire d'erreur durable est posé APRÈS le bind.** Attaché avant, il verrait passer les
515
517
  `EADDRINUSE` de repli et terminerait le kernel en croyant à une panne
516
- (`ServerHttp.attachErrorHandler()`, `server-http.ts:186`).
518
+ (`ServerHttp.attachErrorHandler()`, `server-http.ts:199`).
517
519
 
518
520
  Quand le bind échoue pour de bon — `strict`, ou tous les replis épuisés, ou une erreur qui n'est pas un
519
521
  conflit de port — c'est **fatal** : message explicite puis terminaison du processus
520
- (`ServerHttp.reportBindError()`, `server-http.ts:199`). Un serveur qui n'écoute pas ne doit jamais
522
+ (`ServerHttp.reportBindError()`, `server-http.ts:212`). Un serveur qui n'écoute pas ne doit jamais
521
523
  laisser un processus se croire démarré.
522
524
 
523
525
  ### Le corollaire : les ports effectifs sont publiés
524
526
 
525
527
  Si le port peut glisser, alors « le serveur écoute sur 5151 » n'est plus une vérité mais une
526
528
  convention — et `nodefony status`, `nodefony stop` ou l'attente de disponibilité sonderaient un port
527
- que personne n'écoute. `HttpKernel.publishRuntimePorts()` (`http-kernel.ts:1096`) écrit donc la
529
+ que personne n'écoute. `HttpKernel.publishRuntimePorts()` (`http-kernel.ts:1195`) écrit donc la
528
530
  topologie réelle (pid, ports obtenus, ports désirés) dans un fichier d'état, **dans tous les
529
- environnements** : une application qui déclare son port via `PORT` (PaaS) sort aussi de la convention,
531
+ environnements** : une application qui déclare son port (`NF_PORT`, `NF_PORT_HTTPS` ou `servers.http.port`) sort
532
+ aussi de la convention,
530
533
  même en `strict`. L'écriture est au mieux-effort — une image en lecture seule ne fait jamais tomber un
531
534
  serveur qui, lui, écoute très bien.
532
535
 
@@ -537,19 +540,19 @@ serveur qui, lui, écoute très bien.
537
540
  **Générer un certificat est un confort de développement, pas une fonction de production.** Nodefony
538
541
  n'est pas une autorité de certification : en production, on fournit un vrai certificat (Let's Encrypt,
539
542
  ingress k8s, reverse-proxy). Le service crie un avertissement si ce n'est pas le cas
540
- (`Certificate.resolveStrategy()`, `certificates.ts:407`).
543
+ (`Certificate.resolveStrategy()`, `certificates.ts:369`).
541
544
 
542
545
  ### Les quatre stratégies
543
546
 
544
- Réglées par `certificates.strategy` (`certificatesSchema`, `config.ts:475`), résolues par
545
- `Certificate.resolveStrategy()` (`certificates.ts:407`).
547
+ Réglées par `certificates.strategy` (`certificatesSchema`, `config.ts:479`), résolues par
548
+ `Certificate.resolveStrategy()` (`certificates.ts:369`).
546
549
 
547
550
  | Stratégie | Quand l'utiliser | Ce qui se passe |
548
551
  | --------------- | ------------------------------------------- | --------------------------------------------------------------------------- |
549
552
  | `auto` (défaut) | On ne veut pas décider | `key`+`cert` fournis → `explicit` ; sinon mkcert en dev ; sinon auto-signé. |
550
553
  | `explicit` | **Production** | Charge `key`/`cert`/`ca` depuis la config. Erreur au boot si absents. |
551
554
  | `mkcert` | Développement avec HTTPS sans avertissement | CA locale de confiance → HMR cross-origin et WSS sans erreur navigateur. |
552
- | `selfsigned` | Secours, CI, machine sans mkcert | Auto-signé node-forge, non trusté. |
555
+ | `selfsigned` | Secours, CI, machine sans mkcert | Auto-signé (`node:crypto`), non trusté. |
553
556
 
554
557
  ```typescript
555
558
  // nodefony.config.ts — production : certificat fourni, jamais généré
@@ -572,13 +575,13 @@ export default defineConfig(() => ({
572
575
  > [!TIP]
573
576
  > Pour un HTTPS de développement **sans avertissement navigateur** (indispensable au HMR cross-origin
574
577
  > et au WSS) : `brew install mkcert nss && mkcert -install`. Nodefony le détecte tout seul, sinon il
575
- > l'annonce et retombe sur l'auto-signé (`certificates.ts:401`).
578
+ > l'annonce et retombe sur l'auto-signé (`certificates.ts:398`).
576
579
 
577
580
  ### Ce que le chemin `explicit` évite
578
581
 
579
- `node-forge` est une grosse dépendance. Elle est chargée **paresseusement**, uniquement sur le chemin
580
- de génération (`Certificate.loadForge()`, `certificates.ts:227`) : en production avec un certificat
581
- fourni, elle n'entre jamais dans le processus (`certificates.ts:227`).
582
+ Aucune génération : le certificat fourni est lu tel quel (`certificates.ts:330`). La génération de
583
+ l'auto-signé, elle, n'emploie que `node:crypto` (`x509.ts`) — aucune dépendance cryptographique
584
+ tierce n'est installée avec `@nodefony/http`.
582
585
 
583
586
  ### Conformité de l'auto-signé
584
587
 
@@ -588,7 +591,7 @@ invalid »). Celui de Nodefony respecte les règles qui comptent :
588
591
  | Exigence | Norme | Mise en œuvre |
589
592
  | --------------------------------------- | -------------------- | --------------------------------------------------------- |
590
593
  | Signature SHA-256, **jamais** SHA-1 | RFC 5280, CA/B Forum | `selfSigned.hash` par défaut `sha256` (`config.ts:400`) |
591
- | Numéro de série aléatoire 128 bits | RFC 5280 §4.1.2.2 | `Certificate.generateSerialHex()` (`certificates.ts:292`) |
594
+ | Numéro de série aléatoire 128 bits | RFC 5280 §4.1.2.2 | `Certificate.generateSerialHex()` (`certificates.ts:263`) |
592
595
  | Le SAN fait foi, pas le CN | RFC 6125 | SAN dérivé du kernel si non fourni (`config.ts:444`) |
593
596
  | `notBefore` reculé (décalage d'horloge) | pratique | `selfSigned.backdateMinutes`, défaut 5 (`config.ts:415`) |
594
597
  | Clé privée non lisible par tous | hygiène | `privateKeyMode` `0600` (`config.ts:499`) |
@@ -596,7 +599,7 @@ invalid »). Celui de Nodefony respecte les règles qui comptent :
596
599
  ### Régénération automatique
597
600
 
598
601
  Un certificat présent sur disque n'est pas forcément **adéquat**. `Certificate.isCertAdequate()`
599
- (`certificates.ts:570`) le régénère s'il est expiré, s'il est signé en SHA-1, ou si son SAN ne couvre
602
+ (`certificates.ts:535`) le régénère s'il est expiré, s'il est signé en SHA-1, ou si son SAN ne couvre
600
603
  plus les noms requis — le dernier cas est celui qui sauve : changer le domaine d'écoute sans ce
601
604
  contrôle laisserait un certificat obsolète en place indéfiniment.
602
605
 
@@ -618,7 +621,7 @@ sont ignorés** (`config.ts:965`), et l'IP retenue est celle de la socket réell
618
621
  | `"loopback"`, `"linklocal"`, `"uniquelocal"` | Préréglages de plages privées. |
619
622
 
620
623
  La politique est compilée **une seule fois** au premier usage (`HttpKernel.getTrustProxyChecker()`,
621
- `http-kernel.ts:538`, via `buildTrustProxy()`, `trustProxy.ts:100`) : aucune structure allouée par
624
+ `http-kernel.ts:573`, via `buildTrustProxy()`, `trustProxy.ts:103`) : aucune structure allouée par
622
625
  requête. La résolution de l'IP cliente remonte la chaîne **de droite à gauche** depuis la socket réelle
623
626
  (`resolveForwarded()`, `forwarded.ts:253`) — conforme RFC 7239 et à la recommandation OWASP.
624
627
 
@@ -626,7 +629,7 @@ requête. La résolution de l'IP cliente remonte la chaîne **de droite à gauch
626
629
 
627
630
  Barrière testée **avant le routage**, contre l'injection d'en-tête `Host`. Le domaine canonique du
628
631
  kernel est toujours accepté, plus le loopback en développement (`HttpKernel.compileAlias()`,
629
- `http-kernel.ts:941`). `false` (défaut) = ce socle seul ; une liste ajoute des vhosts (exact ou joker
632
+ `http-kernel.ts:987`). `false` (défaut) = ce socle seul ; une liste ajoute des vhosts (exact ou joker
630
633
  d'un seul niveau, `*.cdn.example.com`) ; `true` désactive la barrière — à réserver au cas où le proxy
631
634
  filtre déjà le `Host` (`config.ts:974`).
632
635
 
@@ -635,13 +638,13 @@ filtre déjà le `Host` (`config.ts:974`).
635
638
  Les navigateurs **n'appliquent pas CORS aux WebSockets**. Sans contrôle, une page tierce peut ouvrir un
636
639
  WS authentifié par le cookie de session de la victime (CSWSH, OWASP WSTG-CLNT-10). Nodefony exige donc
637
640
  par défaut que l'`Origin` du handshake corresponde au `Host` servi
638
- (`HttpKernel.checkWebsocketOrigin()`, `http-kernel.ts:599`), avec tolérance loopback en développement
641
+ (`HttpKernel.checkWebsocketOrigin()`, `http-kernel.ts:621`), avec tolérance loopback en développement
639
642
  et allowlist explicite pour les SPA cross-origine. Un refus se solde par une fermeture **1008 (Policy
640
643
  Violation)**, jamais par un code HTTP.
641
644
 
642
645
  > [!NOTE]
643
646
  > Une requête **sans** `Origin` (client non navigateur : script, agent, test) est acceptée
644
- > (`http-kernel.ts:551`). Ce n'est pas un trou : un attaquant non navigateur n'a aucun besoin de CSWSH,
647
+ > (`http-kernel.ts:630`). Ce n'est pas un trou : un attaquant non navigateur n'a aucun besoin de CSWSH,
645
648
  > il se connecte directement. Le contrôle protège les **utilisateurs**, pas le port.
646
649
 
647
650
  ## Probes de santé — `/livez` et `/readyz`
@@ -658,13 +661,13 @@ redémarrer le pod **en plein drain** par le kubelet, et casserait précisément
658
661
  protéger.
659
662
 
660
663
  Implémentation : court-circuit **total** du pipeline dans `HttpKernel.onHttpRequest()`
661
- (`http-kernel.ts:949`) — pas de contexte, pas de portée DI, pas de session, pas de journal par sonde,
662
- réponses pré-allouées (`HttpKernel.#respondHealth()`, `http-kernel.ts:473`). Et surtout : **avant le
664
+ (`http-kernel.ts:1009`) — pas de contexte, pas de portée DI, pas de session, pas de journal par sonde,
665
+ réponses pré-allouées (`HttpKernel.#respondHealth()`, `http-kernel.ts:508`). Et surtout : **avant le
663
666
  rate-limit**. Un kubelet qui reçoit un `429` croit le pod mort → cascade de redémarrages.
664
667
 
665
668
  | Option | Type | Défaut | Effet |
666
669
  | --------------- | ------ | --------- | ---------------------------------------------------------------------- |
667
- | `enabled` | bool | `true` | Expose les probes (`healthSchema`, `config.ts:940`). |
670
+ | `enabled` | bool | `true` | Expose les probes (`healthSchema`, `config.ts:944`). |
668
671
  | `livenessPath` | string | `/livez` | Chemin de la sonde de vie (`livenessProbe.httpGet.path` k8s). |
669
672
  | `readinessPath` | string | `/readyz` | Chemin de la sonde de disponibilité. |
670
673
  | `shutdownDelay` | ms | `0` | Délai entre la bascule `503` et le début du drain (propagation du LB). |
@@ -728,12 +731,12 @@ sequenceDiagram
728
731
  ```
729
732
 
730
733
  **Étape 1 — la disponibilité bascule en premier.** L'écouteur est posé à `onPostReady` pour être
731
- inséré **en tête** et donc s'exécuter **en premier** (`http-kernel.ts:515`). `readyz` répond `503`, le
734
+ inséré **en tête** et donc s'exécuter **en premier** (`http-kernel.ts:550`). `readyz` répond `503`, le
732
735
  load balancer cesse d'envoyer du trafic, et `shutdownDelay` laisse à cette information le temps de se
733
736
  propager.
734
737
 
735
- **Étape 2 — les clients WebSocket sont prévenus.** `Websocket.terminate()` (`server-websocket.ts:117`)
736
- envoie un message applicatif puis une **frame Close 1001 « Going Away »** (`server-websocket.ts:117`).
738
+ **Étape 2 — les clients WebSocket sont prévenus.** `Websocket.terminate()` (`server-websocket.ts:113`)
739
+ envoie un message applicatif puis une **frame Close 1001 « Going Away »** (`server-websocket.ts:113`).
737
740
  Sans elle, la coupure TCP ferait voir un **1006** au client — code réservé, jamais émis sur le fil,
738
741
  indiscernable d'une panne réseau. Avec 1001, le client sait qu'il doit simplement se reconnecter.
739
742
 
@@ -745,7 +748,7 @@ réponses en cours, sockets inactives fermées, destruction forcée au-delà de
745
748
  > [!IMPORTANT]
746
749
  > L'ordre WS-avant-HTTP n'est pas cosmétique. Les serveurs WebSocket s'inscrivent en
747
750
  > `prependOnceListener` (`server-websocket.ts:91`), les serveurs HTTP en `once`
748
- > (`server-http.ts:151`). Inversé, le terminator détruirait les sockets déjà upgradées **sans** frame
751
+ > (`server-http.ts:170`). Inversé, le terminator détruirait les sockets déjà upgradées **sans** frame
749
752
  > Close : retour du 1006 pour tous les clients temps réel.
750
753
 
751
754
  Deux garde-fous encadrent la séquence : `shutdownTimeout` **par serveur** (le drain nominal) et
@@ -784,9 +787,9 @@ processus à l'arrêt.
784
787
 
785
788
  L'upgrade WebSocket **est** une requête HTTP : il passe donc par le **même** compteur de rate-limit par
786
789
  IP que les requêtes ordinaires, vérifié avant toute allocation de contexte
787
- (`HttpKernel.onWebsocketRequest()`, `http-kernel.ts:1549`). Le `101` étant déjà émis par `ws`, un `429`
790
+ (`HttpKernel.onWebsocketRequest()`, `http-kernel.ts:1848`). Le `101` étant déjà émis par `ws`, un `429`
788
791
  est impossible → la connexion est fermée en **1013 « Try Again Later »**
789
- (`rateLimiter`, `http-kernel.ts:292`), sans
792
+ (`rateLimiter`, `http-kernel.ts:1863`), sans
790
793
  journalisation (un journal par handshake rejeté serait lui-même un amplificateur sous flood).
791
794
 
792
795
  Un second plafond, **désactivé par défaut**, borne le nombre de connexions **simultanées** par IP :
@@ -809,7 +812,7 @@ par seconde. Les choix visibles dans le code :
809
812
  - **Aucun timer par connexion** — un `setInterval` par serveur WebSocket, `unref`, et deux `number` par
810
813
  socket (`wsHeartbeat.ts:69`).
811
814
  - **Rien de compilé par requête** — la politique de trust-proxy, celle des `Origin` WS et les motifs de
812
- `trustedHosts` sont compilés une fois et mémoïsés (`http-kernel.ts:248`, `http-kernel.ts:248`).
815
+ `trustedHosts` sont compilés une fois et mémoïsés (`http-kernel.ts:407`, `http-kernel.ts:407`).
813
816
  - **Rejets avant allocation** — rate-limit HTTP et bornes WS sont vérifiés avant le contexte, la portée
814
817
  DI et l'ALS : un flood coûte une recherche dans une table de hachage.
815
818
  - **Probes hors pipeline** — réponses pré-allouées, aucun objet créé, aucun journal
@@ -817,7 +820,7 @@ par seconde. Les choix visibles dans le code :
817
820
  - **Fichiers statiques en repli** — depuis la bascule « router d'abord », une requête qui matche une
818
821
  route ne paie plus l'appel disque de `serve-static` (**+28 % de requêtes par seconde** mesurés en
819
822
  production mono-processus).
820
- - **`node-forge` jamais chargé en production** avec un certificat fourni (`certificates.ts:227`).
823
+ - **Aucune génération en production** avec un certificat fourni (`certificates.ts:330`).
821
824
 
822
825
  Ordre de grandeur mesuré : un processus Node saturé sur un cœur tient environ 400 requêtes/s en
823
826
  boucle locale avec dégradation gracieuse (1600 connexions concurrentes, aucun crash) ; côté WebSocket,
@@ -832,21 +835,21 @@ demande le backplane realtime.
832
835
 
833
836
  ## 📜 Normes appliquées
834
837
 
835
- | Domaine | Norme | Ancrage |
836
- | ------------------------------------- | ------------------ | -------------------------------------------------------------------------- |
837
- | HTTP/1.1 (sémantique, message) | RFC 9110, 9112 | `node:http` + pipeline `HttpKernel.onHttpRequest()` (`http-kernel.ts:949`) |
838
- | HTTP/2 | RFC 9113 | `ServerHttps.createServerH2()` (`server-https.ts:174`) |
839
- | HTTP/2 Rapid Reset | CVE-2023-44487 | `maxConcurrentStreams` (`config.ts:355`) |
840
- | En-têtes trop volumineux → 431 | RFC 6585 §5 | `handleClientError()` (`clientError.ts:15`) |
841
- | WebSocket — protocole | RFC 6455 | `ws@8` + options (`config.ts:496`) |
842
- | WebSocket — Close 1001 « Going Away » | RFC 6455 §7.4.1 | `Websocket.terminate()` (`server-websocket.ts:117`) |
843
- | WebSocket — 1009 « Message Too Big » | RFC 6455 §7.4.1 | `maxPayload` (`config.ts:543`) |
844
- | WebSocket — validation UTF-8 | RFC 6455 §8.1 | `skipUTF8Validation` (`config.ts:623`) |
845
- | WebSocket — compression | RFC 7692 | `perMessageDeflate` (`config.ts:567`) |
846
- | CSWSH (Origin au handshake) | OWASP WSTG-CLNT-10 | `HttpKernel.checkWebsocketOrigin()` (`http-kernel.ts:599`) |
847
- | En-têtes forwarded | RFC 7239 | `resolveForwarded()` (`forwarded.ts:253`) |
848
- | Certificat — série, SAN, extensions | RFC 5280 | `Certificate.generateSerialHex()` (`certificates.ts:292`) |
849
- | Certificat — identité par le SAN | RFC 6125 | `sanSchema` (`config.ts:442`) |
838
+ | Domaine | Norme | Ancrage |
839
+ | ------------------------------------- | ------------------ | --------------------------------------------------------------------------- |
840
+ | HTTP/1.1 (sémantique, message) | RFC 9110, 9112 | `node:http` + pipeline `HttpKernel.onHttpRequest()` (`http-kernel.ts:1009`) |
841
+ | HTTP/2 | RFC 9113 | `ServerHttps.createServerH2()` (`server-https.ts:187`) |
842
+ | HTTP/2 Rapid Reset | CVE-2023-44487 | `maxConcurrentStreams` (`config.ts:355`) |
843
+ | En-têtes trop volumineux → 431 | RFC 6585 §5 | `handleClientError()` (`clientError.ts:15`) |
844
+ | WebSocket — protocole | RFC 6455 | `ws@8` + options (`config.ts:496`) |
845
+ | WebSocket — Close 1001 « Going Away » | RFC 6455 §7.4.1 | `Websocket.terminate()` (`server-websocket.ts:113`) |
846
+ | WebSocket — 1009 « Message Too Big » | RFC 6455 §7.4.1 | `maxPayload` (`config.ts:543`) |
847
+ | WebSocket — validation UTF-8 | RFC 6455 §8.1 | `skipUTF8Validation` (`config.ts:623`) |
848
+ | WebSocket — compression | RFC 7692 | `perMessageDeflate` (`config.ts:567`) |
849
+ | CSWSH (Origin au handshake) | OWASP WSTG-CLNT-10 | `HttpKernel.checkWebsocketOrigin()` (`http-kernel.ts:621`) |
850
+ | En-têtes forwarded | RFC 7239 | `resolveForwarded()` (`forwarded.ts:253`) |
851
+ | Certificat — série, SAN, extensions | RFC 5280 | `Certificate.generateSerialHex()` (`certificates.ts:263`) |
852
+ | Certificat — identité par le SAN | RFC 6125 | `sanSchema` (`config.ts:446`) |
850
853
 
851
854
  ## ⚠️ Pièges (symptôme → cause → correction)
852
855
 
@@ -863,7 +866,7 @@ demande le backplane realtime.
863
866
  | Cascade de redémarrages sous charge | Sonde de santé soumise au rate-limit | Déjà géré : les probes court-circuitent avant le rate-limit (`http-kernel.ts:848`) |
864
867
  | `curl --http2` renvoie du HTTP/1.1 | `servers.https.protocol: "1.1"`, ou client sans ALPN | Passer `protocol: "2.0"` (défaut) et vérifier le client |
865
868
  | Avertissement navigateur en HTTPS de développement | Certificat auto-signé (mkcert absent) | `brew install mkcert nss && mkcert -install`, puis redémarrer |
866
- | Le certificat n'est pas régénéré après un changement de domaine | On croit qu'un fichier présent suffit | Déjà géré : le SAN est vérifié (`certificates.ts:555`) |
869
+ | Le certificat n'est pas régénéré après un changement de domaine | On croit qu'un fichier présent suffit | Déjà géré : le SAN est vérifié (`certificates.ts:543`) |
867
870
  | `strategy: "explicit"` fait échouer le boot | `key`/`cert` absents de la configuration | Fournir les deux chemins — l'échec est volontaire, jamais un repli silencieux |
868
871
  | Une IP falsifiée passe dans les journaux d'audit | `trustProxy` accordé trop largement | Restreindre à l'IP/CIDR du proxy, ou revenir à `false` |
869
872
  | Handshake WebSocket refusé en `1008` | `Origin` non autorisée (anti-CSWSH) | Ajouter l'origine dans `websocket.allowedOrigins` |
@@ -886,7 +889,7 @@ l'origine du transport.
886
889
 
887
890
  `proxy:generate` mérite un mot : la configuration nginx/HAProxy est **dérivée** des domaines de
888
891
  confiance, des ports effectifs et des dossiers statiques montés — donc elle ne diverge pas du code. Le
889
- résumé de certificat vient de `Certificate.describe()` (`certificates.ts:853`), source unique partagée
892
+ résumé de certificat vient de `Certificate.describe()` (`certificates.ts:758`), source unique partagée
890
893
  par la commande, le boot et un futur écran d'administration.
891
894
 
892
895
  **Runtime.** `nodefony status` et `nodefony stop` lisent les ports effectifs publiés au boot ; ils