@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.
- package/README.md +12 -3
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +6 -4
- package/dist/nodefony/command/assetsPublishCommand.js +1 -1
- package/dist/nodefony/command/certificatesCommand.js +1 -2
- package/dist/nodefony/command/networkCommand.js +1 -1
- package/dist/nodefony/command/proxyGenerateCommand.js +1 -1
- package/dist/nodefony/config/config.js +26 -4
- package/dist/nodefony/interfaces/IRouting.js +1 -0
- package/dist/nodefony/interfaces/ISecurity.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +2 -1
- package/dist/nodefony/service/audit-logger.js +16 -13
- package/dist/nodefony/service/certificates.js +87 -144
- package/dist/nodefony/service/error-renderer.js +11 -8
- package/dist/nodefony/service/http-kernel.js +342 -165
- package/dist/nodefony/service/pretty-request-logger.js +3 -10
- package/dist/nodefony/service/servers/clientError.js +2 -2
- package/dist/nodefony/service/servers/server-http.js +23 -23
- package/dist/nodefony/service/servers/server-https.js +41 -42
- package/dist/nodefony/service/servers/server-static.js +13 -17
- package/dist/nodefony/service/servers/server-websocket-secure.js +11 -10
- package/dist/nodefony/service/servers/server-websocket.js +11 -10
- package/dist/nodefony/service/servers/wsHeartbeat.js +2 -2
- package/dist/nodefony/service/sessions/sessions-service.js +22 -15
- package/dist/nodefony/service/trace.js +3 -2
- package/dist/nodefony/service/upload/upload-service.js +11 -10
- package/dist/nodefony/service/x509.js +324 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -1
- package/dist/nodefony/src/context/Context.js +34 -19
- package/dist/nodefony/src/context/forwarded.js +4 -4
- package/dist/nodefony/src/context/http/HttpContext.js +133 -88
- package/dist/nodefony/src/context/http/Request.js +108 -46
- package/dist/nodefony/src/context/http/Response.js +134 -54
- package/dist/nodefony/src/context/http/parser.js +9 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +35 -11
- package/dist/nodefony/src/context/http2/Response.js +53 -25
- package/dist/nodefony/src/context/metaData.js +1 -1
- package/dist/nodefony/src/context/trustProxy.js +4 -3
- package/dist/nodefony/src/context/websocket/Response.js +13 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +59 -26
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +41 -0
- package/dist/nodefony/src/cookies/cookie.js +3 -3
- package/dist/nodefony/src/errors/httpError.js +6 -6
- package/dist/nodefony/src/errors/thrownMessage.js +27 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +4 -3
- package/dist/nodefony/src/profiler/Profiler.js +4 -4
- package/dist/nodefony/src/servers/kernelServers.js +29 -0
- package/dist/nodefony/src/servers/portBinder.js +65 -23
- package/dist/nodefony/src/session/session.js +18 -10
- package/dist/nodefony/src/session/sessionStoreFailure.js +2 -1
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +24 -9
- package/dist/types/index.d.ts +16 -0
- package/dist/types/nodefony/config/config.d.ts +23 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +24 -7
- package/dist/types/nodefony/interfaces/ICookie.d.ts +18 -18
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -4
- package/dist/types/nodefony/interfaces/IResponse.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRouting.d.ts +87 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +45 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +8 -8
- package/dist/types/nodefony/interfaces/IUpload.d.ts +9 -9
- package/dist/types/nodefony/interfaces/index.d.ts +2 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +14 -10
- package/dist/types/nodefony/service/certificates.d.ts +42 -57
- package/dist/types/nodefony/service/http-kernel.d.ts +192 -42
- package/dist/types/nodefony/service/servers/server-http.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +4 -1
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +6 -2
- package/dist/types/nodefony/service/upload/upload-service.d.ts +1 -1
- package/dist/types/nodefony/service/x509.d.ts +86 -0
- package/dist/types/nodefony/src/context/Context.d.ts +21 -10
- package/dist/types/nodefony/src/context/forwarded.d.ts +3 -3
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +75 -18
- package/dist/types/nodefony/src/context/http/Request.d.ts +39 -2
- package/dist/types/nodefony/src/context/http/Response.d.ts +47 -7
- package/dist/types/nodefony/src/context/http/parser.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +32 -2
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +4 -4
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +11 -2
- package/dist/types/nodefony/src/context/websocket/wsCloseReason.d.ts +21 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +9 -9
- package/dist/types/nodefony/src/errors/httpError.d.ts +7 -7
- package/dist/types/nodefony/src/errors/thrownMessage.d.ts +12 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +28 -28
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +20 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +16 -65
- package/dist/types/nodefony/src/session/session.d.ts +12 -12
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +1 -1
- package/docs/cookies.md +25 -25
- package/docs/index.md +4 -2
- package/docs/observabilite.md +27 -27
- package/docs/rate-limit.md +20 -20
- package/docs/servers.md +82 -79
- package/docs/session.md +74 -69
- package/docs/upload.md +49 -43
- package/package.json +9 -12
package/docs/rate-limit.md
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
182
|
-
- `X-RateLimit-Reset` : **epoch en secondes** de la fin de fenêtre (`http-kernel.ts:
|
|
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:
|
|
207
|
-
depuis la config (`windowMs = windowS × 1000`, `http-kernel.ts:
|
|
208
|
-
(`http-kernel.ts:
|
|
209
|
-
- **Émission HTTP** : sous le quota, les en-têtes `X-RateLimit-*` sont posés (`http-kernel.ts:
|
|
210
|
-
la requête continue ; au-delà, `Retry-After` (`http-kernel.ts:
|
|
211
|
-
(`http-kernel.ts:
|
|
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:
|
|
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:
|
|
252
|
-
| Connexions simultanées | Sockets **ouvertes** en même temps par IP | `wsMaxConnectionsPerIp` | close `1013` — `tryAcquire` refuse (`http-kernel.ts:
|
|
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:
|
|
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:
|
|
284
|
-
| `Retry-After` (delta-seconds) | RFC 9110 §10.2.3 | en-tête posé sur le `429` (`http-kernel.ts:
|
|
285
|
-
| IP cliente derrière proxy | RFC 7239 | `resolveForwarded()` (`http-kernel.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
331
|
-
consulté sur son drapeau `active`, un serveur désactivé est **sauté**, pas créé (`http-kernel.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
428
|
-
| ---------------------------- | ----- |
|
|
429
|
-
| `maxHeadersCount` | int | `2000`
|
|
430
|
-
| `keepAliveTimeout` | ms | `5000`
|
|
431
|
-
| `timeout` | ms | `
|
|
432
|
-
| `requestTimeout` | ms | `30000`
|
|
433
|
-
| `responseTimeout` | ms | `30000`
|
|
434
|
-
| `shutdownTimeout` | ms | `5000`
|
|
435
|
-
| `headers` | objet | `null`
|
|
436
|
-
| `rejectUnauthorized` (https) | bool | `false`
|
|
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:
|
|
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:
|
|
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()` (`
|
|
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. **
|
|
508
|
-
|
|
509
|
-
`
|
|
510
|
-
`
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
545
|
-
`Certificate.resolveStrategy()` (`certificates.ts:
|
|
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
|
|
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:
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
662
|
-
réponses pré-allouées (`HttpKernel.#respondHealth()`, `http-kernel.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
736
|
-
envoie un message applicatif puis une **frame Close 1001 « Going Away »** (`server-websocket.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
-
|
|
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:
|
|
838
|
-
| HTTP/2 | RFC 9113 | `ServerHttps.createServerH2()` (`server-https.ts:
|
|
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:
|
|
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:
|
|
847
|
-
| En-têtes forwarded | RFC 7239 | `resolveForwarded()` (`forwarded.ts:253`)
|
|
848
|
-
| Certificat — série, SAN, extensions | RFC 5280 | `Certificate.generateSerialHex()` (`certificates.ts:
|
|
849
|
-
| Certificat — identité par le SAN | RFC 6125 | `sanSchema` (`config.ts:
|
|
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:
|
|
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:
|
|
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
|