@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.5

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 (138) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +13 -10
  4. package/dist/nodefony/command/assetsPublishCommand.js +5 -3
  5. package/dist/nodefony/command/certificatesCommand.js +5 -3
  6. package/dist/nodefony/command/networkCommand.js +5 -3
  7. package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
  8. package/dist/nodefony/config/config.js +3 -3
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  12. package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
  13. package/dist/nodefony/service/audit-logger.js +23 -30
  14. package/dist/nodefony/service/certificates.js +97 -13
  15. package/dist/nodefony/service/error-renderer.js +29 -12
  16. package/dist/nodefony/service/http-kernel.js +198 -50
  17. package/dist/nodefony/service/pretty-request-logger.js +15 -13
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +3 -3
  20. package/dist/nodefony/service/servers/clientError.js +22 -3
  21. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  22. package/dist/nodefony/service/servers/server-http.js +5 -8
  23. package/dist/nodefony/service/servers/server-https.js +12 -24
  24. package/dist/nodefony/service/servers/server-static.js +3 -3
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  29. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  30. package/dist/nodefony/service/trace.js +1 -2
  31. package/dist/nodefony/service/upload/upload-service.js +5 -8
  32. package/dist/nodefony/service/x509.js +1 -2
  33. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  34. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  35. package/dist/nodefony/src/context/Context.js +78 -43
  36. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  37. package/dist/nodefony/src/context/forwarded.js +1 -2
  38. package/dist/nodefony/src/context/http/HttpContext.js +28 -6
  39. package/dist/nodefony/src/context/http/Request.js +33 -29
  40. package/dist/nodefony/src/context/http/Response.js +23 -6
  41. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  42. package/dist/nodefony/src/context/http/parser.js +36 -10
  43. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  44. package/dist/nodefony/src/context/http2/Request.js +4 -8
  45. package/dist/nodefony/src/context/http2/Response.js +41 -9
  46. package/dist/nodefony/src/context/metaData.js +1 -2
  47. package/dist/nodefony/src/context/requestId.js +1 -2
  48. package/dist/nodefony/src/context/requestTrace.js +104 -0
  49. package/dist/nodefony/src/context/responseEnded.js +1 -2
  50. package/dist/nodefony/src/context/timingMode.js +21 -0
  51. package/dist/nodefony/src/context/transport.js +45 -0
  52. package/dist/nodefony/src/context/trustProxy.js +1 -2
  53. package/dist/nodefony/src/context/unsentResponse.js +30 -0
  54. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  55. package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
  56. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  57. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  58. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  59. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  60. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  61. package/dist/nodefony/src/cookies/cookie.js +3 -7
  62. package/dist/nodefony/src/errors/httpError.js +1 -2
  63. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  64. package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
  65. package/dist/nodefony/src/profiler/Profiler.js +104 -32
  66. package/dist/nodefony/src/profiler/aggregate.js +442 -0
  67. package/dist/nodefony/src/proxy/forward.js +29 -5
  68. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  69. package/dist/nodefony/src/proxy/rules.js +12 -6
  70. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  71. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  72. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  73. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  74. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  75. package/dist/nodefony/src/servers/portBinder.js +41 -10
  76. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  77. package/dist/nodefony/src/session/session.js +2 -3
  78. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  79. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  80. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  81. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  82. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  83. package/dist/types/index.d.ts +9 -3
  84. package/dist/types/nodefony/config/config.d.ts +4 -0
  85. package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
  86. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  87. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
  88. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
  89. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  91. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
  92. package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
  93. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  94. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  95. package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
  96. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  97. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  98. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  99. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  100. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  101. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  102. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  103. package/dist/types/nodefony/src/context/Context.d.ts +38 -5
  104. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
  105. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  106. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  107. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  108. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  109. package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
  110. package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
  111. package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
  112. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  113. package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
  114. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  115. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
  118. package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
  119. package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
  120. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  121. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  122. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  123. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  124. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  125. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  126. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  127. package/docs/cookies.md +85 -78
  128. package/docs/diagnostics-channel.md +303 -0
  129. package/docs/index.md +6 -1
  130. package/docs/observabilite.md +159 -81
  131. package/docs/rate-limit.md +83 -83
  132. package/docs/reverse-proxy.md +72 -70
  133. package/docs/servers.md +338 -224
  134. package/docs/session.md +179 -179
  135. package/docs/sse.md +24 -20
  136. package/docs/upload.md +120 -117
  137. package/package.json +8 -9
  138. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -56,11 +56,11 @@ flowchart LR
56
56
  Trois idées suffisent pour raisonner :
57
57
 
58
58
  1. **Un montage = un préfixe → une origine.** `"/billing/"` → `http://127.0.0.1:8080`. Le préfixe le
59
- plus long gagne (`ReverseProxy.match()`, `reverse-proxy.ts:229`).
59
+ plus long gagne (`ReverseProxy.match()`, `reverse-proxy.ts#ReverseProxy.match`).
60
60
  2. **L'aiguillage passe AVANT le routage.** Une route attrape-tout de l'application ne peut pas
61
- avaler un préfixe monté (`HttpKernel.routeHttpRequest()`, `http-kernel.ts:1699`).
61
+ avaler un préfixe monté (`HttpKernel.routeHttpRequest()`, `http-kernel.ts#HttpKernel.routeHttpRequest`).
62
62
  3. **Sans montage, il ne coûte rien.** Le pipeline lit un champ (`mounts === null`) et passe son
63
- chemin (`ReverseProxy.mounts`, `reverse-proxy.ts:174`).
63
+ chemin (`ReverseProxy.mounts`, `reverse-proxy.ts#ReverseProxy.mounts`).
64
64
 
65
65
  ## 📖 Lexique
66
66
 
@@ -100,17 +100,17 @@ autres. Mal écrit, un proxy ouvre ses propres failles, et c'est contre elles qu
100
100
 
101
101
  - **Contournement de préfixe** — `/billing/..%2fadmin` : le proxy voit `/billing/`, l'amont décode
102
102
  `/admin`. Un chemin ambigu est refusé en 400, jamais normalisé en silence (`isAmbiguousPath()`,
103
- `rules.ts:229`).
103
+ `rules.ts#isAmbiguousPath`).
104
104
  - **Désynchronisation de requête** (_request smuggling_) — proxy et amont ne s'accordent pas sur la
105
- fin du corps. Le cadrage est recalculé à chaque relais (`bodyFramingOf()`, `forward.ts:166`).
105
+ fin du corps. Le cadrage est recalculé à chaque relais (`bodyFramingOf()`, `forward.ts#bodyFramingOf`).
106
106
  - **Usurpation d'origine** — un client pose lui-même `X-Forwarded-For: 10.0.0.1`. Ces en-têtes ne
107
- sont crus que d'un pair `trustProxy` (`requestHeadersFor()`, `forward.ts:201`).
107
+ sont crus que d'un pair `trustProxy` (`requestHeadersFor()`, `forward.ts#requestHeadersFor`).
108
108
 
109
109
  ## La vision Nodefony
110
110
 
111
111
  Le proxy est un **service générique** de `@nodefony/http`, `reverse-proxy` (`ReverseProxy`,
112
- `reverse-proxy.ts:82`). Il accepte deux sources de montages, soumises aux **mêmes règles**
113
- (`proxyMountProblems()`, `rules.ts:98`) :
112
+ `reverse-proxy.ts#ReverseProxy`). Il accepte deux sources de montages, soumises aux **mêmes règles**
113
+ (`proxyMountProblems()`, `rules.ts#proxyMountProblems`) :
114
114
 
115
115
  - **la configuration** de l'application — `proxy.mounts`, validée au démarrage ;
116
116
  - **un module**, par `mount()` — c'est ainsi que `@nodefony/frontend` sert Vite sous `/_vite/`.
@@ -130,7 +130,7 @@ Le compromis assumé :
130
130
  > **Un préfixe monté est servi AVANT le pare-feu, la protection CSRF et les en-têtes de sécurité
131
131
  > APPLICATIFS de l'application (CSP, `Referrer-Policy`, COOP).** C'est une surface publique pour qui
132
132
  > atteint Nodefony : l'amont porte sa propre politique d'accès (`ReverseProxy.forward()`,
133
- > `reverse-proxy.ts:319`). Ne montez jamais un service qui suppose être protégé par le pare-feu
133
+ > `reverse-proxy.ts#ReverseProxy.forward`). Ne montez jamais un service qui suppose être protégé par le pare-feu
134
134
  > Nodefony.
135
135
 
136
136
  Ce qui s'applique malgré tout, parce que posé à l'entrée, avant l'aiguillage :
@@ -212,12 +212,12 @@ via: 1.1 nodefony-3f9a1c2e
212
212
  - Le préfixe a été retiré du chemin, et l'amont l'apprend par `X-Forwarded-Prefix`.
213
213
  - `Host` est celui de l'amont ; le nom demandé par le client part dans `X-Forwarded-Host`.
214
214
  - `Via` porte le pseudonyme de CE processus, tiré au hasard au démarrage — il ne dit rien de la
215
- machine (`ReverseProxy.pseudonym`, `reverse-proxy.ts:290`).
215
+ machine (`ReverseProxy.pseudonym`, `reverse-proxy.ts#ReverseProxy.pseudonym`).
216
216
 
217
217
  ## ⚙️ Configuration
218
218
 
219
219
  Section `proxy` de `use("@nodefony/http", { … })`, schéma Zod strict : une clé inconnue interrompt le
220
- démarrage en la nommant (`proxySchema`, `config.ts:1085`).
220
+ démarrage en la nommant (`proxySchema`, `config.ts#proxySchema`).
221
221
 
222
222
  | Option | Type | Défaut | Effet |
223
223
  | ------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -226,26 +226,27 @@ démarrage en la nommant (`proxySchema`, `config.ts:1085`).
226
226
  | `maxSockets` | entier | `256` | Connexions simultanées vers UN amont (pool dédié par montage) ; au-delà, les requêtes attendent. Borne aussi les tunnels WebSocket, hors pool : au-delà, 503. |
227
227
  | `mounts` | `record` | `{}` | Préfixe → montage. Préfixe le plus long d'abord. |
228
228
 
229
- Chaque montage (`proxyMountSchema`, `config.ts:1013`) :
229
+ Chaque montage (`proxyMountSchema`, `config.ts#proxyMountSchema`) :
230
230
 
231
- | Option | Type | Défaut | Effet |
232
- | -------------- | ---------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
233
- | `target` | `string` | — (requis) | Origine de l'amont, `http(s)://hôte[:port]`, **sans chemin**. |
234
- | `methods` | `string[]` | toutes | Méthodes relayées ; une autre méthode suit le routage de l'application. |
235
- | `websocket` | `boolean` | `false` | Relaie aussi l'upgrade WebSocket du préfixe. |
236
- | `stripPrefix` | `boolean` | `false` | Retire le préfixe du chemin transmis ; `Location` est alors ramené sous le préfixe. |
237
- | `preserveHost` | `boolean` | `false` | Transmet le `Host` du client au lieu de celui de la cible. |
238
- | `stripHeaders` | `string[]` | `[]` | En-têtes de requête à ne pas transmettre (`cookie`, `authorization`…). |
239
- | `timeoutMs` | entier | `proxy.timeoutMs` | Délai d'inactivité propre au montage, handshake WebSocket compris (jusqu'au `101`). Ne s'applique pas à un WebSocket établi. |
240
- | `secure` | `boolean` | `true` | Vérifie le certificat d'une cible `https`. `false` n'est accepté que vers la boucle locale. |
231
+ | Option | Type | Défaut | Effet |
232
+ | -------------- | --------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
233
+ | `target` | `string` | — (requis) | Origine de l'amont, `http(s)://hôte[:port]`, **sans chemin**. |
234
+ | `methods` | `string[]` | toutes | Méthodes relayées ; une autre méthode suit le routage de l'application. |
235
+ | `websocket` | `boolean` | `false` | Relaie aussi l'upgrade WebSocket du préfixe. |
236
+ | `stripPrefix` | `boolean` | `false` | Retire le préfixe du chemin transmis ; `Location` est alors ramené sous le préfixe. |
237
+ | `preserveHost` | `boolean` | `false` | Transmet le `Host` du client au lieu de celui de la cible. |
238
+ | `stripHeaders` | `string[]` | `[]` | En-têtes de requête à ne pas transmettre (`cookie`, `authorization`…). |
239
+ | `timeoutMs` | entier | `proxy.timeoutMs` | Délai d'inactivité propre au montage, handshake WebSocket compris (jusqu'au `101`). Ne s'applique pas à un WebSocket établi. |
240
+ | `secure` | `boolean` | `true` | Vérifie le certificat d'une cible `https`. `false` n'est accepté que vers la boucle locale. |
241
+ | `accessLog` | `"all"` \| `"errors"` | `"all"` | Ligne d'accès des requêtes relayées : toutes, ou seulement celles en échec (statut ≥ 400), avec le statut rendu par l'amont. |
241
242
 
242
- **Refusés au démarrage**, avec le motif (`proxyMountProblems()`, `rules.ts:98`) :
243
+ **Refusés au démarrage**, avec le motif (`proxyMountProblems()`, `rules.ts#proxyMountProblems`) :
243
244
 
244
245
  - le préfixe `/` — il relaierait toute l'application, routes et pare-feu compris ;
245
246
  - un préfixe sous `/nodefony/` (console d'administration) ou, depuis la configuration, sous
246
- `/_vite/` (monté par `@nodefony/frontend`) — `RESERVED_PROXY_PREFIXES` (`rules.ts:18`) ;
247
+ `/_vite/` (monté par `@nodefony/frontend`) — `RESERVED_PROXY_PREFIXES` (`rules.ts#RESERVED_PROXY_PREFIXES`) ;
247
248
  - un préfixe qui contient `?`, `#`, `%`, `\`, un espace ou un segment `.`/`..` ;
248
- - une cible qui n'est pas une origine `http(s)` nue (`parseProxyOrigin()`, `rules.ts:56`) ;
249
+ - une cible qui n'est pas une origine `http(s)` nue (`parseProxyOrigin()`, `rules.ts#parseProxyOrigin`) ;
249
250
  - deux clés qui désignent le même préfixe (`/svc` et `/svc/`).
250
251
 
251
252
  ### Choisir les réglages — en situation
@@ -259,7 +260,7 @@ retirer.
259
260
 
260
261
  **L'amont croit être à la racine** (un service existant qu'on ne modifie pas) : retirer le préfixe.
261
262
  Ses redirections `Location: /login` sont ramenées en `/billing/login`
262
- (`rewriteLocation()`, `forward.ts:288`).
263
+ (`rewriteLocation()`, `forward.ts#rewriteLocation`).
263
264
 
264
265
  ```ts ignore
265
266
  "/billing/": { target: "http://127.0.0.1:8080", stripPrefix: true }
@@ -295,17 +296,18 @@ modules, ses images et le WebSocket du rechargement à chaud partagent alors l'o
295
296
  l'application — une IP de réseau local, un téléphone ou un conteneur fonctionnent en HTTPS sans
296
297
  réglage.
297
298
 
298
- | Ce qui est fixé par `@nodefony/frontend` | Pourquoi |
299
- | ----------------------------------------------- | ---------------------------------------------------------- |
300
- | `methods: ["GET", "HEAD"]` | un serveur de sources ne sert que des lectures |
301
- | `websocket: true` | le rechargement à chaud passe par un WebSocket |
302
- | `stripHeaders: ["cookie", "authorization"]` | Vite n'a pas à voir la session de l'application |
303
- | cible = le Vite de CETTE application, s'il sert | un port repris par une autre application n'est jamais visé |
299
+ | Ce qui est fixé par `@nodefony/frontend` | Pourquoi |
300
+ | ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
301
+ | `methods: ["GET", "HEAD"]` | un serveur de sources ne sert que des lectures |
302
+ | `websocket: true` | le rechargement à chaud passe par un WebSocket |
303
+ | `stripHeaders: ["cookie", "authorization"]` | Vite n'a pas à voir la session de l'application |
304
+ | `accessLog: "errors"` | une page charge des centaines de modules ; seuls les échecs méritent une ligne au journal |
305
+ | cible = le Vite de CETTE application, s'il sert | un port repris par une autre application n'est jamais visé |
304
306
 
305
307
  Ce qui se règle reste côté frontend, dans `use("@nodefony/frontend", { … })` : `devHost` (défaut
306
308
  `127.0.0.1`, à garder — le navigateur ne joint jamais Vite directement) et `devPort` (défaut `5173`,
307
309
  chaque famille prend le port suivant). Le préfixe `/_vite/` est refusé dans `proxy.mounts` : il
308
- appartient au module (`MODULE_PROXY_PREFIXES`, `rules.ts:25`). En production, Vite ne tourne pas et
310
+ appartient au module (`MODULE_PROXY_PREFIXES`, `rules.ts#MODULE_PROXY_PREFIXES`). En production, Vite ne tourne pas et
309
311
  aucun montage n'est posé.
310
312
 
311
313
  ## 🔌 WebSocket relayé
@@ -313,10 +315,10 @@ aucun montage n'est posé.
313
315
  Avec `websocket: true`, l'upgrade du préfixe est relayé au lieu d'aller au serveur WebSocket de
314
316
  Nodefony. Un seul écouteur `upgrade` par serveur fait l'aiguillage : le proxy d'abord, puis `ws`,
315
317
  créé en `noServer` pour garder ses propres contrôles (`path`, `verifyClient`, `maxPayload`)
316
- (`attachUpgradeDispatch()`, `upgradeDispatch.ts:41`).
318
+ (`attachUpgradeDispatch()`, `upgradeDispatch.ts#attachUpgradeDispatch`).
317
319
 
318
320
  Avant de relayer, chaque contrôle rend la réponse qu'aurait rendue le serveur WebSocket de Nodefony
319
- (`ReverseProxy.handleUpgrade()`, `reverse-proxy.ts:384`) :
321
+ (`ReverseProxy.handleUpgrade()`, `reverse-proxy.ts#ReverseProxy.handleUpgrade`) :
320
322
 
321
323
  | Le handshake… | Réponse |
322
324
  | ------------------------------------------------------------------------- | ------- |
@@ -328,7 +330,7 @@ Avant de relayer, chaque contrôle rend la réponse qu'aurait rendue le serveur
328
330
  | passe tous les contrôles | relayé |
329
331
 
330
332
  Les quotas sont ceux d'un upgrade servi par Nodefony, appliqués par la **même** règle du noyau
331
- (`HttpKernel.websocketQuotaRefusal()`, `http-kernel.ts:2230`) ; un tunnel ouvert garde sa place
333
+ (`HttpKernel.websocketQuotaRefusal()`, `http-kernel.ts#HttpKernel.websocketQuotaRefusal`) ; un tunnel ouvert garde sa place
332
334
  jusqu'à sa fermeture. Seule la forme du refus diffère : le proxy répond avant le `101`, il peut donc
333
335
  rendre un statut HTTP, là où le serveur WebSocket ne peut plus que fermer avec le code 1013.
334
336
 
@@ -337,9 +339,9 @@ Une fois l'upgrade transmis, un amont qui accepte la connexion puis se tait est
337
339
  qu'il veut.
338
340
 
339
341
  Seul `Upgrade: websocket` est relayé : un tunnel `h2c` ou tout autre protocole raccordé octet pour
340
- octet échapperait à toute inspection (`websocketHandshakeProblem()`, `rules.ts:252`). Une fois la
342
+ octet échapperait à toute inspection (`websocketHandshakeProblem()`, `rules.ts#websocketHandshakeProblem`). Une fois la
341
343
  connexion établie, les deux sockets sont raccordées et détruites ensemble ; le TCP keep-alive détecte
342
- un pair mort (`forwardUpgrade()`, `forward.ts:590`).
344
+ un pair mort (`forwardUpgrade()`, `forward.ts#forwardUpgrade`).
343
345
 
344
346
  ## 🧩 Étendre — monter depuis un module
345
347
 
@@ -359,13 +361,13 @@ proxy?.unmount("/outil/");
359
361
  ```
360
362
 
361
363
  - `mount()` est **idempotent** pour un préfixe : il remplace le montage précédent. S'il remplace un
362
- montage de la configuration, il le dit en `WARNING` (`ReverseProxy.mount()`, `reverse-proxy.ts:129`).
364
+ montage de la configuration, il le dit en `WARNING` (`ReverseProxy.mount()`, `reverse-proxy.ts#ReverseProxy.mount`).
363
365
  - `unmount()` ferme le pool de l'amont ; le dernier retiré remet `mounts` à `null`, et le pipeline
364
- cesse de le consulter (`ReverseProxy.unmount()`, `reverse-proxy.ts:199`).
366
+ cesse de le consulter (`ReverseProxy.unmount()`, `reverse-proxy.ts#ReverseProxy.unmount`).
365
367
 
366
368
  **Exemple réel** : `@nodefony/frontend` monte ainsi chaque famille Vite (voir « Le cas Vite »), avec
367
369
  une cible fonction qui ne vise un port que si le Vite de CETTE application y répond
368
- (`FrontendService.mountDevProxy()`, `FrontendService.ts:486`).
370
+ (`FrontendService.mountDevProxy()`, `FrontendService.ts#FrontendService.mountDevProxy`).
369
371
 
370
372
  ## 🏗️ Architecture interne — le parcours d'une requête relayée
371
373
 
@@ -386,25 +388,25 @@ sequenceDiagram
386
388
 
387
389
  Ce que le relais fait aux en-têtes, dans les deux sens :
388
390
 
389
- | Vers l'amont (`requestHeadersFor()`, `forward.ts:201`) | Vers le client (`responseHeadersFor()`, `forward.ts:311`) |
390
- | --------------------------------------------------------------------- | ---------------------------------------------------------------- |
391
- | retire les en-têtes de connexion, et ceux que nomme `Connection` | retire les en-têtes de connexion, et ceux que nomme `Connection` |
392
- | retire `Expect` (Nodefony a déjà répondu `100`) et les `stripHeaders` | garde les `Set-Cookie` multiples |
393
- | réécrit `Host` sur la cible (sauf `preserveHost`) | ramène `Location` sous le préfixe (`stripPrefix`) |
394
- | `X-Forwarded-*` : prolongés d'un pair `trustProxy`, recommencés sinon | prolonge `Via` |
395
- | `X-Forwarded-Prefix` : à la suite de celui d'un pair `trustProxy` | |
396
- | `Forwarded` d'un pair `trustProxy` : prolongé de ce saut, jamais créé | |
397
- | prolonge `Via` ; recalcule le cadrage du corps | |
391
+ | Vers l'amont (`requestHeadersFor()`, `forward.ts#requestHeadersFor`) | Vers le client (`responseHeadersFor()`, `forward.ts#responseHeadersFor`) |
392
+ | --------------------------------------------------------------------- | ------------------------------------------------------------------------ |
393
+ | retire les en-têtes de connexion, et ceux que nomme `Connection` | retire les en-têtes de connexion, et ceux que nomme `Connection` |
394
+ | retire `Expect` (Nodefony a déjà répondu `100`) et les `stripHeaders` | garde les `Set-Cookie` multiples |
395
+ | réécrit `Host` sur la cible (sauf `preserveHost`) | ramène `Location` sous le préfixe (`stripPrefix`) |
396
+ | `X-Forwarded-*` : prolongés d'un pair `trustProxy`, recommencés sinon | prolonge `Via` |
397
+ | `X-Forwarded-Prefix` : à la suite de celui d'un pair `trustProxy` | |
398
+ | `Forwarded` d'un pair `trustProxy` : prolongé de ce saut, jamais créé | |
399
+ | prolonge `Via` ; recalcule le cadrage du corps | |
398
400
 
399
- Les échecs, dans l'ordre où ils peuvent survenir (`forwardRequest()`, `forward.ts:408`) :
401
+ Les échecs, dans l'ordre où ils peuvent survenir (`forwardRequest()`, `forward.ts#forwardRequest`) :
400
402
 
401
403
  - **508** — la requête porte déjà le pseudonyme de ce processus dans `Via`, ou au moins
402
404
  `MAX_VIA_HOPS` (10) sauts : une boucle, l'amont n'est pas contacté (`viaLoops()`,
403
- `forward.ts:129`). Le plafond compte parce que le pseudonyme ne repère une boucle qu'au retour
405
+ `forward.ts#viaLoops`). Le plafond compte parce que le pseudonyme ne repère une boucle qu'au retour
404
406
  dans ce processus — derrière un répartiteur qui tourne sur N instances, après N+1 sauts ;
405
407
  - **501** — un `Transfer-Encoding` autre que `chunked` seul (`gzip, chunked`) : le transmettre
406
408
  réétiqueté livrerait à l'amont un corps compressé qu'il croirait en clair (`bodyFramingOf()`,
407
- `forward.ts:166`) ;
409
+ `forward.ts#bodyFramingOf`) ;
408
410
  - **504** — connexion non établie sous `connectTimeoutMs`, ou amont muet au-delà de `timeoutMs` ;
409
411
  - **502** — amont injoignable, ou réponse que la sortie refuse (en-tête interdit en HTTP/2) ;
410
412
  - **coupure** — si l'amont tombe APRÈS avoir commencé à répondre, la réponse au client est
@@ -412,8 +414,8 @@ Les échecs, dans l'ordre où ils peuvent survenir (`forwardRequest()`, `forward
412
414
 
413
415
  Chaque montage a son **pool keep-alive dédié**, jamais l'agent global de Node : sous
414
416
  `NODE_USE_ENV_PROXY` (poste d'entreprise), l'agent global enverrait un amont local au proxy de
415
- l'entreprise (`ReverseProxy.agentFor()`, `reverse-proxy.ts:270`). Les pools sont fermés à l'arrêt du
416
- noyau (`ReverseProxy.closeAll()`, `reverse-proxy.ts:215`) ; un relais encore en vol part ensuite sur
417
+ l'entreprise (`ReverseProxy.agentFor()`, `reverse-proxy.ts#ReverseProxy.agentFor`). Les pools sont fermés à l'arrêt du
418
+ noyau (`ReverseProxy.closeAll()`, `reverse-proxy.ts#ReverseProxy.closeAll`) ; un relais encore en vol part ensuite sur
417
419
  une connexion à usage unique, sans recréer de pool que personne ne fermerait.
418
420
 
419
421
  ## 🔐 Sécurité
@@ -424,9 +426,9 @@ une connexion à usage unique, sans recréer de pool que personne ne fermerait.
424
426
  - **Quotas maintenus** : rate-limit par IP sur les requêtes relayées ; mêmes bornes par IP pour un
425
427
  upgrade relayé que pour un upgrade servi, plus `maxSockets` tunnels par amont.
426
428
  - **Barrière d'hôte maintenue** : un `Host` hors `trustedHosts` n'est jamais relayé ; il suit le
427
- pipeline jusqu'au 421, comme toute requête (`HttpKernel.routeHttpRequest()`, `http-kernel.ts:1699`).
429
+ pipeline jusqu'au 421, comme toute requête (`HttpKernel.routeHttpRequest()`, `http-kernel.ts#HttpKernel.routeHttpRequest`).
428
430
  - **Chemins ambigus refusés**, pas normalisés : `.`, `..`, `\`, octet nul et leurs formes encodées
429
- `%2e`, `%2f`, `%5c`, `%00`, `%25` (`isAmbiguousPath()`, `rules.ts:229`).
431
+ `%2e`, `%2f`, `%5c`, `%00`, `%25` (`isAmbiguousPath()`, `rules.ts#isAmbiguousPath`).
430
432
  - **Chaîne de confiance** : `Forwarded`, `X-Forwarded-*` et `X-Real-IP` d'un client direct sont
431
433
  écartés ; seul un pair `trustProxy` les prolonge.
432
434
  - **Certificat vérifié** vers une cible `https` ; `secure: false` n'est accepté que vers
@@ -437,18 +439,18 @@ une connexion à usage unique, sans recréer de pool que personne ne fermerait.
437
439
 
438
440
  ## 📜 Normes appliquées
439
441
 
440
- | Norme | Exigence | Comment le code s'y conforme |
441
- | ----------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
442
- | RFC 9110 §7.6.1 | Un intermédiaire retire les en-têtes de connexion | Liste fixe + jetons de `Connection`, dans les deux sens (`requestHeadersFor()`, `forward.ts:201`) |
443
- | RFC 9110 §7.6.3 | Un intermédiaire ajoute son entrée à `Via` | Pseudonyme par processus, prolongé à l'aller et au retour (`forward.ts:244`) |
444
- | RFC 9112 §6.3 | Un seul cadrage de corps ; `Transfer-Encoding` l'emporte | `Content-Length` retiré quand le corps est découpé (`bodyFramingOf()`, `forward.ts:166`) |
445
- | RFC 9112 §6.1 | Codage de transfert non compris → 501 | Tout autre que `chunked` seul est refusé, jamais réétiqueté (`forward.ts:147`) |
446
- | RFC 9113 §8.2.2 | Pas d'en-tête de connexion en HTTP/2 | Réponse refusée par la sortie h2 → 502, jamais une exception non rattrapée |
447
- | RFC 7239 §8.1 | Ne croire `Forwarded` que d'un relais de confiance | `trustProxy` décide (`ReverseProxy.forward()`, `reverse-proxy.ts:319`) |
448
- | RFC 7239 §4 · §6 | Chaque relais ajoute son élément ; IPv6 entre crochets | Élément `for=…;proto=…;host=…` ajouté, valeurs non-jeton entre guillemets |
449
- | RFC 6585 §4 | `429 Too Many Requests` | Quota par IP dépassé sur un upgrade relayé, avant le `101` |
450
- | RFC 6455 §4.2.1 · §10.2 | Handshake conforme ; contrôle d'Origin contre le CSWSH | `websocketHandshakeProblem()` (`rules.ts:252`) + règle d'Origin du serveur WS |
451
- | RFC 5842 §7.2 | `508 Loop Detected` | Emprunté à WebDAV pour une boucle de relais (`viaLoops()`, `forward.ts:129`) |
442
+ | Norme | Exigence | Comment le code s'y conforme |
443
+ | ----------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
444
+ | RFC 9110 §7.6.1 | Un intermédiaire retire les en-têtes de connexion | Liste fixe + jetons de `Connection`, dans les deux sens (`requestHeadersFor()`, `forward.ts#requestHeadersFor`) |
445
+ | RFC 9110 §7.6.3 | Un intermédiaire ajoute son entrée à `Via` | Pseudonyme par processus, prolongé à l'aller et au retour (`forward.ts#requestHeadersFor`) |
446
+ | RFC 9112 §6.3 | Un seul cadrage de corps ; `Transfer-Encoding` l'emporte | `Content-Length` retiré quand le corps est découpé (`bodyFramingOf()`, `forward.ts#bodyFramingOf`) |
447
+ | RFC 9112 §6.1 | Codage de transfert non compris → 501 | Tout autre que `chunked` seul est refusé, jamais réétiqueté (`forward.ts#viaEntry`) |
448
+ | RFC 9113 §8.2.2 | Pas d'en-tête de connexion en HTTP/2 | Réponse refusée par la sortie h2 → 502, jamais une exception non rattrapée |
449
+ | RFC 7239 §8.1 | Ne croire `Forwarded` que d'un relais de confiance | `trustProxy` décide (`ReverseProxy.forward()`, `reverse-proxy.ts#ReverseProxy.forward`) |
450
+ | RFC 7239 §4 · §6 | Chaque relais ajoute son élément ; IPv6 entre crochets | Élément `for=…;proto=…;host=…` ajouté, valeurs non-jeton entre guillemets |
451
+ | RFC 6585 §4 | `429 Too Many Requests` | Quota par IP dépassé sur un upgrade relayé, avant le `101` |
452
+ | RFC 6455 §4.2.1 · §10.2 | Handshake conforme ; contrôle d'Origin contre le CSWSH | `websocketHandshakeProblem()` (`rules.ts#websocketHandshakeProblem`) + règle d'Origin du serveur WS |
453
+ | RFC 5842 §7.2 | `508 Loop Detected` | Emprunté à WebDAV pour une boucle de relais (`viaLoops()`, `forward.ts#viaLoops`) |
452
454
 
453
455
  ## ⚡ Performance & mémoire
454
456
 
@@ -472,7 +474,7 @@ en-tête `Via`.
472
474
 
473
475
  | Symptôme | Cause | Correction |
474
476
  | ---------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
475
- | Le boot s'arrête sur `proxy.mounts` | Préfixe `/`, réservé, ou cible avec un chemin | Lire le motif : il nomme la clé et le geste (`rules.ts:98`) |
477
+ | Le boot s'arrête sur `proxy.mounts` | Préfixe `/`, réservé, ou cible avec un chemin | Lire le motif : il nomme la clé et le geste (`rules.ts#proxyMountProblems`) |
476
478
  | Une route de l'application sous le préfixe n'est jamais atteinte | Le proxy passe AVANT le routage | Attendu : choisir un préfixe qui n'appartient qu'à l'amont |
477
479
  | L'amont redirige hors du montage | `stripPrefix` + liens absolus dans le HTML de l'amont | Servir l'amont sous son préfixe, ou lui faire lire `X-Forwarded-Prefix` |
478
480
  | L'amont voit `127.0.0.1` comme client | Lecture de l'IP de la socket au lieu de `X-Forwarded-For` | Faire confiance à Nodefony côté amont (son équivalent de `trustProxy`) |