@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.
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +19 -11
- 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 +45 -6
- package/dist/nodefony/config/defineModuleConfig.js +2 -3
- package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -3
- package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
- package/dist/nodefony/service/audit-logger.js +22 -16
- package/dist/nodefony/service/certificates.js +228 -19
- package/dist/nodefony/service/error-renderer.js +30 -13
- package/dist/nodefony/service/http-kernel.js +331 -82
- package/dist/nodefony/service/pretty-request-logger.js +5 -4
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +336 -0
- 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 +7 -10
- package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
- package/dist/nodefony/service/servers/server-websocket.js +18 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
- package/dist/nodefony/service/sessions/sessions-service.js +34 -20
- 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 +51 -32
- package/dist/nodefony/src/context/domainMatcher.js +21 -4
- package/dist/nodefony/src/context/forwarded.js +3 -4
- package/dist/nodefony/src/context/http/HttpContext.js +98 -46
- package/dist/nodefony/src/context/http/Request.js +34 -38
- package/dist/nodefony/src/context/http/Response.js +39 -7
- package/dist/nodefony/src/context/http/SseStream.js +349 -0
- 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 +22 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/responseEnded.js +26 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/websocket/Response.js +42 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
- 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 +1 -2
- package/dist/nodefony/src/profiler/Profiler.js +108 -30
- package/dist/nodefony/src/profiler/aggregate.js +422 -0
- package/dist/nodefony/src/proxy/forward.js +500 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +188 -0
- 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 +128 -0
- package/dist/nodefony/src/session/session.js +30 -15
- 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 +12 -3
- package/dist/types/nodefony/config/config.d.ts +16 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
- package/dist/types/nodefony/service/certificates.d.ts +147 -5
- package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
- package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
- 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 +2 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
- package/dist/types/nodefony/src/context/Context.d.ts +35 -6
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -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 +15 -5
- package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +88 -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 +69 -0
- package/dist/types/nodefony/src/session/session.d.ts +2 -0
- package/docs/cookies.md +85 -78
- package/docs/index.md +5 -1
- package/docs/observabilite.md +81 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +522 -0
- package/docs/servers.md +378 -230
- package/docs/session.md +180 -180
- package/docs/sse.md +287 -0
- package/docs/upload.md +120 -117
- package/package.json +10 -11
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -7,14 +7,17 @@ import type { IRateLimitStore } from "../src/rateLimit/IRateLimitStore.js";
|
|
|
7
7
|
import { type ITrustedHostsConfig } from "../src/context/domainMatcher.js";
|
|
8
8
|
import http from "node:http";
|
|
9
9
|
import http2 from "node:http2";
|
|
10
|
-
import type { IncomingMessage } from "node:http";
|
|
10
|
+
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
|
11
11
|
import Ws from "ws";
|
|
12
12
|
import httpServer from "../service/servers/server-http.js";
|
|
13
13
|
import httpsServer from "../service/servers/server-https.js";
|
|
14
|
+
import h2cServer from "../service/servers/server-h2c.js";
|
|
14
15
|
import websocketServer from "../service/servers/server-websocket.js";
|
|
15
16
|
import websocketSecureServer from "../service/servers/server-websocket-secure.js";
|
|
16
17
|
import Statics from "./servers/server-static.js";
|
|
18
|
+
import type ReverseProxy from "./reverse-proxy.js";
|
|
17
19
|
import WebsocketContext from "../src/context/websocket/WebsocketContext.js";
|
|
20
|
+
import type { IUpgradeAdmission } from "../src/servers/upgradeDispatch.js";
|
|
18
21
|
import HttpContext from "../src/context/http/HttpContext.js";
|
|
19
22
|
import Context, { HTTPMethod, WebSocketState } from "../src/context/Context.js";
|
|
20
23
|
import Certicates from "./certificates.js";
|
|
@@ -25,13 +28,24 @@ import type { IErrorRenderer } from "../interfaces/IErrorRenderer.js";
|
|
|
25
28
|
import type { IRequestLogger } from "../interfaces/IRequestLogger.js";
|
|
26
29
|
import type { DefaultOptionsService } from "nodefony";
|
|
27
30
|
import type { IHttpConfig } from "../config/config.js";
|
|
31
|
+
/**
|
|
32
|
+
* Config interne (shape déclarée par `config/config.ts`). Locale au module —
|
|
33
|
+
* pas exportée tant que la surface publique n'a pas vocation à être étendue.
|
|
34
|
+
*/
|
|
35
|
+
/** Ce qu'il faut d'une requête pour en compter la connexion par IP. */
|
|
36
|
+
export interface IConnectionRequest {
|
|
37
|
+
readonly headers: IncomingHttpHeaders;
|
|
38
|
+
readonly socket: {
|
|
39
|
+
readonly remoteAddress?: string | undefined;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
28
42
|
export type ProtocolType = "1.1" | "2.0" | "3.0";
|
|
29
43
|
export type httpRequest = http.IncomingMessage | http2.Http2ServerRequest;
|
|
30
44
|
export type httpResponse = http.ServerResponse | http2.Http2ServerResponse;
|
|
45
|
+
export { responseEnded } from "../src/context/responseEnded.js";
|
|
31
46
|
export type ContextType = WebsocketContext | HttpContext | Context;
|
|
32
|
-
export type ServerType
|
|
33
|
-
|
|
34
|
-
export type SchemeType = "http" | "https" | "ws" | "wss";
|
|
47
|
+
export type { ServerType, SchemeType } from "../interfaces/IContext.js";
|
|
48
|
+
import type { ServerType, SchemeType } from "../interfaces/IContext.js";
|
|
35
49
|
export interface WsMetaData {
|
|
36
50
|
type?: "message" | "handshake" | undefined;
|
|
37
51
|
state?: WebSocketState | undefined;
|
|
@@ -81,18 +95,31 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
81
95
|
cert: string;
|
|
82
96
|
ca: string;
|
|
83
97
|
serverStatic: Statics | null;
|
|
98
|
+
/**
|
|
99
|
+
* Proxy inverse (`reverse-proxy`) — lu à chaque requête avant le routage ;
|
|
100
|
+
* `mounts === null` tant que personne n'a monté de préfixe.
|
|
101
|
+
*/
|
|
102
|
+
reverseProxy: ReverseProxy | null;
|
|
84
103
|
domain: string;
|
|
85
104
|
trustedHosts?: ITrustedHostsConfig | undefined;
|
|
86
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Contrôle de domaine actif (`domainCheck` de la config d'application) —
|
|
107
|
+
* lu à la source, pour le HTTP comme pour le handshake WebSocket.
|
|
108
|
+
*/
|
|
109
|
+
get domainCheck(): boolean;
|
|
87
110
|
regAlias: RegExp[];
|
|
88
111
|
module: Module;
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Port du serveur HTTPS effectivement lié, `undefined` s'il est inactif —
|
|
114
|
+
* lu sur le serveur, jamais recopié (lu par `HttpContext.redirectHttps`).
|
|
115
|
+
*/
|
|
116
|
+
get httpsPort(): number | undefined;
|
|
117
|
+
/** Port du serveur HTTP effectivement lié, `undefined` s'il est inactif. */
|
|
118
|
+
get httpPort(): number | undefined;
|
|
119
|
+
private boundPort;
|
|
91
120
|
responseTimeout: {
|
|
92
121
|
http: number;
|
|
93
122
|
https: number;
|
|
94
|
-
http2: number;
|
|
95
|
-
http3: number;
|
|
96
123
|
};
|
|
97
124
|
closeTimeOutWs: {
|
|
98
125
|
ws: number;
|
|
@@ -196,6 +223,22 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
196
223
|
* @returns la politique mémoïsée (0 alloc après le 1er handshake).
|
|
197
224
|
*/
|
|
198
225
|
private getWsOriginPolicy;
|
|
226
|
+
/**
|
|
227
|
+
* L'`Origin` d'un handshake WebSocket est-elle acceptée ? Règle UNIQUE,
|
|
228
|
+
* interrogeable SANS contexte : le serveur WebSocket de Nodefony
|
|
229
|
+
* ({@link checkWebsocketOrigin}) et le proxy inverse, qui relaie un upgrade
|
|
230
|
+
* avant qu'aucun contexte n'existe, décident par elle.
|
|
231
|
+
*
|
|
232
|
+
* Same-origin (nom de l'`Origin` = nom servi, port ignoré), loopback toléré
|
|
233
|
+
* en development, allowlist `allowedOrigins` ; sans `Origin` (client
|
|
234
|
+
* non-navigateur) → acceptée. Doctrine complète : {@link checkWebsocketOrigin}.
|
|
235
|
+
*
|
|
236
|
+
* @param origin - valeur brute de l'en-tête `Origin`
|
|
237
|
+
* @param hostname - nom d'hôte servi, sans port (`Context.domain`)
|
|
238
|
+
* @param secure - handshake reçu en TLS (politique `websocketSecure`)
|
|
239
|
+
* @returns `true` si le handshake peut se poursuivre
|
|
240
|
+
*/
|
|
241
|
+
isWebsocketOriginAllowed(origin: string | undefined, hostname: string, secure: boolean): boolean;
|
|
199
242
|
/**
|
|
200
243
|
* B4 — Validation d'`Origin` au handshake WebSocket (anti-CSWSH, OWASP
|
|
201
244
|
* WSTG-CLNT-10). Les navigateurs n'appliquent PAS CORS aux WebSockets : sans
|
|
@@ -304,7 +347,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
304
347
|
*/
|
|
305
348
|
/**
|
|
306
349
|
* Override the default error renderer — e.g. to hide stack traces in prod
|
|
307
|
-
* or emit RFC
|
|
350
|
+
* or emit RFC 9457 problem+json. Stateless singleton expected.
|
|
308
351
|
*/
|
|
309
352
|
setErrorRenderer(renderer: IErrorRenderer): void;
|
|
310
353
|
getErrorRenderer(): IErrorRenderer;
|
|
@@ -339,7 +382,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
339
382
|
* pipeline.
|
|
340
383
|
*/
|
|
341
384
|
private dispatchHttpRequest;
|
|
342
|
-
initServers(): Promise<(httpServer | httpsServer | websocketServer | websocketSecureServer)[]>;
|
|
385
|
+
initServers(): Promise<(httpServer | httpsServer | h2cServer | websocketServer | websocketSecureServer)[]>;
|
|
343
386
|
/**
|
|
344
387
|
* Publie les ports EFFECTIFS du runtime (state file), une fois l'écoute établie.
|
|
345
388
|
*
|
|
@@ -498,7 +541,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
498
541
|
* @throws (rejet) le refus du pare-feu, après `onAuthFailure`.
|
|
499
542
|
*/
|
|
500
543
|
private runFirewall;
|
|
501
|
-
createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws, type: ServerType): WebsocketContext;
|
|
544
|
+
createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws | null, type: ServerType): WebsocketContext;
|
|
502
545
|
/**
|
|
503
546
|
* Teardown d'une connexion WS à sa fermeture : signal d'abandon, hooks
|
|
504
547
|
* post-réponse, persistance de la session, puis libération du scope.
|
|
@@ -506,10 +549,47 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
506
549
|
* écouteur d'événement synchrone, qui n'attend rien).
|
|
507
550
|
*/
|
|
508
551
|
private finishWebsocket;
|
|
509
|
-
|
|
510
|
-
|
|
552
|
+
/**
|
|
553
|
+
* Point d'entrée d'un upgrade vers une route WebSocket de Nodefony, appelé
|
|
554
|
+
* par le répartiteur d'upgrade AVANT le `101` (#596) : rien ne s'ouvre tant
|
|
555
|
+
* que le pipeline n'a pas dit oui.
|
|
556
|
+
*
|
|
557
|
+
* Les bornes par IP passent en premier, avant tout scope, ALS ou pipeline
|
|
558
|
+
* (symétrie avec le 429 HTTP et avec le proxy inverse) : le vrai vecteur DoS
|
|
559
|
+
* est d'accumuler des sockets ouvertes. La place est rendue à la fermeture du
|
|
560
|
+
* socket du client — même règle que pour un tunnel relayé.
|
|
561
|
+
*
|
|
562
|
+
* @param req - requête d'upgrade
|
|
563
|
+
* @param admission - le verdict à rendre : accepter (`101`) ou refuser (HTTP)
|
|
564
|
+
* @param type - serveur qui a reçu l'upgrade
|
|
565
|
+
* @returns la promesse du traitement de la connexion
|
|
566
|
+
*/
|
|
567
|
+
onWebsocketUpgrade(req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
|
|
568
|
+
/**
|
|
569
|
+
* Pipeline d'un handshake WebSocket, dans la bulle ALS de la connexion :
|
|
570
|
+
* jugement (`onConnect`), pare-feu, gardes de l'action — PUIS seulement
|
|
571
|
+
* l'acceptation, la socket et l'action de handshake. Tout refus antérieur à
|
|
572
|
+
* l'acceptation part en statut HTTP (`onError` → `refuseHandshake`).
|
|
573
|
+
*
|
|
574
|
+
* @param scope - scope DI de la connexion
|
|
575
|
+
* @param req - requête d'upgrade
|
|
576
|
+
* @param admission - le verdict à rendre
|
|
577
|
+
* @param type - serveur qui a reçu l'upgrade
|
|
578
|
+
* @returns ce que rend l'action de handshake
|
|
579
|
+
*/
|
|
580
|
+
handleWebsocket(scope: Scope, req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
|
|
511
581
|
private releaseOrphanWsScope;
|
|
512
|
-
|
|
582
|
+
/**
|
|
583
|
+
* Juge un handshake WebSocket AVANT son acceptation : hôte, origine,
|
|
584
|
+
* `beforeResolve`, contrôleur (protocole négocié), session. N'ouvre rien —
|
|
585
|
+
* l'acceptation n'a lieu qu'après le pare-feu et les gardes de l'action
|
|
586
|
+
* (cf {@link handleWebsocket}).
|
|
587
|
+
*
|
|
588
|
+
* @param context - contexte du handshake, encore sans socket
|
|
589
|
+
* @param error - erreur levée à la construction du contexte, relancée telle quelle
|
|
590
|
+
* @throws la première erreur du jugement — rendue en statut HTTP par `onError`
|
|
591
|
+
*/
|
|
592
|
+
onConnect(context: WebsocketContext | null, error?: unknown): Promise<void>;
|
|
513
593
|
checkValidDomain(context: ContextType): number;
|
|
514
594
|
isValidDomain(context: ContextType): boolean;
|
|
515
595
|
/**
|
|
@@ -527,5 +607,67 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
527
607
|
* couvre ce nom.
|
|
528
608
|
*/
|
|
529
609
|
isTrustedHostname(hostname: string): boolean;
|
|
610
|
+
/**
|
|
611
|
+
* Refus d'un upgrade WebSocket AVANT le `101`, par un statut HTTP
|
|
612
|
+
* (RFC 6455 §4.2.2) — appelé par le répartiteur d'upgrade, après le proxy
|
|
613
|
+
* inverse et avant le serveur `ws`.
|
|
614
|
+
*
|
|
615
|
+
* Pourquoi avant : accepter puis fermer fait croire au client que le serveur
|
|
616
|
+
* a répondu. Le client Vite sonde « le serveur est-il revenu ? » en ouvrant
|
|
617
|
+
* une socket ; une ouverture suivie d'une fermeture `4004` lui faisait
|
|
618
|
+
* recharger la page avant que Vite soit prêt — page blanche.
|
|
619
|
+
*
|
|
620
|
+
* Ne juge que l'existence d'une route WebSocket pour le chemin
|
|
621
|
+
* ({@link IRequestRouter.servesWebsocket}) : hôte, origine, sous-protocole,
|
|
622
|
+
* quotas et pare-feu restent jugés après l'ouverture, comme avant. Un refus
|
|
623
|
+
* compte au limiteur de débit comme une requête HTTP refusée — sans quoi un
|
|
624
|
+
* flot d'upgrades vers des chemins inexistants y échapperait.
|
|
625
|
+
*
|
|
626
|
+
* @param req - requête d'upgrade
|
|
627
|
+
* @returns le statut du refus — `404` chemin sans route WebSocket, `400` URL
|
|
628
|
+
* inanalysable, `429` débit dépassé, `503` routeur pas encore posé (boot) —
|
|
629
|
+
* ou `null` si l'upgrade est admis
|
|
630
|
+
*/
|
|
631
|
+
websocketUpgradeRefusal(req: IncomingMessage): 400 | 404 | 429 | 503 | null;
|
|
632
|
+
/**
|
|
633
|
+
* Bornes par IP d'un upgrade WebSocket : débit de handshakes (le MÊME
|
|
634
|
+
* compteur que les requêtes HTTP — un upgrade EST un GET) puis connexions
|
|
635
|
+
* simultanées (`wsMaxConnectionsPerIp`). Seule implémentation, appelée par
|
|
636
|
+
* le serveur WebSocket de Nodefony ET par le proxy inverse pour un upgrade
|
|
637
|
+
* qu'il relaie : un tunnel relayé n'échappe pas aux quotas d'une connexion
|
|
638
|
+
* servie.
|
|
639
|
+
*
|
|
640
|
+
* L'IP est résolue comme en HTTP (RFC 7239 + `trustProxy`), et seulement si
|
|
641
|
+
* un limiteur est armé — 0 coût quand tout est désactivé (défaut). Aucun log
|
|
642
|
+
* par refus : sous un flood, il serait lui-même un amplificateur.
|
|
643
|
+
*
|
|
644
|
+
* @param req - requête d'upgrade
|
|
645
|
+
* @param connection - ce dont la fermeture libère la place comptée (le `ws`
|
|
646
|
+
* accepté, ou le socket du client pour un tunnel relayé)
|
|
647
|
+
* @returns `null` si l'upgrade est admis — sa place est alors rendue à la
|
|
648
|
+
* fermeture de `connection` —, sinon la raison du refus
|
|
649
|
+
*/
|
|
650
|
+
websocketQuotaRefusal(req: IncomingMessage, connection: {
|
|
651
|
+
once(event: "close", listener: () => void): unknown;
|
|
652
|
+
}): "rate limit" | "too many connections" | null;
|
|
653
|
+
/**
|
|
654
|
+
* Prend une place de connexion LONGUE pour l'IP de la requête — le plafond
|
|
655
|
+
* `wsMaxConnectionsPerIp`, partagé par les sockets WebSocket ET les flux SSE :
|
|
656
|
+
* un seul budget par IP, sinon N sockets plus N flux doubleraient le plafond.
|
|
657
|
+
* Le débit n'est pas recompté ici : la requête qui ouvre un flux l'a déjà été
|
|
658
|
+
* à l'entrée du pipeline.
|
|
659
|
+
*
|
|
660
|
+
* @param req - la requête qui ouvre le flux (en-têtes + socket du pair)
|
|
661
|
+
* @param connection - ce dont la fermeture rend la place (la réponse)
|
|
662
|
+
* @returns `true` si la place est prise — ou si aucun plafond n'est armé
|
|
663
|
+
*/
|
|
664
|
+
acquireConnectionSlot(req: IConnectionRequest, connection: {
|
|
665
|
+
once(event: "close", listener: () => void): unknown;
|
|
666
|
+
}): boolean;
|
|
667
|
+
/**
|
|
668
|
+
* Re-valide tous les flux SSE et ferme ceux dont l'identité est morte.
|
|
669
|
+
* Publique pour qu'un banc la déclenche sans attendre le tick.
|
|
670
|
+
*/
|
|
671
|
+
revalidateStreams(): Promise<void>;
|
|
530
672
|
}
|
|
531
673
|
export default HttpKernel;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import { Service, Module } from "nodefony";
|
|
4
|
+
import type { IProxyMount, IProxyMountOptions } from "../interfaces/IReverseProxy.js";
|
|
5
|
+
import { type ProxiedRequest, type ProxiedResponse } from "../src/proxy/forward.js";
|
|
6
|
+
/** Un montage tel que le service le garde : la dernière cible analysée y est mise en cache. */
|
|
7
|
+
interface IMountRecord extends IProxyMount {
|
|
8
|
+
/** D'où vient le montage : la configuration de l'application, ou un module. */
|
|
9
|
+
readonly source: "config" | "module";
|
|
10
|
+
/** Dernière origine rendue par `target`, et son analyse. */
|
|
11
|
+
lastOrigin: string | undefined;
|
|
12
|
+
lastUrl: URL | undefined;
|
|
13
|
+
/** Pool de connexions vers l'amont, créé au premier relais (scheme de la cible). */
|
|
14
|
+
agent: http.Agent | null;
|
|
15
|
+
/** Tunnels WebSocket ouverts vers l'amont — bornés par `maxSockets`, hors pool. */
|
|
16
|
+
tunnels: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Proxy inverse de Nodefony : relaie un préfixe d'URL vers un autre serveur
|
|
20
|
+
* HTTP — requêtes ET, sur demande, upgrades WebSocket — sur la MÊME origine
|
|
21
|
+
* que la page. Le client ne voit qu'un serveur, un certificat ; l'amont peut
|
|
22
|
+
* rester sur la boucle locale.
|
|
23
|
+
*
|
|
24
|
+
* Deux sources de montages, mêmes règles (`src/proxy/rules.ts`) : la section
|
|
25
|
+
* `proxy.mounts` de la configuration, lue à la construction, et
|
|
26
|
+
* {@link ReverseProxy.mount}, appelé par un module (`@nodefony/frontend` y
|
|
27
|
+
* relaie `/_vite/<famille>/` vers ses serveurs Vite de développement). Un
|
|
28
|
+
* module se résout ce service par NOM (`container.get("reverse-proxy")`) :
|
|
29
|
+
* il n'a pas à importer `@nodefony/http`.
|
|
30
|
+
*
|
|
31
|
+
* Consulté AVANT le routage : une route attrape-tout de l'application ne peut
|
|
32
|
+
* pas avaler un préfixe relayé. Sans montage, le pipeline ne paie qu'une
|
|
33
|
+
* lecture de champ (`mounts === null`) — c'est le cas de toute application
|
|
34
|
+
* qui ne s'en sert pas.
|
|
35
|
+
*
|
|
36
|
+
* Règles communes à tout montage : en-têtes de connexion jamais transmis
|
|
37
|
+
* (RFC 9110 §7.6.1), `X-Forwarded-For/-Proto/-Host` et `Via` posés par le
|
|
38
|
+
* proxy, Host hors `trustedHosts` jamais relayé (même barrière, même 421 que
|
|
39
|
+
* toute requête), amont injoignable → 502, amont muet → 504, boucle → 508.
|
|
40
|
+
*/
|
|
41
|
+
declare class ReverseProxy extends Service {
|
|
42
|
+
#private;
|
|
43
|
+
module: Module;
|
|
44
|
+
/**
|
|
45
|
+
* Montages actifs, du préfixe le PLUS LONG au plus court : le premier qui
|
|
46
|
+
* couvre une URL est le plus spécifique. `null` tant qu'il n'y en a aucun.
|
|
47
|
+
*/
|
|
48
|
+
mounts: IMountRecord[] | null;
|
|
49
|
+
/** Délai d'inactivité appliqué aux montages qui n'en fixent pas (`proxy.timeoutMs`). */
|
|
50
|
+
readonly defaultTimeoutMs: number;
|
|
51
|
+
/** Délai d'établissement de la connexion amont (`proxy.connectTimeoutMs`). */
|
|
52
|
+
readonly connectTimeoutMs: number;
|
|
53
|
+
/** Connexions simultanées par amont (`proxy.maxSockets`) : pool ET tunnels WebSocket. */
|
|
54
|
+
readonly maxSockets: number;
|
|
55
|
+
/**
|
|
56
|
+
* Nom de CE processus dans `Via` (RFC 9110 §7.6.3) : tiré au hasard au
|
|
57
|
+
* démarrage, il ne dit rien de la machine et distingue deux instances —
|
|
58
|
+
* un message qui le porte déjà est une boucle (508).
|
|
59
|
+
*/
|
|
60
|
+
readonly pseudonym: string;
|
|
61
|
+
constructor(module: Module);
|
|
62
|
+
/**
|
|
63
|
+
* Relaie un préfixe d'URL vers un amont. Idempotent pour un même module :
|
|
64
|
+
* un même préfixe est remplacé. Un module qui recouvre un préfixe de la
|
|
65
|
+
* CONFIGURATION le remplace aussi, mais le dit — l'auteur de l'application
|
|
66
|
+
* doit pouvoir lire pourquoi son montage ne sert plus.
|
|
67
|
+
*
|
|
68
|
+
* @param prefix - préfixe d'URL (normalisé : `/` en tête et en fin)
|
|
69
|
+
* @param options - cible et réglages du montage
|
|
70
|
+
* @throws Error qui nomme chaque refus (préfixe `/` ou réservé, cible qui
|
|
71
|
+
* n'est pas une origine http(s) nue, `secure: false` hors boucle locale…)
|
|
72
|
+
*/
|
|
73
|
+
mount(prefix: string, options: IProxyMountOptions): void;
|
|
74
|
+
/**
|
|
75
|
+
* Retire un montage. Le dernier retiré rend `mounts` à `null` : le pipeline
|
|
76
|
+
* cesse alors de le consulter.
|
|
77
|
+
*
|
|
78
|
+
* @param prefix - préfixe tel que monté (normalisé de la même façon)
|
|
79
|
+
*/
|
|
80
|
+
unmount(prefix: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Ferme tous les pools (arrêt du noyau). Les montages restent déclarés ; un
|
|
83
|
+
* relais encore en vol part ensuite sur une connexion à usage unique.
|
|
84
|
+
*/
|
|
85
|
+
closeAll(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Montage le plus spécifique qui couvre une URL, s'il y en a un.
|
|
88
|
+
*
|
|
89
|
+
* @param url - cible brute de la requête (chemin + requête)
|
|
90
|
+
* @returns le montage au préfixe le plus long qui couvre l'URL
|
|
91
|
+
*/
|
|
92
|
+
match(url: string | undefined): IProxyMount | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Origine courante d'un montage, analysée — mise en cache tant que la cible
|
|
95
|
+
* rend la même chaîne.
|
|
96
|
+
*
|
|
97
|
+
* @returns l'URL de l'amont, ou `undefined` si rien n'est relayable
|
|
98
|
+
*/
|
|
99
|
+
private targetOf;
|
|
100
|
+
/**
|
|
101
|
+
* Pool de connexions d'un montage, créé au premier relais et recréé si la
|
|
102
|
+
* cible change de scheme.
|
|
103
|
+
*
|
|
104
|
+
* Un `Agent` DÉDIÉ, jamais l'agent global : sous `NODE_USE_ENV_PROXY`, cas
|
|
105
|
+
* courant d'un poste d'entreprise, l'agent global envoie tout vers
|
|
106
|
+
* `HTTP_PROXY` — un amont sur la boucle locale serait demandé au proxy de
|
|
107
|
+
* l'entreprise. Un agent construit ici n'a pas de `proxyEnv` : il va droit
|
|
108
|
+
* à la cible. Keep-alive borné (`maxSockets`), `lifo` : le socket le plus
|
|
109
|
+
* récent, le moins susceptible d'avoir été fermé par l'amont.
|
|
110
|
+
*/
|
|
111
|
+
private agentFor;
|
|
112
|
+
/** Réglages de relais d'un montage. */
|
|
113
|
+
private relayFor;
|
|
114
|
+
/** Règles du noyau HTTP (résolues par nom), `undefined` hors serveur. */
|
|
115
|
+
private rules;
|
|
116
|
+
/** Ce que le proxy annonce à l'amont sur le client. */
|
|
117
|
+
private fromOf;
|
|
118
|
+
/**
|
|
119
|
+
* Relaie une requête HTTP si un montage la couvre — méthode comprise. Cible
|
|
120
|
+
* pas encore connue → `503` + `Retry-After`, sans aller jusqu'au routage.
|
|
121
|
+
*
|
|
122
|
+
* L'appelant a déjà tranché la barrière d'hôte : c'est le pipeline HTTP, qui
|
|
123
|
+
* la calcule pour toute requête (`Context.validDomain`). Un chemin ambigu
|
|
124
|
+
* sous un préfixe monté rend 400, sans contacter l'amont.
|
|
125
|
+
*
|
|
126
|
+
* ⚠️ Un préfixe monté est servi AVANT le pare-feu, la CSRF et les en-têtes
|
|
127
|
+
* de sécurité applicatifs : c'est une surface publique pour qui atteint
|
|
128
|
+
* Nodefony. L'amont porte sa propre politique d'accès.
|
|
129
|
+
*
|
|
130
|
+
* @param req - requête du client
|
|
131
|
+
* @param res - réponse au client
|
|
132
|
+
* @param scheme - scheme par lequel le client est arrivé
|
|
133
|
+
* @returns la promesse de fin d'échange, ou `undefined` si la requête n'est
|
|
134
|
+
* pas relayée (hors préfixe ou méthode non relayée : elle suit alors son
|
|
135
|
+
* chemin normal)
|
|
136
|
+
*/
|
|
137
|
+
forward(req: ProxiedRequest, res: ProxiedResponse, scheme: string): Promise<void> | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Relaie un upgrade WebSocket si un montage `websocket` couvre son URL.
|
|
140
|
+
* Appelé par le répartiteur d'`upgrade` des serveurs WebSocket, AVANT le
|
|
141
|
+
* serveur WebSocket de Nodefony.
|
|
142
|
+
*
|
|
143
|
+
* Contrôles, dans l'ordre, chacun avec la réponse que rendrait le serveur
|
|
144
|
+
* WebSocket de Nodefony : cible pas encore connue → 503 ; `Host` hors `trustedHosts` (si `domainCheck`) →
|
|
145
|
+
* 421 ; chemin ambigu ou handshake non conforme (RFC 6455 §4.2.1 — seul
|
|
146
|
+
* `Upgrade: websocket` est relayé, jamais un tunnel `h2c`) → 400 ;
|
|
147
|
+
* `Origin` refusée par la MÊME règle que le serveur WebSocket (anti-CSWSH,
|
|
148
|
+
* RFC 6455 §10.2) → 403 ; `maxSockets` tunnels déjà ouverts vers l'amont →
|
|
149
|
+
* 503 ; quotas par IP du noyau (débit de handshakes, connexions
|
|
150
|
+
* simultanées) → 429. Le socket est alors consommé.
|
|
151
|
+
*
|
|
152
|
+
* @returns `true` si le proxy a pris le socket en charge
|
|
153
|
+
*/
|
|
154
|
+
handleUpgrade(req: http.IncomingMessage, socket: Duplex, head: Buffer): boolean;
|
|
155
|
+
}
|
|
156
|
+
export default ReverseProxy;
|
|
@@ -12,3 +12,19 @@ import type { Duplex } from "node:stream";
|
|
|
12
12
|
* @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
|
|
13
13
|
*/
|
|
14
14
|
export declare function handleClientError(error: NodeJS.ErrnoException, socket: Duplex): void;
|
|
15
|
+
/**
|
|
16
|
+
* Gère l'event `sessionError` d'un serveur HTTP/2 (h2c ou TLS) : un pair PARTI
|
|
17
|
+
* se tait, toute autre erreur est rapportée.
|
|
18
|
+
*
|
|
19
|
+
* Une session réinitialisée par le pair n'a rien à diagnostiquer. C'est la
|
|
20
|
+
* signature d'une sonde de port — `nodefony status`, `doctor`, le superviseur
|
|
21
|
+
* de développement, la sonde `tcpSocket` d'un orchestrateur — qui se connecte
|
|
22
|
+
* puis ferme sans lire le `SETTINGS` que le serveur envoie dès l'ouverture : le
|
|
23
|
+
* système ferme alors par un RST plutôt qu'un FIN, et Node lève `ECONNRESET`.
|
|
24
|
+
* La rapporter imprimait une pile entière par sonde. La règle « client parti »
|
|
25
|
+
* est celle des frames WebSocket ({@link isPeerGoneError}), pas une copie.
|
|
26
|
+
*
|
|
27
|
+
* @param error - erreur de session émise par `node:http2`
|
|
28
|
+
* @param report - rapporte une erreur réelle, à la sévérité choisie par le serveur
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleSessionError(error: Error, report: (error: Error) => void): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Service, type Module, FamilyType } from "nodefony";
|
|
2
|
+
import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
|
|
3
|
+
import http2 from "node:http2";
|
|
4
|
+
import { AddressInfo } from "node:net";
|
|
5
|
+
import { type IDrainTerminator } from "./serverShutdown.js";
|
|
6
|
+
import type { IHttpConfig } from "../../config/config.js";
|
|
7
|
+
import type { DefaultOptionsService } from "nodefony";
|
|
8
|
+
/**
|
|
9
|
+
* Serveur HTTP/2 en clair (h2c, connaissance préalable — RFC 9113 §3.3), sur un
|
|
10
|
+
* port DÉDIÉ (`servers.h2c`, 5153 par défaut), pour un proxy inverse qui parle
|
|
11
|
+
* HTTP/2 à ses amonts.
|
|
12
|
+
*
|
|
13
|
+
* Il partage le chemin HTTP/2 du 5152 — même `onHttpRequest`, mêmes limites
|
|
14
|
+
* anti-DoS (`http2ServerLimits`), même drain par GOAWAY — sans TLS : `serverType`
|
|
15
|
+
* vaut `"http"`, le schéma vient du transport ou d'un proxy de confiance, jamais
|
|
16
|
+
* du pseudo-en-tête `:scheme`. Le protocole du saut (`"h2c"`) se constate sur la
|
|
17
|
+
* requête (`transport.ts#alpnProtocolOf`).
|
|
18
|
+
*
|
|
19
|
+
* Hors de son périmètre, délibérément : HTTP/1.1 (refusé par la préface HTTP/2 —
|
|
20
|
+
* sondes et clients HTTP/1.1 restent sur le port HTTP), WebSocket (aucun CONNECT
|
|
21
|
+
* étendu, RFC 8441 : un proxy route `Upgrade: websocket` vers le port HTTP) et la
|
|
22
|
+
* bascule `Upgrade: h2c` (retirée par RFC 9113 §3.1).
|
|
23
|
+
*/
|
|
24
|
+
declare class ServerH2c extends Service {
|
|
25
|
+
private readonly httpKernel;
|
|
26
|
+
options: IHttpConfig["http"] & DefaultOptionsService;
|
|
27
|
+
module: Module<IHttpConfig>;
|
|
28
|
+
server: http2.Http2Server | null;
|
|
29
|
+
httpTerminator: IDrainTerminator | null;
|
|
30
|
+
port: number;
|
|
31
|
+
protocol: ProtocolType;
|
|
32
|
+
ready: boolean;
|
|
33
|
+
type: ServerType;
|
|
34
|
+
domain: string | null;
|
|
35
|
+
scheme: SchemeType;
|
|
36
|
+
address: string | null;
|
|
37
|
+
family: FamilyType | null;
|
|
38
|
+
active: boolean;
|
|
39
|
+
infos: AddressInfo | null;
|
|
40
|
+
constructor(module: Module<IHttpConfig>, httpKernel: HttpKernel);
|
|
41
|
+
/**
|
|
42
|
+
* Crée et met en écoute le serveur h2c.
|
|
43
|
+
*
|
|
44
|
+
* @returns le serveur à l'écoute
|
|
45
|
+
* @throws BootConfigurationError si `servers.h2c.port` est celui du serveur
|
|
46
|
+
* HTTP ou HTTPS — un port partagé n'est pas un conflit à contourner
|
|
47
|
+
*/
|
|
48
|
+
createServer(): Promise<http2.Http2Server>;
|
|
49
|
+
showBanner(): void;
|
|
50
|
+
}
|
|
51
|
+
export default ServerH2c;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Service, Module, FamilyType } from "nodefony";
|
|
1
|
+
import { Service, type Module, FamilyType } from "nodefony";
|
|
2
2
|
import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
|
|
3
3
|
import http from "node:http";
|
|
4
4
|
import http2 from "node:http2";
|
|
5
5
|
import { AddressInfo } from "node:net";
|
|
6
|
-
import {
|
|
6
|
+
import { type IDrainTerminator } from "./serverShutdown.js";
|
|
7
7
|
import type { IHttpConfig } from "../../config/config.js";
|
|
8
8
|
import type { DefaultOptionsService } from "nodefony";
|
|
9
9
|
declare class ServerHttp extends Service {
|
|
@@ -11,7 +11,7 @@ declare class ServerHttp extends Service {
|
|
|
11
11
|
options: IHttpConfig["http"] & DefaultOptionsService;
|
|
12
12
|
module: Module;
|
|
13
13
|
server: http.Server | http2.Http2Server | null;
|
|
14
|
-
httpTerminator:
|
|
14
|
+
httpTerminator: IDrainTerminator | null;
|
|
15
15
|
port: number;
|
|
16
16
|
protocol: ProtocolType;
|
|
17
17
|
ready: boolean;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { Service, Module, FamilyType } from "nodefony";
|
|
1
|
+
import { Service, type Module, FamilyType } from "nodefony";
|
|
2
2
|
import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
|
|
3
3
|
import https from "node:https";
|
|
4
4
|
import http2 from "node:http2";
|
|
5
|
-
import {
|
|
5
|
+
import { type IDrainTerminator } from "./serverShutdown.js";
|
|
6
6
|
import { AddressInfo } from "node:net";
|
|
7
7
|
import type { IHttpConfig } from "../../config/config.js";
|
|
8
8
|
import type { DefaultOptionsService } from "nodefony";
|
|
9
9
|
declare class ServerHttps extends Service {
|
|
10
10
|
private readonly httpKernel;
|
|
11
11
|
options: IHttpConfig["https"] & DefaultOptionsService;
|
|
12
|
-
httpTerminator:
|
|
12
|
+
httpTerminator: IDrainTerminator | null;
|
|
13
13
|
module: Module;
|
|
14
14
|
server: https.Server | http2.Http2SecureServer | null;
|
|
15
15
|
port: number;
|
|
@@ -23,7 +23,7 @@ declare class ServerHttps extends Service {
|
|
|
23
23
|
active: boolean;
|
|
24
24
|
infos: AddressInfo | null;
|
|
25
25
|
constructor(module: Module, httpKernel: HttpKernel);
|
|
26
|
-
terminator():
|
|
26
|
+
terminator(): IDrainTerminator;
|
|
27
27
|
setPort(): number;
|
|
28
28
|
createServer(): Promise<https.Server | http2.Http2SecureServer>;
|
|
29
29
|
createServerH2(): Promise<http2.Http2SecureServer>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
2
|
import { Service, Module, FamilyType } from "nodefony";
|
|
3
3
|
import type { DefaultOptionsService } from "nodefony";
|
|
4
4
|
import type { IHttpConfig } from "../../config/config.js";
|
|
5
5
|
import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
|
|
6
6
|
import { AddressInfo } from "node:net";
|
|
7
|
-
import type { IncomingMessage } from "node:http";
|
|
8
7
|
import httpsServers from "./server-https.js";
|
|
9
8
|
declare class WebsocketSecure extends Service {
|
|
9
|
+
#private;
|
|
10
10
|
private readonly httpKernel;
|
|
11
11
|
options: IHttpConfig["websocketSecure"] & DefaultOptionsService;
|
|
12
12
|
module: Module;
|
|
@@ -25,7 +25,6 @@ declare class WebsocketSecure extends Service {
|
|
|
25
25
|
constructor(module: Module, httpKernel: HttpKernel);
|
|
26
26
|
setPort(): number;
|
|
27
27
|
createServer(serverHttps: httpsServers): Promise<WebSocketServer>;
|
|
28
|
-
onConnection(ws: Ws, req: IncomingMessage): void;
|
|
29
28
|
terminate(): Promise<boolean>;
|
|
30
29
|
showBanner(): void;
|
|
31
30
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
2
|
import { Service, Module, FamilyType } from "nodefony";
|
|
3
3
|
import type { DefaultOptionsService } from "nodefony";
|
|
4
4
|
import type { IHttpConfig } from "../../config/config.js";
|
|
5
5
|
import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
|
|
6
6
|
import { AddressInfo } from "node:net";
|
|
7
|
-
import type { IncomingMessage } from "node:http";
|
|
8
7
|
import httpServer from "./server-http.js";
|
|
9
8
|
declare class Websocket extends Service {
|
|
9
|
+
#private;
|
|
10
10
|
private readonly httpKernel;
|
|
11
11
|
options: IHttpConfig["websocket"] & DefaultOptionsService;
|
|
12
12
|
module: Module;
|
|
@@ -25,7 +25,6 @@ declare class Websocket extends Service {
|
|
|
25
25
|
constructor(module: Module, httpKernel: HttpKernel);
|
|
26
26
|
setPort(): number;
|
|
27
27
|
createServer(serverHttp: httpServer): Promise<WebSocketServer>;
|
|
28
|
-
onConnection(ws: Ws, req: IncomingMessage): void;
|
|
29
28
|
terminate(): Promise<boolean>;
|
|
30
29
|
showBanner(): void;
|
|
31
30
|
}
|
|
@@ -1,27 +1,41 @@
|
|
|
1
|
-
import { HttpTerminator } from "http-terminator";
|
|
2
1
|
import type { Server as HttpServer } from "node:http";
|
|
3
2
|
import type { Server as HttpsServer } from "node:https";
|
|
4
|
-
import type { Http2SecureServer } from "node:http2";
|
|
3
|
+
import type { Http2SecureServer, Http2Server } from "node:http2";
|
|
4
|
+
/** Serveur que `createDrainTerminator` sait drainer. */
|
|
5
|
+
export type DrainableServer = HttpServer | HttpsServer | Http2Server | Http2SecureServer;
|
|
6
|
+
/** Arrêt gracieux d'un serveur, rendu par `createDrainTerminator`. */
|
|
7
|
+
export interface IDrainTerminator {
|
|
8
|
+
/**
|
|
9
|
+
* Draine puis ferme le serveur ; idempotent (le deuxième appel rend la même promesse).
|
|
10
|
+
*
|
|
11
|
+
* @returns une promesse résolue quand toutes les connexions sont fermées
|
|
12
|
+
*/
|
|
13
|
+
terminate(): Promise<void>;
|
|
14
|
+
}
|
|
5
15
|
/**
|
|
6
|
-
* Fabrique
|
|
16
|
+
* Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
|
|
7
17
|
*
|
|
8
|
-
* Au `terminate()` (
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
18
|
+
* Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
|
|
19
|
+
* - **HTTP/2** : chaque session reçoit un GOAWAY (`session.close()`) — les flux
|
|
20
|
+
* en vol se terminent, les nouveaux sont refusés, et le client sait qu'il doit
|
|
21
|
+
* rouvrir ailleurs au lieu de constater une coupure ;
|
|
22
|
+
* - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
|
|
23
|
+
* inactive est fermée tout de suite, une requête qui arrive encore sur une
|
|
24
|
+
* socket gardée ouverte reçoit aussi `Connection: close`.
|
|
25
|
+
*
|
|
26
|
+
* Ce qui reste après `shutdownTimeout` ms est détruit. `server.close()` est
|
|
27
|
+
* appelé ici : ne pas le rappeler derrière.
|
|
13
28
|
*
|
|
14
29
|
* ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
|
|
15
|
-
* SANS frame Close
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* HTTP en `once`.
|
|
30
|
+
* SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
|
|
31
|
+
* AVANT ce drain. Garanti par l'ordre des listeners `onTerminate` : WS/WSS
|
|
32
|
+
* s'attachent en `prependOnceListener`, les serveurs HTTP en `once`.
|
|
19
33
|
*
|
|
20
|
-
* @param server - serveur Node à drainer (
|
|
34
|
+
* @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
|
|
35
|
+
* connexions commence ici)
|
|
21
36
|
* @param shutdownTimeout - délai de drain en ms avant destruction forcée
|
|
22
|
-
* @returns
|
|
37
|
+
* @returns l'arrêt à invoquer au shutdown
|
|
23
38
|
*/
|
|
24
|
-
export declare function createDrainTerminator(server:
|
|
39
|
+
export declare function createDrainTerminator(server: DrainableServer, shutdownTimeout?: number): IDrainTerminator;
|
|
25
40
|
/** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
|
|
26
41
|
export declare const DEFAULT_SHUTDOWN_TIMEOUT = 5000;
|
|
27
|
-
export type { HttpTerminator };
|