@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.
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +13 -10
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +3 -3
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -2
- package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
- package/dist/nodefony/service/audit-logger.js +23 -30
- package/dist/nodefony/service/certificates.js +97 -13
- package/dist/nodefony/service/error-renderer.js +29 -12
- package/dist/nodefony/service/http-kernel.js +198 -50
- package/dist/nodefony/service/pretty-request-logger.js +15 -13
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +3 -3
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +3 -3
- package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
- package/dist/nodefony/service/servers/server-websocket.js +10 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
- package/dist/nodefony/service/sessions/sessions-service.js +7 -14
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +78 -43
- package/dist/nodefony/src/context/domainMatcher.js +1 -2
- package/dist/nodefony/src/context/forwarded.js +1 -2
- package/dist/nodefony/src/context/http/HttpContext.js +28 -6
- package/dist/nodefony/src/context/http/Request.js +33 -29
- package/dist/nodefony/src/context/http/Response.js +23 -6
- package/dist/nodefony/src/context/http/SseStream.js +1 -2
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +41 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/responseEnded.js +1 -2
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +33 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
- package/dist/nodefony/src/profiler/Profiler.js +104 -32
- package/dist/nodefony/src/profiler/aggregate.js +442 -0
- package/dist/nodefony/src/proxy/forward.js +29 -5
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +12 -6
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
- package/dist/nodefony/src/session/session.js +2 -3
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +9 -3
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/certificates.d.ts +55 -4
- package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/src/context/Context.d.ts +38 -5
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
- package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
- package/docs/cookies.md +85 -78
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +6 -1
- package/docs/observabilite.md +159 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +72 -70
- package/docs/servers.md +338 -224
- package/docs/session.md +179 -179
- package/docs/sse.md +24 -20
- package/docs/upload.md +120 -117
- package/package.json +8 -9
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
package/docs/reverse-proxy.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
113
|
-
(`proxyMountProblems()`, `rules.ts
|
|
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
|
|
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
|
|
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
|
|
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
|
|
229
|
+
Chaque montage (`proxyMountSchema`, `config.ts#proxyMountSchema`) :
|
|
230
230
|
|
|
231
|
-
| Option | Type
|
|
232
|
-
| -------------- |
|
|
233
|
-
| `target` | `string`
|
|
234
|
-
| `methods` | `string[]`
|
|
235
|
-
| `websocket` | `boolean`
|
|
236
|
-
| `stripPrefix` | `boolean`
|
|
237
|
-
| `preserveHost` | `boolean`
|
|
238
|
-
| `stripHeaders` | `string[]`
|
|
239
|
-
| `timeoutMs` | entier
|
|
240
|
-
| `secure` | `boolean`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
416
|
-
noyau (`ReverseProxy.closeAll()`, `reverse-proxy.ts
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
445
|
-
| RFC 9112 §6.1 | Codage de transfert non compris → 501 | Tout autre que `chunked` seul est refusé, jamais réétiqueté (`forward.ts
|
|
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
|
|
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
|
|
451
|
-
| RFC 5842 §7.2 | `508 Loop Detected` | Emprunté à WebDAV pour une boucle de relais (`viaLoops()`, `forward.ts
|
|
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
|
|
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`) |
|