@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.
Files changed (138) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +13 -10
  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 +3 -3
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  12. package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
  13. package/dist/nodefony/service/audit-logger.js +23 -30
  14. package/dist/nodefony/service/certificates.js +97 -13
  15. package/dist/nodefony/service/error-renderer.js +29 -12
  16. package/dist/nodefony/service/http-kernel.js +198 -50
  17. package/dist/nodefony/service/pretty-request-logger.js +15 -13
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +3 -3
  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 +3 -3
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  29. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  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 +78 -43
  36. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  37. package/dist/nodefony/src/context/forwarded.js +1 -2
  38. package/dist/nodefony/src/context/http/HttpContext.js +28 -6
  39. package/dist/nodefony/src/context/http/Request.js +33 -29
  40. package/dist/nodefony/src/context/http/Response.js +23 -6
  41. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  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 +41 -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/requestTrace.js +104 -0
  49. package/dist/nodefony/src/context/responseEnded.js +1 -2
  50. package/dist/nodefony/src/context/timingMode.js +21 -0
  51. package/dist/nodefony/src/context/transport.js +45 -0
  52. package/dist/nodefony/src/context/trustProxy.js +1 -2
  53. package/dist/nodefony/src/context/unsentResponse.js +30 -0
  54. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  55. package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
  56. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  57. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  58. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  59. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  60. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  61. package/dist/nodefony/src/cookies/cookie.js +3 -7
  62. package/dist/nodefony/src/errors/httpError.js +1 -2
  63. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  64. package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
  65. package/dist/nodefony/src/profiler/Profiler.js +104 -32
  66. package/dist/nodefony/src/profiler/aggregate.js +442 -0
  67. package/dist/nodefony/src/proxy/forward.js +29 -5
  68. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  69. package/dist/nodefony/src/proxy/rules.js +12 -6
  70. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  71. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  72. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  73. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  74. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  75. package/dist/nodefony/src/servers/portBinder.js +41 -10
  76. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  77. package/dist/nodefony/src/session/session.js +2 -3
  78. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  79. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  80. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  81. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  82. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  83. package/dist/types/index.d.ts +9 -3
  84. package/dist/types/nodefony/config/config.d.ts +4 -0
  85. package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
  86. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  87. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
  88. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
  89. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  91. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
  92. package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
  93. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  94. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  95. package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
  96. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  97. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  98. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  99. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  100. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  101. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  102. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  103. package/dist/types/nodefony/src/context/Context.d.ts +38 -5
  104. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
  105. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  106. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  107. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  108. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  109. package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
  110. package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
  111. package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
  112. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  113. package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -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 +58 -8
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
  118. package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
  119. package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
  120. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  121. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  122. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  123. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  124. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  125. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  126. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  127. package/docs/cookies.md +85 -78
  128. package/docs/diagnostics-channel.md +303 -0
  129. package/docs/index.md +6 -1
  130. package/docs/observabilite.md +159 -81
  131. package/docs/rate-limit.md +83 -83
  132. package/docs/reverse-proxy.md +72 -70
  133. package/docs/servers.md +338 -224
  134. package/docs/session.md +179 -179
  135. package/docs/sse.md +24 -20
  136. package/docs/upload.md +120 -117
  137. package/package.json +8 -9
  138. 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
- //#region nodefony/service/request-logger.ts
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 ctx = context;
17
- const txt = `${logColor.cyan("URL")} : ${ctx.url} ${logColor.cyan("FROM")} : ${ctx.remoteAddress} ${logColor.cyan("ORIGIN")} : ${ctx.originUrl?.host} ${logColor.cyan("ID")} : ${ctx.requestId}`;
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 ?? ctx.response?.statusCode ?? 500;
21
- const msgid = `${ctx.type} ${logColor.magenta(errCode)} ${logColor.red(ctx.method ?? "")}`;
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: ctx.kernel?.environment === "production" ? `${txt} ${err}` : `${txt}\n ${err}`,
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: `${ctx.type} ${logColor.magenta(ctx.response?.statusCode ?? "")} ${ctx.method}`
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 errCode = error.code ?? ctx.response?.statusCode ?? 500;
38
- const msgid = `${ctx.type} ${logColor.magenta(errCode)} ${logColor.red(ctx.method ?? "")}`;
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")} : ${ctx.url} ${logColor.cyan("FROM")} : ${ctx.remoteAddress} ${logColor.cyan("ORIGIN")} : ${ctx.originUrl?.host} ${logColor.cyan("ID")} : ${ctx.requestId}\n ` + error.toString(),
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 = `${ctx.type} ${logColor.magenta(ctx.response?.statusCode ?? "")} ${ctx.method}`;
54
+ const msgid = `${context.serverType} ${logColor.magenta(statusOf(context) ?? "")} ${context.method}`;
46
55
  return {
47
- text: `${logColor.cyan("URL")} : ${ctx.url} ${logColor.cyan("Accept-Protocol")} : ${acceptedProtocol || "*"} ${logColor.cyan("FROM")} : ${ctx.remoteAddress} ${logColor.cyan("ORIGIN")} : ${ctx.originUrl?.host} ${logColor.cyan("ID")} : ${ctx.requestId}`,
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
- //#region nodefony/service/reverse-proxy.ts
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.host ?? req.headers[":authority"];
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
- //#region nodefony/service/servers/clientError.ts
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
- //#endregion
20
- export { handleClientError };
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 __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
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.153.0/helpers/esm/decorateParam.js";
9
- import { Module, Service, extend, inject, nodefonyError } from "nodefony";
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
- //#region nodefony/service/servers/server-http.ts
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, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], ServerHttp);
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 __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
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.153.0/helpers/esm/decorateParam.js";
9
- import { Module, Service, extend, inject, nodefonyError } from "nodefony";
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
- import { TLSSocket } from "node:tls";
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
- if (h2Cfg.maxSessionMemory) opt.maxSessionMemory = h2Cfg.maxSessionMemory;
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
- if (this.options.timeout) this.server.setTimeout(this.options.timeout, () => {
109
+ setHttp2SessionTimeout(this.server, this.options.timeout, () => {
117
110
  this.fire("onTimeout", this);
118
111
  });
119
112
  this.server.on("request", (request, response) => {
120
- let alpnProtocol = false;
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(error, "ERROR", "HTTP2 Server sessionError");
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, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], ServerHttps);
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
- //#region nodefony/service/servers/server-static.ts
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[":authority"];
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 __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
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 { Module, Service, inject } from "nodefony";
6
+ import { Inject, Service } from "nodefony";
9
7
  import Ws, { WebSocketServer } from "ws";
10
- //#region nodefony/service/servers/server-websocket-secure.ts
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 = "websocket-secure";
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
- this.server.on("connection", this.onConnection.bind(this));
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, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], WebsocketSecure);
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 __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
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 { Module, Service, inject } from "nodefony";
6
+ import { Inject, Service } from "nodefony";
9
7
  import Ws, { WebSocketServer } from "ws";
10
- //#region nodefony/service/servers/server-websocket.ts
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 = "websocket";
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
- this.server.on("connection", this.onConnection.bind(this));
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, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], Websocket);
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 { createHttpTerminator } from "http-terminator";
2
- //#region nodefony/service/servers/serverShutdown.ts
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 le terminator de drain graceful d'un serveur HTTP/HTTPS/HTTP2.
6
+ * Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
5
7
  *
6
- * Au `terminate()` (shutdown SIGTERM/docker stop) : les requêtes in-flight se
7
- * terminent (header `connection: close` injecté sur les réponses en cours), les
8
- * sockets idle sont fermées immédiatement, et tout ce qui reste après
9
- * `shutdownTimeout` ms est détruit de force. Le terminator appelle lui-même
10
- * `server.close()` — ne pas le rappeler derrière.
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 par le terminator → les serveurs WS doivent fermer leurs
14
- * clients (close 1001) AVANT ce drain. Garanti par l'ordre des listeners
15
- * `onTerminate` : WS/WSS s'attachent en `prependOnceListener`, les serveurs
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 (créé, pas forcément listening)
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 le terminator à invoquer au shutdown
27
+ * @returns l'arrêt à invoquer au shutdown
21
28
  */
22
29
  function createDrainTerminator(server, shutdownTimeout) {
23
- return createHttpTerminator({
24
- server,
25
- gracefulTerminationTimeout: shutdownTimeout ?? 5e3
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 };