@nodefony/http 10.0.0-beta.2 → 10.0.0-beta.4

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 (139) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +19 -11
  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 +45 -6
  9. package/dist/nodefony/config/defineModuleConfig.js +2 -3
  10. package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -3
  12. package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
  13. package/dist/nodefony/service/audit-logger.js +22 -16
  14. package/dist/nodefony/service/certificates.js +228 -19
  15. package/dist/nodefony/service/error-renderer.js +30 -13
  16. package/dist/nodefony/service/http-kernel.js +331 -82
  17. package/dist/nodefony/service/pretty-request-logger.js +5 -4
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +336 -0
  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 +7 -10
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +18 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
  29. package/dist/nodefony/service/sessions/sessions-service.js +34 -20
  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 +51 -32
  36. package/dist/nodefony/src/context/domainMatcher.js +21 -4
  37. package/dist/nodefony/src/context/forwarded.js +3 -4
  38. package/dist/nodefony/src/context/http/HttpContext.js +98 -46
  39. package/dist/nodefony/src/context/http/Request.js +34 -38
  40. package/dist/nodefony/src/context/http/Response.js +39 -7
  41. package/dist/nodefony/src/context/http/SseStream.js +349 -0
  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 +22 -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/responseEnded.js +26 -0
  49. package/dist/nodefony/src/context/transport.js +45 -0
  50. package/dist/nodefony/src/context/trustProxy.js +1 -2
  51. package/dist/nodefony/src/context/websocket/Response.js +42 -6
  52. package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
  53. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  54. package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
  55. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  56. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  57. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  58. package/dist/nodefony/src/cookies/cookie.js +3 -7
  59. package/dist/nodefony/src/errors/httpError.js +1 -2
  60. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  61. package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
  62. package/dist/nodefony/src/profiler/Profiler.js +108 -30
  63. package/dist/nodefony/src/profiler/aggregate.js +422 -0
  64. package/dist/nodefony/src/proxy/forward.js +500 -0
  65. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  66. package/dist/nodefony/src/proxy/rules.js +188 -0
  67. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  68. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  69. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  70. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  71. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  72. package/dist/nodefony/src/servers/portBinder.js +41 -10
  73. package/dist/nodefony/src/servers/upgradeDispatch.js +128 -0
  74. package/dist/nodefony/src/session/session.js +30 -15
  75. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  76. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  77. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  78. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  79. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  80. package/dist/types/index.d.ts +12 -3
  81. package/dist/types/nodefony/config/config.d.ts +16 -0
  82. package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
  83. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
  84. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  85. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
  86. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  87. package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
  88. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  89. package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
  90. package/dist/types/nodefony/service/certificates.d.ts +147 -5
  91. package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
  92. package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
  93. package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
  94. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  95. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  96. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  97. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  98. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +2 -3
  99. package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
  100. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  101. package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
  102. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
  103. package/dist/types/nodefony/src/context/Context.d.ts +35 -6
  104. package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
  105. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
  106. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  107. package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
  108. package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
  109. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  110. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  111. package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
  112. package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
  113. package/dist/types/nodefony/src/context/transport.d.ts +53 -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 +57 -7
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
  118. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
  119. package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
  120. package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
  121. package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
  122. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  123. package/dist/types/nodefony/src/proxy/rules.d.ts +88 -0
  124. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  125. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  126. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  127. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +69 -0
  128. package/dist/types/nodefony/src/session/session.d.ts +2 -0
  129. package/docs/cookies.md +85 -78
  130. package/docs/index.md +5 -1
  131. package/docs/observabilite.md +81 -81
  132. package/docs/rate-limit.md +83 -83
  133. package/docs/reverse-proxy.md +522 -0
  134. package/docs/servers.md +378 -230
  135. package/docs/session.md +180 -180
  136. package/docs/sse.md +287 -0
  137. package/docs/upload.md +120 -117
  138. package/package.json +10 -11
  139. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
@@ -0,0 +1,168 @@
1
+ import http from "node:http";
2
+ import type http2 from "node:http2";
3
+ import type { Duplex } from "node:stream";
4
+ import type { IProxyMount } from "../../interfaces/IReverseProxy.js";
5
+ /** Requête entrante relayable : HTTP/1.1 ou HTTP/2 (API de compatibilité). */
6
+ export type ProxiedRequest = http.IncomingMessage | http2.Http2ServerRequest;
7
+ /** Réponse vers le client : HTTP/1.1 ou HTTP/2 (API de compatibilité). */
8
+ export type ProxiedResponse = http.ServerResponse | http2.Http2ServerResponse;
9
+ /**
10
+ * Ce que le proxy sait du client, et qu'il annonce à l'amont
11
+ * (`X-Forwarded-*`, `Via`).
12
+ */
13
+ export interface IForwardedFrom {
14
+ /** Adresse du pair TCP immédiat — ajoutée à la chaîne `X-Forwarded-For`. */
15
+ remoteAddress: string | undefined;
16
+ /** Scheme par lequel le pair est arrivé (`http` | `https`). */
17
+ scheme: string;
18
+ /** Autorité demandée (`Host` ou `:authority`). */
19
+ host: string | undefined;
20
+ /**
21
+ * Le pair est-il un relais de confiance (`trustProxy`) ? Seul un tel pair
22
+ * peut faire passer des `Forwarded`/`X-Forwarded-*`/`X-Real-IP` : venus
23
+ * d'ailleurs, ils sont forgeables (RFC 7239 §8.1) et sont écartés.
24
+ */
25
+ trustedPeer: boolean;
26
+ /** Version du message reçu, pour `Via` (`"1.1"`, `"2.0"`). */
27
+ httpVersion: string;
28
+ }
29
+ /** Réglages du relais, fixés par le service (identiques pour tous les montages). */
30
+ export interface IRelaySettings {
31
+ /** Pseudonyme de CE processus dans `Via` (RFC 9110 §7.6.3) — base de la garde de boucle. */
32
+ pseudonym: string;
33
+ /** Délai d'établissement de la connexion amont (ms). Dépassé : 504. */
34
+ connectTimeoutMs: number;
35
+ /**
36
+ * Pool de connexions du montage (keep-alive borné) ; `false` = connexion à
37
+ * usage unique (arrêt engagé : plus de pool à créer).
38
+ */
39
+ agent: http.Agent | false | undefined;
40
+ }
41
+ /**
42
+ * Sauts `Via` au-delà desquels un message est tenu pour une boucle. Notre
43
+ * pseudonyme ne repère une boucle qu'au RETOUR dans ce processus : derrière un
44
+ * répartiteur qui tourne sur N instances, cela peut prendre N+1 sauts, chacun
45
+ * tenant une connexion. Une chaîne légitime (CDN, répartiteur, ingress) en
46
+ * compte rarement plus de cinq.
47
+ */
48
+ export declare const MAX_VIA_HOPS = 10;
49
+ /**
50
+ * Le message est-il déjà passé par CE processus ? Un `Via` qui porte notre
51
+ * pseudonyme signe une boucle (RFC 9110 §7.6 : un proxy ne se renvoie pas un
52
+ * message sans s'en protéger).
53
+ *
54
+ * @param via - en-tête `Via` reçu
55
+ * @param pseudonym - pseudonyme de ce processus
56
+ */
57
+ export declare function viaLoops(via: string | string[] | undefined, pseudonym: string): boolean;
58
+ /**
59
+ * Cadrage du corps transmis à l'amont (RFC 9112 §6). `unsupported` : un
60
+ * codage de transfert autre que `chunked` seul — le proxy ne sait pas le
61
+ * transmettre fidèlement.
62
+ */
63
+ export type BodyFraming = "none" | "length" | "chunked" | "unsupported";
64
+ /**
65
+ * Cadrage du corps reçu — décidé UNE fois, pour que l'amont lise exactement
66
+ * les octets que le proxy lui envoie (RFC 9112 §6.3). Un `Transfer-Encoding`
67
+ * l'emporte sur un `Content-Length` (que l'on retire alors) ; en HTTP/2, un
68
+ * flux sans fin à la tête porte un corps même sans longueur annoncée.
69
+ *
70
+ * @param req - requête reçue
71
+ * @returns le cadrage à appliquer vers l'amont
72
+ */
73
+ export declare function bodyFramingOf(req: ProxiedRequest): BodyFraming;
74
+ /**
75
+ * En-têtes de la requête à transmettre à l'amont.
76
+ *
77
+ * - écartés : pseudo-en-têtes HTTP/2, en-têtes de connexion et ceux que nomme
78
+ * `Connection`, `Expect` (le serveur a déjà répondu `100`), en-têtes retirés
79
+ * par le montage, chaîne de relais venue d'un pair NON fiable ;
80
+ * - `Host` réécrit sur la cible (sauf `preserveHost`) ;
81
+ * - `X-Forwarded-For` prolongé (pair fiable) ou recommencé ;
82
+ * `X-Forwarded-Proto`/`-Host` hérités d'un pair fiable, posés sinon ;
83
+ * `X-Forwarded-Prefix` quand le préfixe est retiré — à la suite de celui
84
+ * d'un pair fiable ; `Forwarded` d'un pair fiable prolongé de ce saut ;
85
+ * - `Via` prolongé de ce saut ;
86
+ * - cadrage du corps rendu cohérent ({@link bodyFramingOf}).
87
+ *
88
+ * @returns un objet neuf, prêt pour `http.request`
89
+ */
90
+ export declare function requestHeadersFor(headers: http.IncomingHttpHeaders, mount: IProxyMount, target: URL, from: IForwardedFrom, pseudonym: string, framing: BodyFraming): http.OutgoingHttpHeaders;
91
+ /**
92
+ * Ramène sous le préfixe un `Location` émis par un amont qui ignore son
93
+ * montage (`stripPrefix`) — l'équivalent de `proxy_redirect default` de
94
+ * nginx. Un chemin absolu (`/login`) et une URL sur l'origine de l'amont sont
95
+ * réécrits ; toute autre URL (un tiers) part intacte.
96
+ *
97
+ * @returns la valeur réécrite, ou celle reçue
98
+ */
99
+ export declare function rewriteLocation(location: string, mount: IProxyMount, target: URL): string;
100
+ /**
101
+ * En-têtes de la réponse de l'amont à renvoyer au client : en-têtes de
102
+ * connexion écartés — y compris ceux que nomme `Connection` —, `Via`
103
+ * prolongé, `Location` ramené sous le préfixe (`stripPrefix`) ; le reste
104
+ * intact (un `set-cookie` multiple reste un tableau).
105
+ *
106
+ * @returns un objet neuf, valable en HTTP/1.1 comme en HTTP/2
107
+ */
108
+ export declare function responseHeadersFor(headers: http.IncomingHttpHeaders, mount: IProxyMount, target: URL, hop: string): http.OutgoingHttpHeaders;
109
+ /**
110
+ * Chemin transmis à l'amont : tel quel, ou sans le préfixe (`stripPrefix`).
111
+ *
112
+ * @param url - cible de la requête (chemin + requête), qui commence par le préfixe
113
+ * @param mount - montage qui relaie
114
+ * @returns le chemin, toujours avec un `/` en tête
115
+ */
116
+ export declare function upstreamPath(url: string, mount: IProxyMount): string;
117
+ /**
118
+ * Relaie une requête HTTP vers l'amont et renvoie sa réponse au client.
119
+ *
120
+ * Le corps est transmis avec un cadrage cohérent ({@link bodyFramingOf}). Un
121
+ * message qui porte déjà notre `Via` rend 508 sans contacter l'amont. Un
122
+ * client parti coupe l'échange amont ; un amont injoignable rend 502 ; une
123
+ * connexion non établie à temps ou un amont muet rendent 504 — à condition
124
+ * que rien n'ait encore été envoyé, sinon la réponse est interrompue (un
125
+ * statut ne se rattrape pas). Un amont coupé en plein corps interrompt la
126
+ * réponse au client au lieu de la laisser pendre.
127
+ *
128
+ * @returns une promesse résolue à la fermeture de la réponse — jamais rejetée
129
+ */
130
+ export declare function forwardRequest(req: ProxiedRequest, res: ProxiedResponse, mount: IProxyMount, target: URL, from: IForwardedFrom, relay: IRelaySettings): Promise<void>;
131
+ /**
132
+ * Sérialise une tête de réponse HTTP/1.1 depuis des en-têtes BRUTS (paires
133
+ * nom/valeur), sans réordonner ni fusionner, en écartant les noms donnés —
134
+ * une réponse `101` doit rendre au client exactement ce que l'amont a négocié.
135
+ *
136
+ * @param status - ligne de statut, sans CRLF
137
+ * @param rawHeaders - `IncomingMessage.rawHeaders`
138
+ * @param skip - noms à écarter (minuscules)
139
+ * @param extra - lignes ajoutées à la fin (`Nom: valeur`)
140
+ * @returns la tête complète, CRLF final compris
141
+ */
142
+ export declare function rawHead(status: string, rawHeaders: readonly string[], skip?: ReadonlySet<string> | null, extra?: readonly string[]): string;
143
+ /**
144
+ * Réponse brute minimale sur un socket d'upgrade, puis fermeture.
145
+ *
146
+ * Les en-têtes passent par la validation de `node:http` : un nom ou une valeur
147
+ * invalide — un saut de ligne surtout, qui forgerait un en-tête de plus — est
148
+ * OMIS, jamais écrit. `Connection` et `Content-Length` sont posés ici et ne se
149
+ * remplacent pas.
150
+ *
151
+ * @param socket - socket du client, encore en attente de sa réponse
152
+ * @param status - statut HTTP du refus
153
+ * @param reason - phrase de raison
154
+ * @param headers - en-têtes du refus (`WWW-Authenticate`, `Retry-After`…)
155
+ */
156
+ export declare function refuseUpgrade(socket: Duplex, status: number, reason: string, headers?: Readonly<Record<string, string | number>> | null): void;
157
+ /**
158
+ * Relaie un upgrade WebSocket DÉJÀ validé (méthode, `Upgrade`, clé, version,
159
+ * `Origin` — cf `ReverseProxy.handleUpgrade`) : rejoue le handshake vers
160
+ * l'amont, renvoie sa réponse `101` au client puis raccorde les deux sockets
161
+ * octet pour octet. Un refus de l'amont (`400`, `403`…) est rendu avec un
162
+ * cadrage refait (fin de corps = fermeture) ; un amont injoignable rend `502`,
163
+ * une connexion non établie à temps — ou un amont qui accepte puis se tait
164
+ * au-delà de `timeoutMs` — `504`, une boucle `508`. Le délai d'inactivité
165
+ * cesse au `101` : un tunnel établi peut rester muet. Une erreur d'un côté
166
+ * détruit l'autre.
167
+ */
168
+ export declare function forwardUpgrade(req: http.IncomingMessage, socket: Duplex, head: Buffer, mount: IProxyMount, target: URL, from: IForwardedFrom, relay: IRelaySettings): void;
@@ -42,6 +42,12 @@ export interface ProxyIntrospection {
42
42
  httpPort: number;
43
43
  /** Port HTTPS/2 du backend (re-encrypt). */
44
44
  httpsPort: number;
45
+ /**
46
+ * Port h2c du backend (`servers.h2c`), ou `null` s'il n'est pas activé. Le
47
+ * générateur haproxy y envoie le trafic en HTTP/2 (`proto h2`) ; nginx, qui
48
+ * ne parle pas HTTP/2 à ses amonts, l'ignore.
49
+ */
50
+ h2cPort?: number | null;
45
51
  /** Dossiers statiques servis à la racine `/` (ordre = priorité). */
46
52
  staticRoots: string[];
47
53
  /** Montages statiques préfixés (servis tels quels). */
@@ -71,6 +77,16 @@ export interface ProxyIntrospection {
71
77
  * saines.
72
78
  */
73
79
  keepaliveIntervalMs: number;
80
+ /**
81
+ * Chemin de la sonde de disponibilité (`health.readinessPath`), ou `null`
82
+ * si les sondes sont désactivées.
83
+ *
84
+ * HAProxy la sonde (`option httpchk`) au lieu d'un simple contrôle de
85
+ * connexion : un exemplaire qui draine garde son port ouvert, mais `/readyz`
86
+ * répond déjà `503` — c'est ce qui le retire du répartiteur AVANT qu'il
87
+ * cesse d'accepter. Un contrôle TCP le garderait jusqu'à la fermeture du port.
88
+ */
89
+ readinessPath?: string | null;
74
90
  /**
75
91
  * Terminaison TLS au frontal, ou `null` pour n'écouter qu'en clair.
76
92
  *
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Règles d'un montage du proxy inverse — UNIQUE source, lue par le schéma de
3
+ * configuration (`proxy.mounts`) ET par `ReverseProxy.mount()` : un montage
4
+ * écrit dans la config et un montage posé par un module obéissent aux mêmes
5
+ * refus, avec les mêmes mots.
6
+ *
7
+ * Pur : n'importe que le cœur, importable depuis `config/config.ts` sans
8
+ * cycle.
9
+ */
10
+ /**
11
+ * Préfixes que le framework se réserve : un montage n'y est JAMAIS accepté.
12
+ * `/nodefony/` porte la console d'administration et les API de Studio — les
13
+ * relayer ailleurs détournerait une surface protégée par le pare-feu.
14
+ */
15
+ export declare const RESERVED_PROXY_PREFIXES: readonly string[];
16
+ /**
17
+ * Préfixes réservés aux montages POSÉS PAR UN MODULE : la configuration ne
18
+ * peut pas les prendre. `/_vite/` appartient à `@nodefony/frontend`, qui y
19
+ * relaie ses serveurs Vite de développement.
20
+ */
21
+ export declare const MODULE_PROXY_PREFIXES: readonly string[];
22
+ /**
23
+ * Normalise un préfixe d'URL : `/` en tête et en fin, sans `/` doublé. Règle
24
+ * UNIQUE des montages statiques et du proxy inverse — deux écritures du même
25
+ * préfixe doivent désigner la même entrée.
26
+ *
27
+ * @param prefix - préfixe tel qu'écrit (`"svc"`, `"/svc"`, `"/svc/"`)
28
+ * @returns le préfixe normalisé (`"/svc/"`)
29
+ */
30
+ export declare function normalizePrefix(prefix: string): string;
31
+ /**
32
+ * Analyse une origine d'amont. Seule une origine NUE est acceptée : un chemin,
33
+ * une requête, un fragment ou des identifiants dans la cible déplaceraient en
34
+ * silence toutes les URL relayées, ou feraient voyager un secret dans la
35
+ * configuration.
36
+ *
37
+ * @param origin - `scheme://hôte[:port]`
38
+ * @returns l'URL analysée, ou `null` si ce n'est pas une origine http(s) nue
39
+ */
40
+ export declare function parseProxyOrigin(origin: string): URL | null;
41
+ /** Ce qu'une règle de montage examine — la forme commune config / code. */
42
+ export interface IProxyMountShape {
43
+ target: unknown;
44
+ methods?: readonly string[] | undefined;
45
+ stripHeaders?: readonly string[] | undefined;
46
+ timeoutMs?: number | undefined;
47
+ secure?: boolean | undefined;
48
+ }
49
+ /** Un refus, nommé : le champ fautif et pourquoi. */
50
+ export interface IProxyMountProblem {
51
+ field: "prefix" | "target" | "methods" | "stripHeaders" | "timeoutMs" | "secure";
52
+ message: string;
53
+ }
54
+ /**
55
+ * Examine un montage. Rend TOUS les refus, pas le premier : une configuration
56
+ * se corrige en une passe.
57
+ *
58
+ * @param prefix - préfixe tel qu'écrit
59
+ * @param mount - réglages du montage
60
+ * @param source - `"config"` (déclaré dans `proxy.mounts`) ou `"module"`
61
+ * (posé par un module) — seuls les préfixes réservés aux modules diffèrent
62
+ * @returns la liste des refus, vide si le montage est sain
63
+ */
64
+ export declare function proxyMountProblems(prefix: string, mount: IProxyMountShape, source: "config" | "module"): IProxyMountProblem[];
65
+ /**
66
+ * Le chemin d'une requête relayée est-il ambigu ? Un chemin que le proxy et
67
+ * l'amont pourraient lire différemment — segment `.`/`..`, barre inverse,
68
+ * octet nul, ou leur forme encodée (`%2e`, `%2f`, `%5c`, `%00`, `%25`) — est
69
+ * REFUSÉ (400), jamais normalisé en silence : c'est le défaut de Traefik, et
70
+ * la parade aux contournements de préfixe (`/svc/..%2fadmin`).
71
+ *
72
+ * @param url - cible brute de la requête (chemin + requête éventuelle)
73
+ * @returns `true` si le chemin doit être refusé
74
+ */
75
+ export declare function isAmbiguousPath(url: string): boolean;
76
+ /**
77
+ * Un upgrade est-il un handshake WebSocket conforme (RFC 6455 §4.2.1) ? Seul
78
+ * un tel handshake est relayé : un `Upgrade` quelconque (`h2c`…) raccordé
79
+ * octet pour octet ouvrirait un tunnel que rien n'inspecte.
80
+ *
81
+ * Appelé à CHAQUE handshake (proxy inverse et routes de Nodefony) : aucune
82
+ * allocation — des expressions testées, jamais de découpe de chaîne.
83
+ *
84
+ * @param method - méthode de la requête
85
+ * @param headers - en-têtes reçus
86
+ * @returns la raison du refus, ou `null` si le handshake est conforme
87
+ */
88
+ export declare function websocketHandshakeProblem(method: string | undefined, headers: Record<string, string | string[] | undefined>): string | null;
@@ -0,0 +1,37 @@
1
+ import type { Http2SecureServer, Http2Server, ServerOptions } from "node:http2";
2
+ import type { IHttpConfig } from "../../config/config.js";
3
+ /**
4
+ * Limites anti-DoS d'un serveur HTTP/2, lues dans la section `http2` du module :
5
+ * `maxSessionMemory` (amplification mémoire) et `maxConcurrentStreams` dans les
6
+ * SETTINGS (CVE-2023-44487, Rapid Reset). Règle UNIQUE des serveurs HTTP/2 — TLS
7
+ * (5152) comme h2c — : une limite oubliée sur l'un d'eux en ferait la porte de
8
+ * l'attaque.
9
+ *
10
+ * Une limite absente garde le défaut de Node (aucune régression).
11
+ *
12
+ * @param limits - `module.options.http2`.
13
+ * @returns les options à fusionner dans celles de `createServer`/`createSecureServer`.
14
+ */
15
+ export declare function http2ServerLimits(limits: Partial<IHttpConfig["http2"]> | undefined): Pick<ServerOptions, "maxSessionMemory" | "settings">;
16
+ /**
17
+ * Borne l'inactivité des sessions d'un serveur HTTP/2 — TLS (5152) comme h2c —
18
+ * et contourne un défaut de Node qui garde chaque session fermée en vie jusqu'à
19
+ * l'échéance de ce délai.
20
+ *
21
+ * Le défaut (Node 24, 26) : `closeSession` (`internal/http2/core`) marque la
22
+ * session détruite PUIS appelle `session.setTimeout(0)` ; or `setStreamTimeout`
23
+ * sort aussitôt sur un objet détruit. Le minuteur n'est jamais désarmé et retient
24
+ * la session (~2,4 Ko) pendant `timeoutMs` : sous un flux de connexions neuves,
25
+ * connexions/s × délai × 2,4 Ko. Signalé à Node : https://github.com/nodejs/node/issues/66885
26
+ *
27
+ * Le contournement : désarmer à la réception du GOAWAY, AVANT la destruction —
28
+ * c'est ainsi que ferment un proxy et un client Node, même par `destroy()`. Rien
29
+ * n'est retiré à la protection : Node n'agit déjà plus à l'échéance sur une
30
+ * session fermée (`sessionOnTimeout` sort sur `closed`). Une coupure TCP sans
31
+ * GOAWAY garde la rétention, bornée au délai.
32
+ *
33
+ * @param server - le serveur HTTP/2.
34
+ * @param timeoutMs - délai d'inactivité ; 0 ou absent le laisse désactivé.
35
+ * @param onTimeout - appelé à l'échéance (événement `timeout` du serveur).
36
+ */
37
+ export declare function setHttp2SessionTimeout(server: Http2Server | Http2SecureServer, timeoutMs: number | undefined, onTimeout: () => void): void;
@@ -10,6 +10,15 @@ import type { ServersConfig } from "nodefony";
10
10
  * @returns la section typée, ou `undefined` si elle est absente ou n'est pas un objet.
11
11
  */
12
12
  export declare function asServersConfig(servers: unknown): ServersConfig | undefined;
13
+ /**
14
+ * Le serveur h2c démarre-t-il ? Seul un `enabled: true` EXPLICITE l'allume : la
15
+ * section existe toujours (défauts), un port seul ne vaut pas activation.
16
+ *
17
+ * @param entry - `servers.h2c`.
18
+ */
19
+ export declare function isH2cEnabled(entry: {
20
+ enabled?: boolean | undefined;
21
+ } | undefined): boolean;
13
22
  /**
14
23
  * Port DEMANDÉ pour un serveur (`servers.<type>.port`), ou `undefined` si le
15
24
  * serveur est désactivé, absent, ou sans port numérique.
@@ -17,4 +26,4 @@ export declare function asServersConfig(servers: unknown): ServersConfig | undef
17
26
  * @param servers - section `servers` déjà rétrécie ({@link asServersConfig}).
18
27
  * @param type - le serveur visé.
19
28
  */
20
- export declare function configuredServerPort(servers: ServersConfig | undefined, type: "http" | "https"): number | undefined;
29
+ export declare function configuredServerPort(servers: ServersConfig | undefined, type: "http" | "https" | "h2c"): number | undefined;
@@ -3,12 +3,19 @@ import { resolvePortPolicy } from "nodefony";
3
3
  import type { PortPolicy } from "nodefony";
4
4
  export { resolvePortPolicy };
5
5
  export type { PortPolicy };
6
- /** Serveur écoutable (surface minimale commune `http`/`https`/`http2`). */
6
+ /**
7
+ * Serveur écoutable (surface minimale commune `http`/`https`/`http2`).
8
+ *
9
+ * Les écouteurs sont typés événement par événement : c'est la forme que les
10
+ * surcharges de Node acceptent telle quelle, sans conversion chez l'appelant.
11
+ */
7
12
  export interface Listenable {
8
13
  listen(port: number, host?: string): unknown;
9
14
  address(): AddressInfo | string | null;
10
- once(event: string, listener: (...args: never[]) => void): unknown;
11
- removeListener(event: string, listener: (...args: never[]) => void): unknown;
15
+ once(event: "error", listener: (error: NodeJS.ErrnoException) => void): unknown;
16
+ once(event: "listening", listener: () => void): unknown;
17
+ removeListener(event: "error", listener: (error: NodeJS.ErrnoException) => void): unknown;
18
+ removeListener(event: "listening", listener: () => void): unknown;
12
19
  }
13
20
  /** Nombre de ports essayés après le désiré, en `auto`. */
14
21
  export declare const DEFAULT_PORT_RETRY_ATTEMPTS = 20;
@@ -34,18 +41,35 @@ export interface ServersPortConfig {
34
41
  https?: {
35
42
  port?: number | undefined;
36
43
  } | false;
44
+ h2c?: {
45
+ enabled?: boolean | undefined;
46
+ port?: number | undefined;
47
+ };
37
48
  portPolicy?: PortPolicy;
38
49
  portRetryAttempts?: number;
39
50
  }
51
+ /** Serveur dont `buildBindPlan` compose le plan. */
52
+ export type BoundServer = "http" | "https" | "h2c";
53
+ /**
54
+ * Serveur actif qui convoite le MÊME port que h2c, ou `null`.
55
+ *
56
+ * Un port partagé avec h2c n'est pas un conflit à contourner : sur ce port, le
57
+ * client HTTP/1.1 et le proxy h2c se marchent dessus, et le repli `auto` ferait
58
+ * glisser l'un des deux là où personne ne l'attend. Le boot le refuse.
59
+ *
60
+ * @param servers - `kernel.options.servers`.
61
+ * @returns `"http"` ou `"https"` en conflit, sinon `null` (h2c inactif, port 0).
62
+ */
63
+ export declare function h2cPortConflict(servers: ServersPortConfig | undefined): "http" | "https" | null;
40
64
  /**
41
65
  * Compose le plan de bind d'un serveur depuis la config du kernel — source UNIQUE
42
- * (les deux serveurs l'appellent ; la politique n'est décidée qu'ici).
66
+ * (les serveurs l'appellent ; la politique n'est décidée qu'ici).
43
67
  *
44
68
  * @param which - le serveur qu'on borne.
45
69
  * @param servers - `kernel.options.servers`.
46
70
  * @param environment - `kernel.environment` (arbitre le défaut de la politique).
47
71
  */
48
- export declare function buildBindPlan(which: "http" | "https", servers: ServersPortConfig | undefined, environment: string | undefined): BindPlan;
72
+ export declare function buildBindPlan(which: BoundServer, servers: ServersPortConfig | undefined, environment: string | undefined): BindPlan;
49
73
  /**
50
74
  * Sonde « un serveur répond-il déjà ici ? ».
51
75
  *
@@ -0,0 +1,69 @@
1
+ import { type IncomingMessage } from "node:http";
2
+ import type { Duplex } from "node:stream";
3
+ import type { WebSocket, WebSocketServer } from "ws";
4
+ /** Serveur qui émet `upgrade` : HTTP/1.1, HTTPS ou HTTP/2 sécurisé (`allowHTTP1`). */
5
+ interface IUpgradeEmitter {
6
+ on(event: "upgrade", listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): unknown;
7
+ removeListener(event: "upgrade", listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): unknown;
8
+ }
9
+ /** Ce que le répartiteur lit du proxy inverse (cf `ReverseProxy`). */
10
+ interface IUpgradeRelay {
11
+ readonly mounts: readonly unknown[] | null;
12
+ handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): boolean;
13
+ }
14
+ /**
15
+ * Le verdict d'un upgrade, rendu par le noyau APRÈS sa décision — qui peut
16
+ * attendre (session, pare-feu, garde de l'action). Un seul verdict compte : le
17
+ * premier appel l'emporte, les suivants sont ignorés.
18
+ */
19
+ export interface IUpgradeAdmission {
20
+ /**
21
+ * Termine le handshake (`101`) et rend la socket ouverte.
22
+ *
23
+ * @returns la socket DIRECTEMENT quand `ws` la rend tout de suite (le cas
24
+ * nominal : aucune promesse, aucune micro-tâche), sinon sa promesse ;
25
+ * `null` si le client est parti pendant la décision (ou si `ws` a rejeté
26
+ * le handshake) — il n'y a alors rien à servir
27
+ */
28
+ accept(): WebSocket | null | Promise<WebSocket | null>;
29
+ /**
30
+ * Refuse par un statut HTTP, AVANT le `101` (RFC 6455 §4.2.2).
31
+ *
32
+ * @param status - statut du refus (`401`, `403`, `429`…)
33
+ * @param headers - en-têtes du refus (`WWW-Authenticate`, `Retry-After`)
34
+ */
35
+ refuse(status: number, headers?: Readonly<Record<string, string | number>> | null): void;
36
+ }
37
+ /** Ce qui décide d'un upgrade : reçoit la requête et rend son verdict, plus tard s'il le faut. */
38
+ export type UpgradeAdmit = (req: IncomingMessage, admission: IUpgradeAdmission) => void;
39
+ /**
40
+ * Pose l'UNIQUE écouteur `upgrade` d'un serveur : le proxy inverse d'abord,
41
+ * puis le serveur WebSocket de Nodefony.
42
+ *
43
+ * Pourquoi un seul écouteur : `ws` attaché par son option `server` prend TOUS
44
+ * les upgrades et répond `400` à ceux qu'il ne reconnaît pas. Un second
45
+ * écouteur, même posé avant, ne l'empêcherait pas de répondre sur un socket
46
+ * déjà relayé. Le serveur `ws` est donc créé en `noServer`, et c'est ici que
47
+ * l'upgrade lui est confié — avec ses propres contrôles intacts (`path`,
48
+ * `verifyClient`, `maxPayload`…), que `handleUpgrade` applique.
49
+ *
50
+ * Entre les deux, `refusal` tranche ce qui doit être refusé AVANT le `101`
51
+ * (RFC 6455 §4.2.2) : un chemin sans route WebSocket reçoit un statut HTTP,
52
+ * jamais une ouverture suivie d'une fermeture. Puis la FORME du handshake est
53
+ * contrôlée (`websocketHandshakeProblem`, la règle du proxy inverse) : un
54
+ * handshake malformé reçoit `400` sans jamais atteindre le noyau. Enfin
55
+ * `admit` décide — pare-feu, garde de l'action — et seul son verdict ouvre.
56
+ *
57
+ * Sans montage, le coût par connexion est une lecture de champ.
58
+ *
59
+ * @param server - serveur HTTP(S) qui reçoit les upgrades
60
+ * @param wss - serveur WebSocket de Nodefony (`noServer: true`)
61
+ * @param relay - proxy inverse, lu à chaque upgrade (il peut naître après)
62
+ * @param refusal - statut HTTP qui refuse l'upgrade, `null` pour l'admettre
63
+ * (cf `HttpKernel.websocketUpgradeRefusal`)
64
+ * @param admit - décide d'un upgrade admis (cf `HttpKernel.onWebsocketUpgrade`) ;
65
+ * par défaut, tout upgrade conforme est accepté et émis en `connection`
66
+ * @returns la fonction qui retire l'écouteur — à appeler à l'arrêt
67
+ */
68
+ export declare function attachUpgradeDispatch(server: IUpgradeEmitter, wss: WebSocketServer, relay: () => IUpgradeRelay | null, refusal: (req: IncomingMessage) => number | null, admit?: UpgradeAdmit): () => void;
69
+ export {};
@@ -118,6 +118,8 @@ declare class Session implements ISession {
118
118
  * `onSaveSession`.
119
119
  *
120
120
  * @param user - principal authentifié (string) lié au blob ; défaut courant.
121
+ * Fourni, il DEVIENT le principal de la session : une sauvegarde suivante,
122
+ * dans la même requête, le garde sans qu'on le lui redonne.
121
123
  */
122
124
  save(user?: string): Promise<this>;
123
125
  /**