@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +13 -10
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +3 -3
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -2
- package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
- package/dist/nodefony/service/audit-logger.js +23 -30
- package/dist/nodefony/service/certificates.js +97 -13
- package/dist/nodefony/service/error-renderer.js +29 -12
- package/dist/nodefony/service/http-kernel.js +198 -50
- package/dist/nodefony/service/pretty-request-logger.js +15 -13
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +3 -3
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +3 -3
- package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
- package/dist/nodefony/service/servers/server-websocket.js +10 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
- package/dist/nodefony/service/sessions/sessions-service.js +7 -14
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +78 -43
- package/dist/nodefony/src/context/domainMatcher.js +1 -2
- package/dist/nodefony/src/context/forwarded.js +1 -2
- package/dist/nodefony/src/context/http/HttpContext.js +28 -6
- package/dist/nodefony/src/context/http/Request.js +33 -29
- package/dist/nodefony/src/context/http/Response.js +23 -6
- package/dist/nodefony/src/context/http/SseStream.js +1 -2
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +41 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/responseEnded.js +1 -2
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +33 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
- package/dist/nodefony/src/profiler/Profiler.js +104 -32
- package/dist/nodefony/src/profiler/aggregate.js +442 -0
- package/dist/nodefony/src/proxy/forward.js +29 -5
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +12 -6
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
- package/dist/nodefony/src/session/session.js +2 -3
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +9 -3
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/certificates.d.ts +55 -4
- package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/src/context/Context.d.ts +38 -5
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
- package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
- package/docs/cookies.md +85 -78
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +6 -1
- package/docs/observabilite.md +159 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +72 -70
- package/docs/servers.md +338 -224
- package/docs/session.md +179 -179
- package/docs/sse.md +24 -20
- package/docs/upload.md +120 -117
- package/package.json +8 -9
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { handshakeRefusalStatus } from "../src/context/websocket/WebsocketContext.js";
|
|
2
|
+
import { severityFromStatus } from "./audit-logger.js";
|
|
1
3
|
import { logColor } from "nodefony";
|
|
2
|
-
|
|
4
|
+
// nodefony/service/request-logger.ts
|
|
5
|
+
/**
|
|
6
|
+
* Statut de la réponse : le contrat ne type `response` qu'en `object` ; une
|
|
7
|
+
* annotation (vérifiée, pas une assertion) le lit — le reste passe par le contrat, que le
|
|
8
|
+
* compilateur surveille.
|
|
9
|
+
*/
|
|
10
|
+
const statusOf = (context) => {
|
|
11
|
+
return context.response?.statusCode;
|
|
12
|
+
};
|
|
3
13
|
/**
|
|
4
14
|
* Default Nodefony per-request logger — preserves the legacy colored format.
|
|
5
15
|
*
|
|
@@ -13,14 +23,13 @@ import { logColor } from "nodefony";
|
|
|
13
23
|
*/
|
|
14
24
|
var DefaultRequestLogger = class {
|
|
15
25
|
renderHttp(context, error) {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const err = error ?? ctx.error;
|
|
26
|
+
const txt = `${logColor.cyan("URL")} : ${context.url} ${logColor.cyan("FROM")} : ${context.remoteAddress} ${logColor.cyan("ORIGIN")} : ${context.originUrl?.host} ${logColor.cyan("ID")} : ${context.requestId}`;
|
|
27
|
+
const err = error ?? context.error;
|
|
19
28
|
if (err) {
|
|
20
|
-
const errCode = err.code ??
|
|
21
|
-
const msgid = `${
|
|
29
|
+
const errCode = err.code ?? statusOf(context) ?? 500;
|
|
30
|
+
const msgid = `${context.serverType} ${logColor.magenta(errCode)} ${logColor.red(context.method ?? "")}`;
|
|
22
31
|
return {
|
|
23
|
-
text:
|
|
32
|
+
text: context.kernel?.environment === "production" ? `${txt} ${err}` : `${txt}\n ${err}`,
|
|
24
33
|
severity: "ERROR",
|
|
25
34
|
msgid
|
|
26
35
|
};
|
|
@@ -28,27 +37,26 @@ var DefaultRequestLogger = class {
|
|
|
28
37
|
return {
|
|
29
38
|
text: txt,
|
|
30
39
|
severity: "INFO",
|
|
31
|
-
msgid: `${
|
|
40
|
+
msgid: `${context.serverType} ${logColor.magenta(statusOf(context) ?? "")} ${context.method}`
|
|
32
41
|
};
|
|
33
42
|
}
|
|
34
43
|
renderWebsocket(context, error, acceptedProtocol) {
|
|
35
|
-
const ctx = context;
|
|
36
44
|
if (error) {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
45
|
+
const refused = handshakeRefusalStatus(context, error);
|
|
46
|
+
const errCode = refused ?? error.code ?? statusOf(context) ?? 500;
|
|
47
|
+
const msgid = `${context.serverType} ${logColor.magenta(errCode)} ${logColor.red(context.method ?? "")}`;
|
|
39
48
|
return {
|
|
40
|
-
text: `${logColor.cyan("URL")} : ${
|
|
41
|
-
severity: "ERROR",
|
|
49
|
+
text: `${logColor.cyan("URL")} : ${context.url} ${logColor.cyan("FROM")} : ${context.remoteAddress} ${logColor.cyan("ORIGIN")} : ${context.originUrl?.host} ${logColor.cyan("ID")} : ${context.requestId}\n ` + error.toString(),
|
|
50
|
+
severity: refused !== null ? severityFromStatus(refused) : "ERROR",
|
|
42
51
|
msgid
|
|
43
52
|
};
|
|
44
53
|
}
|
|
45
|
-
const msgid = `${
|
|
54
|
+
const msgid = `${context.serverType} ${logColor.magenta(statusOf(context) ?? "")} ${context.method}`;
|
|
46
55
|
return {
|
|
47
|
-
text: `${logColor.cyan("URL")} : ${
|
|
56
|
+
text: `${logColor.cyan("URL")} : ${context.url} ${logColor.cyan("Accept-Protocol")} : ${acceptedProtocol || "*"} ${logColor.cyan("FROM")} : ${context.remoteAddress} ${logColor.cyan("ORIGIN")} : ${context.originUrl?.host} ${logColor.cyan("ID")} : ${context.requestId}`,
|
|
48
57
|
severity: "INFO",
|
|
49
58
|
msgid
|
|
50
59
|
};
|
|
51
60
|
}
|
|
52
61
|
};
|
|
53
|
-
//#endregion
|
|
54
62
|
export { DefaultRequestLogger as default };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { isAmbiguousPath, normalizePrefix, parseProxyOrigin, proxyMountProblems, websocketHandshakeProblem } from "../src/proxy/rules.js";
|
|
2
|
+
import { requestAuthority } from "../src/context/transport.js";
|
|
2
3
|
import { forwardRequest, forwardUpgrade, refuseUpgrade } from "../src/proxy/forward.js";
|
|
3
4
|
import { Service } from "nodefony";
|
|
4
5
|
import http from "node:http";
|
|
5
6
|
import { randomBytes } from "node:crypto";
|
|
6
7
|
import https from "node:https";
|
|
7
8
|
import { TLSSocket } from "node:tls";
|
|
8
|
-
|
|
9
|
+
// nodefony/service/reverse-proxy.ts
|
|
9
10
|
/** Replis si la section `proxy` manque (module construit hors configuration). */
|
|
10
11
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
11
12
|
const DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
|
|
@@ -240,7 +241,7 @@ var ReverseProxy = class extends Service {
|
|
|
240
241
|
const target = this.targetOf(mount);
|
|
241
242
|
if (target === void 0) return answerPlain(res, 503, "Service Unavailable", "1");
|
|
242
243
|
if (isAmbiguousPath(req.url ?? "/")) return answerPlain(res, 400, "Bad Request");
|
|
243
|
-
const host = req.headers
|
|
244
|
+
const host = requestAuthority(req.headers);
|
|
244
245
|
return forwardRequest(req, res, mount, target, this.fromOf(req, scheme, host), this.relayFor(mount, target));
|
|
245
246
|
}
|
|
246
247
|
/**
|
|
@@ -332,5 +333,4 @@ function hostnameOf(host) {
|
|
|
332
333
|
return null;
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
|
-
//#endregion
|
|
336
336
|
export { ReverseProxy as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { isPeerGoneError } from "../../src/context/websocket/Response.js";
|
|
2
|
+
// nodefony/service/servers/clientError.ts
|
|
2
3
|
/**
|
|
3
4
|
* Gère l'event `clientError` d'un `http.Server` / `https.Server`.
|
|
4
5
|
*
|
|
@@ -16,5 +17,23 @@ function handleClientError(error, socket) {
|
|
|
16
17
|
const statusLine = error.code === "HPE_HEADER_OVERFLOW" ? "431 Request Header Fields Too Large" : "400 Bad Request";
|
|
17
18
|
socket.end(`HTTP/1.1 ${statusLine}\r\nConnection: close\r\n\r\n`);
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Gère l'event `sessionError` d'un serveur HTTP/2 (h2c ou TLS) : un pair PARTI
|
|
22
|
+
* se tait, toute autre erreur est rapportée.
|
|
23
|
+
*
|
|
24
|
+
* Une session réinitialisée par le pair n'a rien à diagnostiquer. C'est la
|
|
25
|
+
* signature d'une sonde de port — `nodefony status`, `doctor`, le superviseur
|
|
26
|
+
* de développement, la sonde `tcpSocket` d'un orchestrateur — qui se connecte
|
|
27
|
+
* puis ferme sans lire le `SETTINGS` que le serveur envoie dès l'ouverture : le
|
|
28
|
+
* système ferme alors par un RST plutôt qu'un FIN, et Node lève `ECONNRESET`.
|
|
29
|
+
* La rapporter imprimait une pile entière par sonde. La règle « client parti »
|
|
30
|
+
* est celle des frames WebSocket ({@link isPeerGoneError}), pas une copie.
|
|
31
|
+
*
|
|
32
|
+
* @param error - erreur de session émise par `node:http2`
|
|
33
|
+
* @param report - rapporte une erreur réelle, à la sévérité choisie par le serveur
|
|
34
|
+
*/
|
|
35
|
+
function handleSessionError(error, report) {
|
|
36
|
+
if (isPeerGoneError(error)) return;
|
|
37
|
+
report(error);
|
|
38
|
+
}
|
|
39
|
+
export { handleClientError, handleSessionError };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { asServersConfig, configuredServerPort, isH2cEnabled } from "../../src/servers/kernelServers.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
|
+
import { handleSessionError } from "./clientError.js";
|
|
4
|
+
import { createDrainTerminator } from "./serverShutdown.js";
|
|
5
|
+
import { bindWithFallback, buildBindPlan, h2cPortConflict } from "../../src/servers/portBinder.js";
|
|
6
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
|
+
import { http2ServerLimits, setHttp2SessionTimeout } from "../../src/servers/http2Limits.js";
|
|
8
|
+
import { BootConfigurationError, Inject, Service, nodefonyError } from "nodefony";
|
|
9
|
+
import http2 from "node:http2";
|
|
10
|
+
// nodefony/service/servers/server-h2c.ts
|
|
11
|
+
/**
|
|
12
|
+
* Serveur HTTP/2 en clair (h2c, connaissance préalable — RFC 9113 §3.3), sur un
|
|
13
|
+
* port DÉDIÉ (`servers.h2c`, 5153 par défaut), pour un proxy inverse qui parle
|
|
14
|
+
* HTTP/2 à ses amonts.
|
|
15
|
+
*
|
|
16
|
+
* Il partage le chemin HTTP/2 du 5152 — même `onHttpRequest`, mêmes limites
|
|
17
|
+
* anti-DoS (`http2ServerLimits`), même drain par GOAWAY — sans TLS : `serverType`
|
|
18
|
+
* vaut `"http"`, le schéma vient du transport ou d'un proxy de confiance, jamais
|
|
19
|
+
* du pseudo-en-tête `:scheme`. Le protocole du saut (`"h2c"`) se constate sur la
|
|
20
|
+
* requête (`transport.ts#alpnProtocolOf`).
|
|
21
|
+
*
|
|
22
|
+
* Hors de son périmètre, délibérément : HTTP/1.1 (refusé par la préface HTTP/2 —
|
|
23
|
+
* sondes et clients HTTP/1.1 restent sur le port HTTP), WebSocket (aucun CONNECT
|
|
24
|
+
* étendu, RFC 8441 : un proxy route `Upgrade: websocket` vers le port HTTP) et la
|
|
25
|
+
* bascule `Upgrade: h2c` (retirée par RFC 9113 §3.1).
|
|
26
|
+
*/
|
|
27
|
+
let ServerH2c = class ServerH2c extends Service {
|
|
28
|
+
httpKernel;
|
|
29
|
+
module;
|
|
30
|
+
server = null;
|
|
31
|
+
httpTerminator = null;
|
|
32
|
+
port;
|
|
33
|
+
protocol = "2.0";
|
|
34
|
+
ready = false;
|
|
35
|
+
type = "http";
|
|
36
|
+
domain;
|
|
37
|
+
scheme = "http";
|
|
38
|
+
address = null;
|
|
39
|
+
family = null;
|
|
40
|
+
active = false;
|
|
41
|
+
infos = null;
|
|
42
|
+
constructor(module, httpKernel) {
|
|
43
|
+
super("server-h2c", module.container ?? void 0, module.notificationsCenter, module.config.http);
|
|
44
|
+
this.httpKernel = httpKernel;
|
|
45
|
+
this.module = module;
|
|
46
|
+
this.active = isH2cEnabled(asServersConfig(module.kernel?.options.servers)?.h2c);
|
|
47
|
+
this.port = configuredServerPort(asServersConfig(module.kernel?.options.servers), "h2c") ?? 0;
|
|
48
|
+
this.domain = this.module.kernel?.domain ?? null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Crée et met en écoute le serveur h2c.
|
|
52
|
+
*
|
|
53
|
+
* @returns le serveur à l'écoute
|
|
54
|
+
* @throws BootConfigurationError si `servers.h2c.port` est celui du serveur
|
|
55
|
+
* HTTP ou HTTPS — un port partagé n'est pas un conflit à contourner
|
|
56
|
+
*/
|
|
57
|
+
createServer() {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
try {
|
|
60
|
+
if (!this.active) return reject(/* @__PURE__ */ new Error(`Server h2c is not allowed in config file`));
|
|
61
|
+
const domain = this.domain;
|
|
62
|
+
if (domain === null) return reject(/* @__PURE__ */ new Error(`Server h2c : aucun domaine d'écoute (kernel absent)`));
|
|
63
|
+
const servers = asServersConfig(this.module.kernel?.options.servers);
|
|
64
|
+
const conflict = h2cPortConflict(servers);
|
|
65
|
+
if (conflict !== null) return reject(new BootConfigurationError(`servers.h2c.port (${this.port}) est aussi celui de servers.${conflict}.port : HTTP/1.1 et h2c ne partagent pas un port. Choisir un port distinct pour servers.h2c.port.`));
|
|
66
|
+
if (!servers?.http) this.log("servers.h2c actif sans servers.http : les sondes de santé et les WebSocket (HTTP/1.1) n'ont plus de port où arriver.", "WARNING");
|
|
67
|
+
const server = http2.createServer(http2ServerLimits(this.module.config.http2));
|
|
68
|
+
this.server = server;
|
|
69
|
+
this.httpTerminator = createDrainTerminator(server, this.options.shutdownTimeout);
|
|
70
|
+
setHttp2SessionTimeout(server, this.options.timeout, () => {
|
|
71
|
+
this.fire("onTimeout", this);
|
|
72
|
+
});
|
|
73
|
+
server.on("request", (request, response) => {
|
|
74
|
+
this.httpKernel.onHttpRequest(request, response, this.type);
|
|
75
|
+
});
|
|
76
|
+
server.on("sessionError", (error) => {
|
|
77
|
+
handleSessionError(error, (e) => this.log(e, "DEBUG", "H2C sessionError"));
|
|
78
|
+
});
|
|
79
|
+
server.on("streamError", (error) => {
|
|
80
|
+
this.log(error, "ERROR", "H2C streamError");
|
|
81
|
+
});
|
|
82
|
+
this.module.fire("onCreateServer", this.type, this);
|
|
83
|
+
bindWithFallback(server, domain, buildBindPlan("h2c", servers, this.module.kernel?.environment)).then(({ address, shiftedFrom }) => {
|
|
84
|
+
this.infos = address;
|
|
85
|
+
this.port = address.port;
|
|
86
|
+
this.address = address.address;
|
|
87
|
+
this.family = familyOf(address);
|
|
88
|
+
if (shiftedFrom !== null) this.log(`Port ${shiftedFrom} déjà occupé → h2c écoute sur ${this.port}. Figer le port : servers.h2c.port ; échouer au lieu de glisser : servers.portPolicy = "strict".`, "WARNING");
|
|
89
|
+
this.ready = true;
|
|
90
|
+
this.module.fire("onServersReady", this.type, this);
|
|
91
|
+
server.on("error", (error) => {
|
|
92
|
+
this.log(new nodefonyError(error), "CRITIC");
|
|
93
|
+
});
|
|
94
|
+
resolve(server);
|
|
95
|
+
}).catch((e) => {
|
|
96
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
97
|
+
this.log(new nodefonyError(error), "CRITIC");
|
|
98
|
+
server.close();
|
|
99
|
+
reject(error);
|
|
100
|
+
});
|
|
101
|
+
this.kernel?.once("onTerminate", async () => {
|
|
102
|
+
if (!this.server) return;
|
|
103
|
+
try {
|
|
104
|
+
await this.httpTerminator?.terminate();
|
|
105
|
+
this.log(`h2c SHUTDOWN PORT : ${this.port}`, "INFO");
|
|
106
|
+
} catch (e) {
|
|
107
|
+
this.log(e, "ERROR", "TERMINATE");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
} catch (e) {
|
|
111
|
+
this.log(e, "CRITIC");
|
|
112
|
+
return reject(e instanceof Error ? e : new Error(String(e)));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
showBanner() {
|
|
117
|
+
if (this.infos) this.log(`Server Listen on h2c://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
ServerH2c = __decorate([__decorateParam(1, Inject("HttpKernel"))], ServerH2c);
|
|
121
|
+
/** Famille d'une adresse d'écoute, lue sans conversion (`AddressInfo.family` est une chaîne). */
|
|
122
|
+
function familyOf(address) {
|
|
123
|
+
return address.family === "IPv6" ? "IPv6" : "IPv4";
|
|
124
|
+
}
|
|
125
|
+
var server_h2c_default = ServerH2c;
|
|
126
|
+
export { server_h2c_default as default };
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import
|
|
3
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
|
|
4
|
-
import http_kernel_default from "../http-kernel.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
5
3
|
import { handleClientError } from "./clientError.js";
|
|
6
4
|
import { createDrainTerminator } from "./serverShutdown.js";
|
|
7
5
|
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
8
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.
|
|
9
|
-
import {
|
|
6
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
|
+
import { Inject, Service, extend, nodefonyError } from "nodefony";
|
|
10
8
|
import http from "node:http";
|
|
11
|
-
|
|
9
|
+
// nodefony/service/servers/server-http.ts
|
|
12
10
|
let ServerHttp = class ServerHttp extends Service {
|
|
13
11
|
httpKernel;
|
|
14
12
|
module;
|
|
@@ -129,7 +127,6 @@ let ServerHttp = class ServerHttp extends Service {
|
|
|
129
127
|
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
130
128
|
}
|
|
131
129
|
};
|
|
132
|
-
ServerHttp = __decorate([__decorateParam(1,
|
|
130
|
+
ServerHttp = __decorate([__decorateParam(1, Inject("HttpKernel"))], ServerHttp);
|
|
133
131
|
var server_http_default = ServerHttp;
|
|
134
|
-
//#endregion
|
|
135
132
|
export { server_http_default as default };
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import http_kernel_default from "../http-kernel.js";
|
|
5
|
-
import { handleClientError } from "./clientError.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
|
+
import { handleClientError, handleSessionError } from "./clientError.js";
|
|
6
4
|
import { createDrainTerminator } from "./serverShutdown.js";
|
|
7
5
|
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
8
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.
|
|
9
|
-
import {
|
|
6
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
|
+
import { http2ServerLimits, setHttp2SessionTimeout } from "../../src/servers/http2Limits.js";
|
|
8
|
+
import { Inject, Service, extend, nodefonyError } from "nodefony";
|
|
10
9
|
import http2 from "node:http2";
|
|
11
10
|
import https from "node:https";
|
|
12
|
-
|
|
13
|
-
//#region nodefony/service/servers/server-https.ts
|
|
11
|
+
// nodefony/service/servers/server-https.ts
|
|
14
12
|
let ServerHttps = class ServerHttps extends Service {
|
|
15
13
|
httpKernel;
|
|
16
14
|
httpTerminator = null;
|
|
@@ -98,7 +96,6 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
98
96
|
createServerH2() {
|
|
99
97
|
return new Promise((resolve, reject) => {
|
|
100
98
|
try {
|
|
101
|
-
const h2Cfg = this.module.options.http2 ?? {};
|
|
102
99
|
const opt = extend({
|
|
103
100
|
allowHTTP1: true,
|
|
104
101
|
rejectUnauthorized: this.options.rejectUnauthorized,
|
|
@@ -106,23 +103,14 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
106
103
|
cert: this.httpKernel.serviceCerticats?.cert,
|
|
107
104
|
ca: this.httpKernel.serviceCerticats?.ca ? this.httpKernel.serviceCerticats.ca : void 0
|
|
108
105
|
});
|
|
109
|
-
|
|
110
|
-
if (h2Cfg.maxConcurrentStreams) opt.settings = {
|
|
111
|
-
...opt.settings,
|
|
112
|
-
maxConcurrentStreams: h2Cfg.maxConcurrentStreams
|
|
113
|
-
};
|
|
106
|
+
Object.assign(opt, http2ServerLimits(this.module.options.http2));
|
|
114
107
|
this.server = http2.createSecureServer(opt);
|
|
115
108
|
this.httpTerminator = this.terminator();
|
|
116
|
-
|
|
109
|
+
setHttp2SessionTimeout(this.server, this.options.timeout, () => {
|
|
117
110
|
this.fire("onTimeout", this);
|
|
118
111
|
});
|
|
119
112
|
this.server.on("request", (request, response) => {
|
|
120
|
-
|
|
121
|
-
const sessionSocket = request.stream?.session?.socket;
|
|
122
|
-
if (sessionSocket instanceof TLSSocket) alpnProtocol = sessionSocket.alpnProtocol;
|
|
123
|
-
if (request.socket instanceof TLSSocket) alpnProtocol = request.socket.alpnProtocol;
|
|
124
|
-
if (alpnProtocol === "h2") this.httpKernel.onHttpRequest(request, response, "http2");
|
|
125
|
-
else this.httpKernel.onHttpRequest(request, response, "https");
|
|
113
|
+
this.httpKernel.onHttpRequest(request, response, this.type);
|
|
126
114
|
});
|
|
127
115
|
this.listenWithPolicy().then(() => resolve(this.server)).catch((e) => {
|
|
128
116
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
@@ -139,7 +127,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
139
127
|
}
|
|
140
128
|
});
|
|
141
129
|
this.server.on("sessionError", (error) => {
|
|
142
|
-
this.log(
|
|
130
|
+
handleSessionError(error, (e) => this.log(e, "ERROR", "HTTP2 Server sessionError"));
|
|
143
131
|
});
|
|
144
132
|
this.server.on("streamError", (error) => {
|
|
145
133
|
this.log(error, "ERROR", "HTTP2 Server streamError");
|
|
@@ -157,6 +145,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
157
145
|
* `EADDRINUSE` de repli, qui ne sont pas des pannes).
|
|
158
146
|
*/
|
|
159
147
|
async listenWithPolicy() {
|
|
148
|
+
if (this.server === null) throw new Error("Server https not created");
|
|
160
149
|
const { address, shiftedFrom } = await bindWithFallback(this.server, this.domain, buildBindPlan("https", asServersConfig(this.module.kernel?.options.servers), this.module.kernel?.environment));
|
|
161
150
|
this.infos = address;
|
|
162
151
|
this.port = address.port;
|
|
@@ -197,7 +186,6 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
197
186
|
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
198
187
|
}
|
|
199
188
|
};
|
|
200
|
-
ServerHttps = __decorate([__decorateParam(1,
|
|
189
|
+
ServerHttps = __decorate([__decorateParam(1, Inject("HttpKernel"))], ServerHttps);
|
|
201
190
|
var server_https_default = ServerHttps;
|
|
202
|
-
//#endregion
|
|
203
191
|
export { server_https_default as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizePrefix } from "../../src/proxy/rules.js";
|
|
2
|
+
import { requestAuthority } from "../../src/context/transport.js";
|
|
2
3
|
import { Service, extend } from "nodefony";
|
|
3
4
|
import http from "node:http";
|
|
4
5
|
import http2 from "node:http2";
|
|
@@ -8,7 +9,7 @@ import { join } from "node:path";
|
|
|
8
9
|
import mime from "mime-types";
|
|
9
10
|
import tls from "node:tls";
|
|
10
11
|
import serveStatic from "serve-static";
|
|
11
|
-
|
|
12
|
+
// nodefony/service/servers/server-static.ts
|
|
12
13
|
const defaultOptions = {
|
|
13
14
|
cacheControl: true,
|
|
14
15
|
maxAge: 345600
|
|
@@ -148,7 +149,7 @@ var Statics = class extends Service {
|
|
|
148
149
|
return scheme + "://" + host;
|
|
149
150
|
} else if (request instanceof http2.Http2ServerRequest) {
|
|
150
151
|
scheme = request.socket instanceof tls.TLSSocket ? "https" : "http";
|
|
151
|
-
host = request.headers
|
|
152
|
+
host = requestAuthority(request.headers);
|
|
152
153
|
return scheme + "://" + host;
|
|
153
154
|
}
|
|
154
155
|
throw new Error(`Bad request type`);
|
|
@@ -181,5 +182,4 @@ var Statics = class extends Service {
|
|
|
181
182
|
return Promise.resolve(response);
|
|
182
183
|
}
|
|
183
184
|
};
|
|
184
|
-
//#endregion
|
|
185
185
|
export { Statics as default };
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
1
2
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import http_kernel_default from "../http-kernel.js";
|
|
5
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.js";
|
|
6
|
-
import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
|
|
3
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
5
|
import { attachUpgradeDispatch } from "../../src/servers/upgradeDispatch.js";
|
|
8
|
-
import {
|
|
6
|
+
import { Inject, Service } from "nodefony";
|
|
9
7
|
import Ws, { WebSocketServer } from "ws";
|
|
10
|
-
|
|
8
|
+
// nodefony/service/servers/server-websocket-secure.ts
|
|
11
9
|
let WebsocketSecure = class WebsocketSecure extends Service {
|
|
12
10
|
httpKernel;
|
|
13
11
|
module;
|
|
@@ -21,7 +19,7 @@ let WebsocketSecure = class WebsocketSecure extends Service {
|
|
|
21
19
|
family = null;
|
|
22
20
|
scheme = "wss";
|
|
23
21
|
address = null;
|
|
24
|
-
type = "
|
|
22
|
+
type = "wss";
|
|
25
23
|
infos = null;
|
|
26
24
|
/** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
|
|
27
25
|
heartbeatTimer = null;
|
|
@@ -52,8 +50,9 @@ let WebsocketSecure = class WebsocketSecure extends Service {
|
|
|
52
50
|
noServer: true,
|
|
53
51
|
clientTracking: true
|
|
54
52
|
});
|
|
55
|
-
this.#detachUpgrade = attachUpgradeDispatch(serverHttps.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req))
|
|
56
|
-
|
|
53
|
+
this.#detachUpgrade = attachUpgradeDispatch(serverHttps.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
|
|
54
|
+
this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
|
|
55
|
+
});
|
|
57
56
|
this.heartbeatTimer = startHeartbeat(this.server, this.options);
|
|
58
57
|
this.kernel?.prependOnceListener("onTerminate", this.terminate.bind(this));
|
|
59
58
|
this.ready = true;
|
|
@@ -65,12 +64,6 @@ let WebsocketSecure = class WebsocketSecure extends Service {
|
|
|
65
64
|
}
|
|
66
65
|
});
|
|
67
66
|
}
|
|
68
|
-
onConnection(ws, req) {
|
|
69
|
-
trackPong(ws);
|
|
70
|
-
this.httpKernel.onWebsocketRequest(ws, req, this.type).catch(() => {
|
|
71
|
-
process.nextTick(() => {});
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
67
|
terminate() {
|
|
75
68
|
return new Promise((resolve, reject) => {
|
|
76
69
|
if (this.heartbeatTimer) {
|
|
@@ -107,7 +100,6 @@ let WebsocketSecure = class WebsocketSecure extends Service {
|
|
|
107
100
|
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
108
101
|
}
|
|
109
102
|
};
|
|
110
|
-
WebsocketSecure = __decorate([__decorateParam(1,
|
|
103
|
+
WebsocketSecure = __decorate([__decorateParam(1, Inject("HttpKernel"))], WebsocketSecure);
|
|
111
104
|
var server_websocket_secure_default = WebsocketSecure;
|
|
112
|
-
//#endregion
|
|
113
105
|
export { server_websocket_secure_default as default };
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
1
2
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import http_kernel_default from "../http-kernel.js";
|
|
5
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.js";
|
|
6
|
-
import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
|
|
3
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
5
|
import { attachUpgradeDispatch } from "../../src/servers/upgradeDispatch.js";
|
|
8
|
-
import {
|
|
6
|
+
import { Inject, Service } from "nodefony";
|
|
9
7
|
import Ws, { WebSocketServer } from "ws";
|
|
10
|
-
|
|
8
|
+
// nodefony/service/servers/server-websocket.ts
|
|
11
9
|
let Websocket = class Websocket extends Service {
|
|
12
10
|
httpKernel;
|
|
13
11
|
module;
|
|
@@ -21,7 +19,7 @@ let Websocket = class Websocket extends Service {
|
|
|
21
19
|
family = null;
|
|
22
20
|
scheme = "ws";
|
|
23
21
|
address = null;
|
|
24
|
-
type = "
|
|
22
|
+
type = "ws";
|
|
25
23
|
infos = null;
|
|
26
24
|
/** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
|
|
27
25
|
heartbeatTimer = null;
|
|
@@ -52,8 +50,9 @@ let Websocket = class Websocket extends Service {
|
|
|
52
50
|
noServer: true,
|
|
53
51
|
clientTracking: true
|
|
54
52
|
});
|
|
55
|
-
this.#detachUpgrade = attachUpgradeDispatch(serverHttp.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req))
|
|
56
|
-
|
|
53
|
+
this.#detachUpgrade = attachUpgradeDispatch(serverHttp.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
|
|
54
|
+
this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
|
|
55
|
+
});
|
|
57
56
|
this.heartbeatTimer = startHeartbeat(this.server, this.options);
|
|
58
57
|
this.kernel?.prependOnceListener("onTerminate", this.terminate.bind(this));
|
|
59
58
|
this.ready = true;
|
|
@@ -65,12 +64,6 @@ let Websocket = class Websocket extends Service {
|
|
|
65
64
|
}
|
|
66
65
|
});
|
|
67
66
|
}
|
|
68
|
-
onConnection(ws, req) {
|
|
69
|
-
trackPong(ws);
|
|
70
|
-
this.httpKernel.onWebsocketRequest(ws, req, this.type).catch(() => {
|
|
71
|
-
process.nextTick(() => {});
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
67
|
terminate() {
|
|
75
68
|
return new Promise((resolve, reject) => {
|
|
76
69
|
if (this.heartbeatTimer) {
|
|
@@ -107,7 +100,6 @@ let Websocket = class Websocket extends Service {
|
|
|
107
100
|
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
108
101
|
}
|
|
109
102
|
};
|
|
110
|
-
Websocket = __decorate([__decorateParam(1,
|
|
103
|
+
Websocket = __decorate([__decorateParam(1, Inject("HttpKernel"))], Websocket);
|
|
111
104
|
var server_websocket_default = Websocket;
|
|
112
|
-
//#endregion
|
|
113
105
|
export { server_websocket_default as default };
|
|
@@ -1,31 +1,91 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import https from "node:https";
|
|
3
|
+
import tls from "node:tls";
|
|
4
|
+
// nodefony/service/servers/serverShutdown.ts
|
|
3
5
|
/**
|
|
4
|
-
* Fabrique
|
|
6
|
+
* Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
|
|
5
7
|
*
|
|
6
|
-
* Au `terminate()` (
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
|
|
9
|
+
* - **HTTP/2** : chaque session reçoit un GOAWAY (`session.close()`) — les flux
|
|
10
|
+
* en vol se terminent, les nouveaux sont refusés, et le client sait qu'il doit
|
|
11
|
+
* rouvrir ailleurs au lieu de constater une coupure ;
|
|
12
|
+
* - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
|
|
13
|
+
* inactive est fermée tout de suite, une requête qui arrive encore sur une
|
|
14
|
+
* socket gardée ouverte reçoit aussi `Connection: close`.
|
|
15
|
+
*
|
|
16
|
+
* Ce qui reste après `shutdownTimeout` ms est détruit. `server.close()` est
|
|
17
|
+
* appelé ici : ne pas le rappeler derrière.
|
|
11
18
|
*
|
|
12
19
|
* ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
|
|
13
|
-
* SANS frame Close
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* HTTP en `once`.
|
|
20
|
+
* SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
|
|
21
|
+
* AVANT ce drain. Garanti par l'ordre des listeners `onTerminate` : WS/WSS
|
|
22
|
+
* s'attachent en `prependOnceListener`, les serveurs HTTP en `once`.
|
|
17
23
|
*
|
|
18
|
-
* @param server - serveur Node à drainer (
|
|
24
|
+
* @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
|
|
25
|
+
* connexions commence ici)
|
|
19
26
|
* @param shutdownTimeout - délai de drain en ms avant destruction forcée
|
|
20
|
-
* @returns
|
|
27
|
+
* @returns l'arrêt à invoquer au shutdown
|
|
21
28
|
*/
|
|
22
29
|
function createDrainTerminator(server, shutdownTimeout) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
const timeout = shutdownTimeout ?? 5e3;
|
|
31
|
+
const emitter = server;
|
|
32
|
+
const speaksHttp2 = !(server instanceof http.Server || server instanceof https.Server);
|
|
33
|
+
const overTls = server instanceof tls.Server;
|
|
34
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
35
|
+
emitter.on("connection", (socket) => track(sockets, socket));
|
|
36
|
+
let http1Sockets = null;
|
|
37
|
+
if (!speaksHttp2) http1Sockets = overTls ? /* @__PURE__ */ new Set() : sockets;
|
|
38
|
+
else if (overTls) http1Sockets = /* @__PURE__ */ new Set();
|
|
39
|
+
if (overTls && http1Sockets !== null) {
|
|
40
|
+
const set = http1Sockets;
|
|
41
|
+
emitter.on("secureConnection", (socket) => {
|
|
42
|
+
if (socket.alpnProtocol !== "h2") track(set, socket);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
let draining = false;
|
|
46
|
+
const sessions = speaksHttp2 ? /* @__PURE__ */ new Set() : null;
|
|
47
|
+
if (sessions !== null) emitter.on("session", (session) => {
|
|
48
|
+
if (draining) {
|
|
49
|
+
session.close();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
sessions.add(session);
|
|
53
|
+
session.once("close", () => sessions.delete(session));
|
|
54
|
+
});
|
|
55
|
+
let terminating = null;
|
|
56
|
+
const drain = () => new Promise((resolve) => {
|
|
57
|
+
draining = true;
|
|
58
|
+
const deadline = setTimeout(() => {
|
|
59
|
+
if (sessions !== null) for (const s of sessions) s.destroy();
|
|
60
|
+
for (const socket of sockets) socket.destroy();
|
|
61
|
+
}, timeout);
|
|
62
|
+
server.close(() => {
|
|
63
|
+
clearTimeout(deadline);
|
|
64
|
+
resolve();
|
|
65
|
+
});
|
|
66
|
+
if (sessions !== null) for (const s of sessions) s.close();
|
|
67
|
+
if (http1Sockets !== null) {
|
|
68
|
+
emitter.on("request", closeAfterResponse);
|
|
69
|
+
for (const socket of http1Sockets) {
|
|
70
|
+
const response = socket._httpMessage;
|
|
71
|
+
if (!response) socket.destroy();
|
|
72
|
+
else if (!response.headersSent) response.setHeader("connection", "close");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
26
75
|
});
|
|
76
|
+
return { terminate() {
|
|
77
|
+
terminating ??= drain();
|
|
78
|
+
return terminating;
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
function track(set, socket) {
|
|
82
|
+
set.add(socket);
|
|
83
|
+
socket.once("close", () => set.delete(socket));
|
|
84
|
+
}
|
|
85
|
+
/** Requête HTTP/1.1 reçue pendant le drain : la connexion ne survivra pas à sa réponse. */
|
|
86
|
+
function closeAfterResponse(request, response) {
|
|
87
|
+
if (request.httpVersionMajor === 1 && !response.headersSent) response.setHeader("connection", "close");
|
|
27
88
|
}
|
|
28
89
|
/** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
|
|
29
90
|
const DEFAULT_SHUTDOWN_TIMEOUT = 5e3;
|
|
30
|
-
//#endregion
|
|
31
91
|
export { DEFAULT_SHUTDOWN_TIMEOUT, createDrainTerminator };
|