@nodefony/http 10.0.0-alpha.1
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/LICENSE +544 -0
- package/README.md +77 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +108 -0
- package/dist/nodefony/command/assetsPublishCommand.js +102 -0
- package/dist/nodefony/command/certificatesCommand.js +47 -0
- package/dist/nodefony/command/networkCommand.js +27 -0
- package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
- package/dist/nodefony/config/config.js +335 -0
- package/dist/nodefony/config/defineModuleConfig.js +93 -0
- package/dist/nodefony/interfaces/IContext.js +1 -0
- package/dist/nodefony/interfaces/ICookie.js +1 -0
- package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
- package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
- package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
- package/dist/nodefony/interfaces/IRequest.js +1 -0
- package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
- package/dist/nodefony/interfaces/IResponse.js +1 -0
- package/dist/nodefony/interfaces/ISession.js +1 -0
- package/dist/nodefony/interfaces/IUpload.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +376 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
- package/dist/nodefony/service/audit-logger.js +159 -0
- package/dist/nodefony/service/certificates.js +545 -0
- package/dist/nodefony/service/error-renderer.js +320 -0
- package/dist/nodefony/service/http-kernel.js +948 -0
- package/dist/nodefony/service/pretty-request-logger.js +72 -0
- package/dist/nodefony/service/request-logger.js +54 -0
- package/dist/nodefony/service/servers/clientError.js +20 -0
- package/dist/nodefony/service/servers/server-http.js +135 -0
- package/dist/nodefony/service/servers/server-https.js +204 -0
- package/dist/nodefony/service/servers/server-static.js +192 -0
- package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
- package/dist/nodefony/service/servers/server-websocket.js +104 -0
- package/dist/nodefony/service/servers/serverShutdown.js +31 -0
- package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
- package/dist/nodefony/service/sessions/sessions-service.js +580 -0
- package/dist/nodefony/service/trace.js +72 -0
- package/dist/nodefony/service/upload/upload-service.js +171 -0
- package/dist/nodefony/src/assets/collectAssets.js +34 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
- package/dist/nodefony/src/context/Context.js +415 -0
- package/dist/nodefony/src/context/domainMatcher.js +88 -0
- package/dist/nodefony/src/context/forwarded.js +185 -0
- package/dist/nodefony/src/context/http/HttpContext.js +309 -0
- package/dist/nodefony/src/context/http/Request.js +543 -0
- package/dist/nodefony/src/context/http/Response.js +368 -0
- package/dist/nodefony/src/context/http/parser.js +188 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
- package/dist/nodefony/src/context/http2/Request.js +29 -0
- package/dist/nodefony/src/context/http2/Response.js +97 -0
- package/dist/nodefony/src/context/metaData.js +47 -0
- package/dist/nodefony/src/context/requestId.js +41 -0
- package/dist/nodefony/src/context/trustProxy.js +167 -0
- package/dist/nodefony/src/context/websocket/Response.js +181 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
- package/dist/nodefony/src/cookies/cookie.js +258 -0
- package/dist/nodefony/src/errors/httpError.js +69 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
- package/dist/nodefony/src/profiler/Profiler.js +139 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
- package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
- package/dist/nodefony/src/servers/portBinder.js +114 -0
- package/dist/nodefony/src/session/session.js +390 -0
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
- package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
- package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
- package/dist/types/index.d.ts +83 -0
- package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
- package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
- package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
- package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
- package/dist/types/nodefony/config/config.d.ts +197 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
- package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
- package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
- package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
- package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
- package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
- package/dist/types/nodefony/interfaces/index.d.ts +7 -0
- package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
- package/dist/types/nodefony/service/certificates.d.ts +246 -0
- package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
- package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
- package/dist/types/nodefony/service/request-logger.d.ts +18 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
- package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
- package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
- package/dist/types/nodefony/service/trace.d.ts +39 -0
- package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
- package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
- package/dist/types/nodefony/src/context/Context.d.ts +195 -0
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
- package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
- package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
- package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
- package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
- package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
- package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
- package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
- package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
- package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
- package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
- package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
- package/dist/types/nodefony/src/session/session.d.ts +171 -0
- package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
- package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
- package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
- package/docs/cookies.md +365 -0
- package/docs/index.md +163 -0
- package/docs/observabilite.md +460 -0
- package/docs/rate-limit.md +372 -0
- package/docs/servers.md +935 -0
- package/docs/session.md +768 -0
- package/docs/upload.md +460 -0
- package/package.json +101 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
|
|
3
|
+
import http_kernel_default from "../http-kernel.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
|
|
6
|
+
import { Module, Service, inject } from "nodefony";
|
|
7
|
+
import Ws, { WebSocketServer } from "ws";
|
|
8
|
+
//#region nodefony/service/servers/server-websocket-secure.ts
|
|
9
|
+
let WebsocketSecure = class WebsocketSecure extends Service {
|
|
10
|
+
httpKernel;
|
|
11
|
+
module;
|
|
12
|
+
ready = false;
|
|
13
|
+
server = null;
|
|
14
|
+
port;
|
|
15
|
+
domain;
|
|
16
|
+
protocol = "1.1";
|
|
17
|
+
family = null;
|
|
18
|
+
scheme = "wss";
|
|
19
|
+
address = null;
|
|
20
|
+
type = "websocket-secure";
|
|
21
|
+
infos = null;
|
|
22
|
+
/** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
|
|
23
|
+
heartbeatTimer = null;
|
|
24
|
+
constructor(module, httpKernel) {
|
|
25
|
+
super("server-websocket-secure", module.container, module.notificationsCenter, module.options.websocketSecure);
|
|
26
|
+
this.httpKernel = httpKernel;
|
|
27
|
+
this.module = module;
|
|
28
|
+
this.port = this.setPort();
|
|
29
|
+
this.domain = this.kernel?.domain;
|
|
30
|
+
this.ready = false;
|
|
31
|
+
}
|
|
32
|
+
setPort() {
|
|
33
|
+
if (this.kernel?.options.servers?.https) return this.kernel?.options.servers?.https?.port || 0;
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
async createServer(serverHttps) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
try {
|
|
39
|
+
this.infos = serverHttps.server.address();
|
|
40
|
+
if (this.infos) {
|
|
41
|
+
this.port = this.infos.port;
|
|
42
|
+
this.address = this.infos.address;
|
|
43
|
+
this.family = this.infos.family;
|
|
44
|
+
this.protocol = serverHttps.protocol;
|
|
45
|
+
}
|
|
46
|
+
this.server = new WebSocketServer({
|
|
47
|
+
...this.options ?? {},
|
|
48
|
+
server: serverHttps.server,
|
|
49
|
+
clientTracking: true
|
|
50
|
+
});
|
|
51
|
+
this.server.on("connection", this.onConnection.bind(this));
|
|
52
|
+
this.heartbeatTimer = startHeartbeat(this.server, this.options);
|
|
53
|
+
this.kernel?.prependOnceListener("onTerminate", this.terminate.bind(this));
|
|
54
|
+
if (this.server) this.ready = true;
|
|
55
|
+
this.module.fire("onServersReady", this.type, this);
|
|
56
|
+
return resolve(this.server);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
this.log(e, "ERROR");
|
|
59
|
+
return reject(e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
onConnection(ws, req) {
|
|
64
|
+
trackPong(ws);
|
|
65
|
+
this.httpKernel.onWebsocketRequest(ws, req, this.type).catch(() => {
|
|
66
|
+
process.nextTick(() => {});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
terminate() {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
if (this.heartbeatTimer) {
|
|
72
|
+
clearInterval(this.heartbeatTimer);
|
|
73
|
+
this.heartbeatTimer = null;
|
|
74
|
+
}
|
|
75
|
+
if (this.server && this.ready) {
|
|
76
|
+
const shutdownMsg = JSON.stringify({ nodefony: { state: "shutDown" } });
|
|
77
|
+
this.server.clients.forEach((client) => {
|
|
78
|
+
if (client.readyState === Ws.OPEN) {
|
|
79
|
+
client.send(shutdownMsg);
|
|
80
|
+
client.close(1001, "Server shutting down");
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
try {
|
|
85
|
+
this.server?.close();
|
|
86
|
+
this.log(` SHUTDOWN WEBSOCKET Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
|
|
87
|
+
return resolve(true);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return reject(e);
|
|
90
|
+
}
|
|
91
|
+
}, 300);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
return resolve(true);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
showBanner() {
|
|
98
|
+
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
WebsocketSecure = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], WebsocketSecure);
|
|
102
|
+
var server_websocket_secure_default = WebsocketSecure;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { server_websocket_secure_default as default };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
|
|
3
|
+
import http_kernel_default from "../http-kernel.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
|
|
6
|
+
import { Module, Service, inject } from "nodefony";
|
|
7
|
+
import Ws, { WebSocketServer } from "ws";
|
|
8
|
+
//#region nodefony/service/servers/server-websocket.ts
|
|
9
|
+
let Websocket = class Websocket extends Service {
|
|
10
|
+
httpKernel;
|
|
11
|
+
module;
|
|
12
|
+
ready = false;
|
|
13
|
+
server = null;
|
|
14
|
+
port;
|
|
15
|
+
domain;
|
|
16
|
+
protocol = "1.1";
|
|
17
|
+
family = null;
|
|
18
|
+
scheme = "ws";
|
|
19
|
+
address = null;
|
|
20
|
+
type = "websocket";
|
|
21
|
+
infos = null;
|
|
22
|
+
/** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
|
|
23
|
+
heartbeatTimer = null;
|
|
24
|
+
constructor(module, httpKernel) {
|
|
25
|
+
super("server-websocket", module.container, module.notificationsCenter, module.options.websocket);
|
|
26
|
+
this.httpKernel = httpKernel;
|
|
27
|
+
this.module = module;
|
|
28
|
+
this.port = this.setPort();
|
|
29
|
+
this.domain = this.kernel?.domain;
|
|
30
|
+
this.ready = false;
|
|
31
|
+
}
|
|
32
|
+
setPort() {
|
|
33
|
+
if (this.kernel?.options.servers?.http) return this.kernel?.options.servers?.http?.port || 0;
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
async createServer(serverHttp) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
try {
|
|
39
|
+
this.infos = serverHttp.server.address();
|
|
40
|
+
if (this.infos) {
|
|
41
|
+
this.port = this.infos.port;
|
|
42
|
+
this.address = this.infos.address;
|
|
43
|
+
this.family = this.infos.family;
|
|
44
|
+
this.protocol = serverHttp.protocol;
|
|
45
|
+
}
|
|
46
|
+
this.server = new WebSocketServer({
|
|
47
|
+
...this.options ?? {},
|
|
48
|
+
server: serverHttp.server,
|
|
49
|
+
clientTracking: true
|
|
50
|
+
});
|
|
51
|
+
this.server.on("connection", this.onConnection.bind(this));
|
|
52
|
+
this.heartbeatTimer = startHeartbeat(this.server, this.options);
|
|
53
|
+
this.kernel?.prependOnceListener("onTerminate", this.terminate.bind(this));
|
|
54
|
+
if (this.server) this.ready = true;
|
|
55
|
+
this.module.fire("onServersReady", this.type, this);
|
|
56
|
+
return resolve(this.server);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
this.log(e, "ERROR");
|
|
59
|
+
return reject(e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
onConnection(ws, req) {
|
|
64
|
+
trackPong(ws);
|
|
65
|
+
this.httpKernel.onWebsocketRequest(ws, req, this.type).catch(() => {
|
|
66
|
+
process.nextTick(() => {});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
terminate() {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
if (this.heartbeatTimer) {
|
|
72
|
+
clearInterval(this.heartbeatTimer);
|
|
73
|
+
this.heartbeatTimer = null;
|
|
74
|
+
}
|
|
75
|
+
if (this.server && this.ready) {
|
|
76
|
+
const shutdownMsg = JSON.stringify({ nodefony: { state: "shutDown" } });
|
|
77
|
+
this.server.clients.forEach((client) => {
|
|
78
|
+
if (client.readyState === Ws.OPEN) {
|
|
79
|
+
client.send(shutdownMsg);
|
|
80
|
+
client.close(1001, "Server shutting down");
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
try {
|
|
85
|
+
this.server?.close();
|
|
86
|
+
this.log(` SHUTDOWN WEBSOCKET Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
|
|
87
|
+
return resolve(true);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return reject(e);
|
|
90
|
+
}
|
|
91
|
+
}, 300);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
return resolve(true);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
showBanner() {
|
|
98
|
+
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
Websocket = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], Websocket);
|
|
102
|
+
var server_websocket_default = Websocket;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { server_websocket_default as default };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHttpTerminator } from "http-terminator";
|
|
2
|
+
//#region nodefony/service/servers/serverShutdown.ts
|
|
3
|
+
/**
|
|
4
|
+
* Fabrique le terminator de drain graceful d'un serveur HTTP/HTTPS/HTTP2.
|
|
5
|
+
*
|
|
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.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ 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`.
|
|
17
|
+
*
|
|
18
|
+
* @param server - serveur Node à drainer (créé, pas forcément listening)
|
|
19
|
+
* @param shutdownTimeout - délai de drain en ms avant destruction forcée
|
|
20
|
+
* @returns le terminator à invoquer au shutdown
|
|
21
|
+
*/
|
|
22
|
+
function createDrainTerminator(server, shutdownTimeout) {
|
|
23
|
+
return createHttpTerminator({
|
|
24
|
+
server,
|
|
25
|
+
gracefulTerminationTimeout: shutdownTimeout ?? 5e3
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
|
|
29
|
+
const DEFAULT_SHUTDOWN_TIMEOUT = 5e3;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { DEFAULT_SHUTDOWN_TIMEOUT, createDrainTerminator };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Ws from "ws";
|
|
2
|
+
//#region nodefony/service/servers/wsHeartbeat.ts
|
|
3
|
+
/**
|
|
4
|
+
* Arme le suivi keep-alive d'UNE connexion : initialise l'horodatage de vie et
|
|
5
|
+
* attache l'unique listener `pong` qui le rafraîchit.
|
|
6
|
+
*
|
|
7
|
+
* Le listener `pong` vit et meurt avec la socket (l'émetteur EST la socket `ws`,
|
|
8
|
+
* détruite au `close`/`terminate`) → aucun `removeListener` explicite à prévoir.
|
|
9
|
+
* Un seul listener par connexion, c'est le pair-event obligatoire du ping.
|
|
10
|
+
*
|
|
11
|
+
* @param ws - la socket fraîchement connectée
|
|
12
|
+
*/
|
|
13
|
+
const trackPong = (ws) => {
|
|
14
|
+
const sock = ws;
|
|
15
|
+
sock._nfLastPong = performance.now();
|
|
16
|
+
sock._nfPingedAt = 0;
|
|
17
|
+
ws.on("pong", () => {
|
|
18
|
+
ws._nfLastPong = performance.now();
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Démarre le heartbeat keep-alive du serveur : détecte les connexions zombies
|
|
23
|
+
* (half-open — TCP encore ouvert mais le pair a disparu sans frame Close) et les
|
|
24
|
+
* `terminate()` pour libérer slot mémoire + descripteur de fichier.
|
|
25
|
+
*
|
|
26
|
+
* Sémantique (fidèle à l'ancienne lib `websocket`) : un ping est émis tous les
|
|
27
|
+
* `keepaliveInterval` ms ; si aucun `pong` n'arrive dans les `keepaliveGracePeriod`
|
|
28
|
+
* ms qui suivent ce ping, la socket est détruite. Temps de réclamation borné par
|
|
29
|
+
* `keepaliveInterval + keepaliveGracePeriod` (+ une granularité de tick).
|
|
30
|
+
*
|
|
31
|
+
* PERF (hot path WS) : **UN seul `setInterval` par serveur** — jamais un timer par
|
|
32
|
+
* connexion. Chaque tick ne lit/écrit que des `number` sur la socket → 0 allocation
|
|
33
|
+
* sur le chemin nominal. `unref()` pour ne pas retenir le process à l'arrêt. Le timer
|
|
34
|
+
* DOIT être `clearInterval` au shutdown (cf appelant).
|
|
35
|
+
*
|
|
36
|
+
* @param server - le `WebSocketServer` dont on surveille `clients`
|
|
37
|
+
* @param options - knobs keep-alive (depuis la config Zod du module)
|
|
38
|
+
* @returns le timer à nettoyer au shutdown, ou `null` si le keep-alive est désactivé
|
|
39
|
+
*/
|
|
40
|
+
const startHeartbeat = (server, options) => {
|
|
41
|
+
const interval = options.keepaliveInterval ?? 0;
|
|
42
|
+
if (interval <= 0) return null;
|
|
43
|
+
const grace = options.keepaliveGracePeriod ?? 0;
|
|
44
|
+
const timer = setInterval(() => {
|
|
45
|
+
const now = performance.now();
|
|
46
|
+
for (const client of server.clients) {
|
|
47
|
+
if (client.readyState !== Ws.OPEN) continue;
|
|
48
|
+
const sock = client;
|
|
49
|
+
if (sock._nfLastPong === void 0) sock._nfLastPong = now;
|
|
50
|
+
if ((sock._nfPingedAt ?? 0) > sock._nfLastPong) {
|
|
51
|
+
if (now - sock._nfPingedAt > grace) client.terminate();
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (now - sock._nfLastPong >= interval) {
|
|
55
|
+
sock._nfPingedAt = now;
|
|
56
|
+
client.ping();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, Math.max(250, grace > 0 ? Math.min(interval, grace) : interval));
|
|
60
|
+
timer.unref?.();
|
|
61
|
+
return timer;
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
export { startHeartbeat, trackPong };
|