@nodefony/http 10.0.0-beta.4 → 10.0.0-beta.6
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/README.md +24 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +29 -25
- package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
- package/dist/nodefony/config/config.js +6 -3
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +17 -3
- package/dist/nodefony/service/audit-logger.js +3 -17
- package/dist/nodefony/service/error-renderer.js +6 -4
- package/dist/nodefony/service/http-kernel.js +191 -40
- package/dist/nodefony/service/pretty-request-logger.js +11 -10
- package/dist/nodefony/service/request-logger.js +1 -1
- package/dist/nodefony/service/reverse-proxy.js +7 -2
- package/dist/nodefony/service/servers/clientError.js +28 -4
- package/dist/nodefony/service/servers/server-h2c.js +5 -5
- package/dist/nodefony/service/servers/server-http.js +5 -3
- package/dist/nodefony/service/servers/server-https.js +12 -5
- package/dist/nodefony/service/servers/server-static.js +1 -6
- package/dist/nodefony/service/servers/server-websocket-secure.js +6 -10
- package/dist/nodefony/service/servers/server-websocket.js +6 -10
- package/dist/nodefony/service/servers/serverShutdown.js +63 -4
- package/dist/nodefony/service/sessions/sessions-service.js +3 -3
- package/dist/nodefony/service/upload/upload-service.js +2 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
- package/dist/nodefony/src/context/Context.js +56 -13
- package/dist/nodefony/src/context/http/ByteStream.js +359 -0
- package/dist/nodefony/src/context/http/HttpContext.js +75 -25
- package/dist/nodefony/src/context/http/Request.js +15 -6
- package/dist/nodefony/src/context/http/Response.js +354 -35
- package/dist/nodefony/src/context/http/SseStream.js +81 -146
- package/dist/nodefony/src/context/http/StreamLifecycle.js +273 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
- package/dist/nodefony/src/context/http2/Request.js +1 -1
- package/dist/nodefony/src/context/http2/Response.js +28 -5
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +20 -1
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +4 -2
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +81 -20
- package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
- package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
- package/dist/nodefony/src/cookies/cookie.js +54 -13
- package/dist/nodefony/src/profiler/FrameProfile.js +22 -0
- package/dist/nodefony/src/profiler/Profiler.js +25 -22
- package/dist/nodefony/src/profiler/aggregate.js +26 -6
- package/dist/nodefony/src/proxy/forward.js +109 -9
- package/dist/nodefony/src/proxy/generateProxyConfig.js +6 -2
- package/dist/nodefony/src/proxy/rules.js +29 -14
- package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
- package/dist/types/index.d.ts +8 -0
- package/dist/types/nodefony/config/config.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- 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 +16 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +5 -1
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/error-renderer.d.ts +5 -4
- package/dist/types/nodefony/service/http-kernel.d.ts +65 -2
- package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +5 -3
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
- package/dist/types/nodefony/src/context/Context.d.ts +25 -1
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +60 -4
- package/dist/types/nodefony/src/context/http/Request.d.ts +8 -1
- package/dist/types/nodefony/src/context/http/Response.d.ts +134 -8
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +32 -7
- package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +53 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +17 -3
- 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 +17 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +41 -1
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
- package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
- package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +27 -102
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +23 -106
- package/dist/types/nodefony/src/proxy/forward.d.ts +30 -2
- package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
- package/docs/byte-stream.md +371 -0
- package/docs/cookies.md +13 -3
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +14 -1
- package/docs/observabilite.md +94 -16
- package/docs/reverse-proxy.md +33 -19
- package/docs/servers.md +26 -15
- package/docs/session.md +3 -3
- package/docs/sse.md +27 -20
- package/package.json +16 -16
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
|
-
import { handleClientError, handleSessionError } from "./clientError.js";
|
|
4
1
|
import { createDrainTerminator } from "./serverShutdown.js";
|
|
5
|
-
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
6
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
7
2
|
import { http2ServerLimits, setHttp2SessionTimeout } from "../../src/servers/http2Limits.js";
|
|
3
|
+
import { handleClientError, handleSessionError } from "./clientError.js";
|
|
4
|
+
import { routeSocketTimeout } from "../../src/context/http/HttpContext.js";
|
|
5
|
+
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
6
|
+
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
7
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.js";
|
|
8
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
|
|
8
9
|
import { Inject, Service, extend, nodefonyError } from "nodefony";
|
|
9
10
|
import http2 from "node:http2";
|
|
10
11
|
import https from "node:https";
|
|
@@ -57,6 +58,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
57
58
|
const server = https.createServer(opt);
|
|
58
59
|
this.server = server;
|
|
59
60
|
this.httpTerminator = this.terminator();
|
|
61
|
+
server.on("timeout", routeSocketTimeout);
|
|
60
62
|
if (this.options.timeout) {
|
|
61
63
|
server.setTimeout(this.options.timeout, () => {
|
|
62
64
|
this.fire("onTimeout", this);
|
|
@@ -106,6 +108,7 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
106
108
|
Object.assign(opt, http2ServerLimits(this.module.options.http2));
|
|
107
109
|
this.server = http2.createSecureServer(opt);
|
|
108
110
|
this.httpTerminator = this.terminator();
|
|
111
|
+
this.server.on("timeout", routeSocketTimeout);
|
|
109
112
|
setHttp2SessionTimeout(this.server, this.options.timeout, () => {
|
|
110
113
|
this.fire("onTimeout", this);
|
|
111
114
|
});
|
|
@@ -129,6 +132,10 @@ let ServerHttps = class ServerHttps extends Service {
|
|
|
129
132
|
this.server.on("sessionError", (error) => {
|
|
130
133
|
handleSessionError(error, (e) => this.log(e, "ERROR", "HTTP2 Server sessionError"));
|
|
131
134
|
});
|
|
135
|
+
this.server.on("clientError", (e, socket) => {
|
|
136
|
+
this.fire("onClientError", e, socket);
|
|
137
|
+
handleClientError(e, socket);
|
|
138
|
+
});
|
|
132
139
|
this.server.on("streamError", (error) => {
|
|
133
140
|
this.log(error, "ERROR", "HTTP2 Server streamError");
|
|
134
141
|
});
|
|
@@ -6,7 +6,6 @@ import http2 from "node:http2";
|
|
|
6
6
|
import { URL } from "node:url";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import mime from "mime-types";
|
|
10
9
|
import tls from "node:tls";
|
|
11
10
|
import serveStatic from "serve-static";
|
|
12
11
|
// nodefony/service/servers/server-static.ts
|
|
@@ -174,11 +173,7 @@ var Statics = class extends Service {
|
|
|
174
173
|
break;
|
|
175
174
|
}
|
|
176
175
|
}
|
|
177
|
-
for (const ele of Object.values(this.servers))
|
|
178
|
-
await this.getStatic(ele, request, response);
|
|
179
|
-
const type = mime.lookup(pathname);
|
|
180
|
-
response.setHeader("Content-Type", type);
|
|
181
|
-
}
|
|
176
|
+
for (const ele of Object.values(this.servers)) await this.getStatic(ele, request, response);
|
|
182
177
|
return Promise.resolve(response);
|
|
183
178
|
}
|
|
184
179
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
2
1
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
3
|
+
import { attachUpgradeDispatch, createNodefonyWebSocketServer } from "../../src/servers/upgradeDispatch.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
|
|
6
6
|
import { Inject, Service } from "nodefony";
|
|
7
|
-
import Ws
|
|
7
|
+
import Ws from "ws";
|
|
8
8
|
// nodefony/service/servers/server-websocket-secure.ts
|
|
9
9
|
let WebsocketSecure = class WebsocketSecure extends Service {
|
|
10
10
|
httpKernel;
|
|
@@ -45,11 +45,7 @@ let WebsocketSecure = class WebsocketSecure extends Service {
|
|
|
45
45
|
this.family = this.infos.family;
|
|
46
46
|
this.protocol = serverHttps.protocol;
|
|
47
47
|
}
|
|
48
|
-
this.server =
|
|
49
|
-
...this.options,
|
|
50
|
-
noServer: true,
|
|
51
|
-
clientTracking: true
|
|
52
|
-
});
|
|
48
|
+
this.server = createNodefonyWebSocketServer(this.options);
|
|
53
49
|
this.#detachUpgrade = attachUpgradeDispatch(serverHttps.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
|
|
54
50
|
this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
|
|
55
51
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
2
1
|
import { asServersConfig, configuredServerPort } from "../../src/servers/kernelServers.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import { startHeartbeat } from "./wsHeartbeat.js";
|
|
3
|
+
import { attachUpgradeDispatch, createNodefonyWebSocketServer } from "../../src/servers/upgradeDispatch.js";
|
|
4
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.js";
|
|
5
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
|
|
6
6
|
import { Inject, Service } from "nodefony";
|
|
7
|
-
import Ws
|
|
7
|
+
import Ws from "ws";
|
|
8
8
|
// nodefony/service/servers/server-websocket.ts
|
|
9
9
|
let Websocket = class Websocket extends Service {
|
|
10
10
|
httpKernel;
|
|
@@ -45,11 +45,7 @@ let Websocket = class Websocket extends Service {
|
|
|
45
45
|
this.family = this.infos.family;
|
|
46
46
|
this.protocol = serverHttp.protocol;
|
|
47
47
|
}
|
|
48
|
-
this.server =
|
|
49
|
-
...this.options,
|
|
50
|
-
noServer: true,
|
|
51
|
-
clientTracking: true
|
|
52
|
-
});
|
|
48
|
+
this.server = createNodefonyWebSocketServer(this.options);
|
|
53
49
|
this.#detachUpgrade = attachUpgradeDispatch(serverHttp.server, this.server, () => this.httpKernel.reverseProxy, (req) => this.httpKernel.websocketUpgradeRefusal(req), (req, admission) => {
|
|
54
50
|
this.httpKernel.onWebsocketUpgrade(req, admission, this.type).catch(() => {});
|
|
55
51
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
+
import { constants } from "node:http2";
|
|
2
3
|
import https from "node:https";
|
|
3
4
|
import tls from "node:tls";
|
|
4
5
|
// nodefony/service/servers/serverShutdown.ts
|
|
@@ -6,9 +7,11 @@ import tls from "node:tls";
|
|
|
6
7
|
* Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
|
|
7
8
|
*
|
|
8
9
|
* Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
|
|
9
|
-
* - **HTTP/2** : chaque session reçoit un GOAWAY
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* - **HTTP/2** : chaque session reçoit un GOAWAY en deux temps — d'abord à
|
|
11
|
+
* `2^31-1` ({@link announceShutdown}), puis, un aller-retour plus tard, le
|
|
12
|
+
* définitif (`session.close()`). Les flux en vol se terminent, les nouveaux
|
|
13
|
+
* sont refusés, et le client sait qu'il doit rouvrir ailleurs au lieu de
|
|
14
|
+
* constater une coupure ;
|
|
12
15
|
* - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
|
|
13
16
|
* inactive est fermée tout de suite, une requête qui arrive encore sur une
|
|
14
17
|
* socket gardée ouverte reçoit aussi `Connection: close`.
|
|
@@ -59,6 +62,9 @@ function createDrainTerminator(server, shutdownTimeout) {
|
|
|
59
62
|
if (sessions !== null) for (const s of sessions) s.destroy();
|
|
60
63
|
for (const socket of sockets) socket.destroy();
|
|
61
64
|
}, timeout);
|
|
65
|
+
announceShutdown(sessions, Math.min(timeout, ANNOUNCE_CAP_MS), () => closeAll(resolve, deadline));
|
|
66
|
+
});
|
|
67
|
+
const closeAll = (resolve, deadline) => {
|
|
62
68
|
server.close(() => {
|
|
63
69
|
clearTimeout(deadline);
|
|
64
70
|
resolve();
|
|
@@ -72,12 +78,65 @@ function createDrainTerminator(server, shutdownTimeout) {
|
|
|
72
78
|
else if (!response.headersSent) response.setHeader("connection", "close");
|
|
73
79
|
}
|
|
74
80
|
}
|
|
75
|
-
}
|
|
81
|
+
};
|
|
76
82
|
return { terminate() {
|
|
77
83
|
terminating ??= drain();
|
|
78
84
|
return terminating;
|
|
79
85
|
} };
|
|
80
86
|
}
|
|
87
|
+
/** Plus long délai d'annonce (ms) : un pair qui ne répond pas au PING ne retient pas l'arrêt. */
|
|
88
|
+
const ANNOUNCE_CAP_MS = 1e3;
|
|
89
|
+
/** Plus grand identifiant de flux (RFC 9113 §6.8) : « aucun flux n'est encore écarté ». */
|
|
90
|
+
const LAST_POSSIBLE_STREAM = 2 ** 31 - 1;
|
|
91
|
+
/**
|
|
92
|
+
* Annonce l'arrêt à chaque session HTTP/2 SANS écarter un flux déjà en route :
|
|
93
|
+
* un PING mesure l'aller-retour, puis GOAWAY à `2^31-1`, puis l'attente de cet
|
|
94
|
+
* aller-retour — après elle, tout flux ouvert par le client avant l'annonce est
|
|
95
|
+
* arrivé. Le GOAWAY définitif (`session.close()`, dernier flux traité) suit
|
|
96
|
+
* dans `done`. Un pair qui ne répond pas au PING est réglé au délai `cap`.
|
|
97
|
+
* RFC 9113 §6.8 : « SHOULD send an initial GOAWAY frame with the last stream
|
|
98
|
+
* identifier set to 2^31-1 […] After allowing time for any in-flight stream
|
|
99
|
+
* creation (at least one round-trip time) ». Sans annonce, un flux croisé avec
|
|
100
|
+
* le GOAWAY est refusé alors qu'il aurait pu être servi.
|
|
101
|
+
*
|
|
102
|
+
* @param sessions - sessions HTTP/2 ouvertes, `null` pour un serveur HTTP/1.1
|
|
103
|
+
* @param cap - délai maximal d'attente des échos (ms)
|
|
104
|
+
* @param done - appelé UNE fois, quand tous les échos sont revenus ou au délai
|
|
105
|
+
*/
|
|
106
|
+
function announceShutdown(sessions, cap, done) {
|
|
107
|
+
let pending = 0;
|
|
108
|
+
let finished = false;
|
|
109
|
+
const finish = () => {
|
|
110
|
+
if (finished) return;
|
|
111
|
+
finished = true;
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
done();
|
|
114
|
+
};
|
|
115
|
+
const timer = setTimeout(finish, cap);
|
|
116
|
+
if (sessions !== null) for (const session of sessions) {
|
|
117
|
+
if (session.closed || session.destroyed) continue;
|
|
118
|
+
let settled = false;
|
|
119
|
+
let wait = null;
|
|
120
|
+
const settle = () => {
|
|
121
|
+
if (settled) return;
|
|
122
|
+
settled = true;
|
|
123
|
+
if (wait !== null) clearTimeout(wait);
|
|
124
|
+
session.removeListener("close", settle);
|
|
125
|
+
if (--pending === 0) finish();
|
|
126
|
+
};
|
|
127
|
+
pending++;
|
|
128
|
+
session.once("close", settle);
|
|
129
|
+
if (!session.ping((error, rttMs) => {
|
|
130
|
+
if (error !== null || settled || session.closed) return settle();
|
|
131
|
+
session.goaway(constants.NGHTTP2_NO_ERROR, LAST_POSSIBLE_STREAM);
|
|
132
|
+
wait = setTimeout(settle, Math.max(1, Math.ceil(rttMs)));
|
|
133
|
+
})) {
|
|
134
|
+
session.goaway(constants.NGHTTP2_NO_ERROR, LAST_POSSIBLE_STREAM);
|
|
135
|
+
settle();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (pending === 0) finish();
|
|
139
|
+
}
|
|
81
140
|
function track(set, socket) {
|
|
82
141
|
set.add(socket);
|
|
83
142
|
socket.once("close", () => set.delete(socket));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.js";
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
3
|
-
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
|
|
4
1
|
import Session from "../../src/session/session.js";
|
|
5
2
|
import MemorySessionStorage from "../../src/session/storage/MemorySessionStorage.js";
|
|
6
3
|
import RevocationGuardStorage from "../../src/session/storage/RevocationGuardStorage.js";
|
|
4
|
+
import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.js";
|
|
5
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.js";
|
|
6
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
|
|
7
7
|
import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Inject, Nodefony, RequestContext, Service, countFacets, extend, injectable, readStoreConnector, readStoreLocation, resolveAutoStore, storeConnectivity } from "nodefony";
|
|
8
8
|
import { createHash, createHmac } from "node:crypto";
|
|
9
9
|
// nodefony/service/sessions/sessions-service.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
|
|
3
3
|
import { Cli, FileClass, Inject, Service } from "nodefony";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import fsp from "node:fs/promises";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cspNonceMetaTag } from "nodefony";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
// nodefony/src/assets/prebuiltUi.ts
|
|
@@ -67,8 +68,8 @@ function resolveUiDelivery(opts) {
|
|
|
67
68
|
* requête. Le SPA fallback reste au controller du module (routes littérales,
|
|
68
69
|
* cf. StudioController).
|
|
69
70
|
*
|
|
70
|
-
* Perf : l'index est lu UNE fois (lazy, caché)
|
|
71
|
-
*
|
|
71
|
+
* Perf : l'index est lu UNE fois (lazy, caché) et découpé UNE fois autour des
|
|
72
|
+
* emplacements du nonce ; un rendu ne coûte qu'un `join`.
|
|
72
73
|
*/
|
|
73
74
|
var PrebuiltUi = class {
|
|
74
75
|
publicPath;
|
|
@@ -76,6 +77,8 @@ var PrebuiltUi = class {
|
|
|
76
77
|
indexPath;
|
|
77
78
|
/** Template HTML caché — `null` tant que non lu (lazy). */
|
|
78
79
|
template = null;
|
|
80
|
+
/** Index découpé autour des emplacements du nonce — `null` tant que non calculé. */
|
|
81
|
+
nonceParts = null;
|
|
79
82
|
constructor(opts) {
|
|
80
83
|
let p = opts.publicPath.trim();
|
|
81
84
|
if (!p.startsWith("/")) p = `/${p}`;
|
|
@@ -107,8 +110,11 @@ var PrebuiltUi = class {
|
|
|
107
110
|
* Document HTML d'entrée de l'UI (index pré-buildé par Vite).
|
|
108
111
|
*
|
|
109
112
|
* @param nonce nonce CSP de la requête (`Context.cspNonce`) — injecté sur
|
|
110
|
-
* chaque `<script
|
|
111
|
-
*
|
|
113
|
+
* chaque `<script>`, `src` compris : sous `'strict-dynamic'`
|
|
114
|
+
* (ADR-0024 §C1), `'self'` est ignoré et seul le nonce autorise
|
|
115
|
+
* une balise ; ce qu'elle importe ensuite hérite de la confiance.
|
|
116
|
+
* Annoncé aussi par `<meta property="csp-nonce">` en tête de
|
|
117
|
+
* `<head>` (ADR-0024 §C2) : les `<style>` créés à l'exécution.
|
|
112
118
|
* @returns le HTML, ou un commentaire HTML fail-loud si l'index est absent
|
|
113
119
|
*/
|
|
114
120
|
renderIndex(nonce) {
|
|
@@ -118,7 +124,19 @@ var PrebuiltUi = class {
|
|
|
118
124
|
return `<!DOCTYPE html><!-- prebuilt UI index missing: ${this.indexPath} -->`;
|
|
119
125
|
}
|
|
120
126
|
if (!nonce) return this.template;
|
|
121
|
-
|
|
127
|
+
this.nonceParts ??= splitAroundNonce(this.template);
|
|
128
|
+
return this.nonceParts.join(nonce);
|
|
122
129
|
}
|
|
123
130
|
};
|
|
131
|
+
/** Repère des emplacements du nonce : un octet nul ne figure dans aucun HTML. */
|
|
132
|
+
const NONCE_SLOT = "\0";
|
|
133
|
+
/**
|
|
134
|
+
* Découpe un index HTML autour des emplacements du nonce : l'attribut de chaque
|
|
135
|
+
* `<script>` et la balise meta posée juste après l'ouverture de `<head>` (rien
|
|
136
|
+
* si le document n'en a pas — les `<script>` restent signés).
|
|
137
|
+
*/
|
|
138
|
+
function splitAroundNonce(template) {
|
|
139
|
+
const marked = template.replaceAll("<script", `<script nonce="${NONCE_SLOT}"`).replace(/<head\b[^>]*>/i, (open) => open + cspNonceMetaTag(NONCE_SLOT));
|
|
140
|
+
return Object.freeze(marked.split(NONCE_SLOT));
|
|
141
|
+
}
|
|
124
142
|
export { PrebuiltUi, resolveUiDelivery };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildMetaData } from "./metaData.js";
|
|
2
2
|
import { protocolNameOf } from "./transport.js";
|
|
3
|
+
import { resolveTimingMode } from "./timingMode.js";
|
|
3
4
|
import Cookie, { cookiesParser } from "../cookies/cookie.js";
|
|
4
5
|
import { RequestContext, Service, logColor } from "nodefony";
|
|
5
6
|
import { randomFillSync, randomUUID } from "node:crypto";
|
|
@@ -106,6 +107,12 @@ var Context = class extends Service {
|
|
|
106
107
|
router = this.get("router");
|
|
107
108
|
resolver = null;
|
|
108
109
|
/**
|
|
110
|
+
* Module qui signe les lignes de CETTE requête : la ligne d'accès et les
|
|
111
|
+
* lignes du contrôleur. `null` = le module de la route (sans route : le
|
|
112
|
+
* journal du noyau). Un contrôleur le change : `this.context.logModule = "billing"`.
|
|
113
|
+
*/
|
|
114
|
+
logModule = null;
|
|
115
|
+
/**
|
|
109
116
|
* Intent de session de la route courante (posé par le Resolver depuis
|
|
110
117
|
* `@UseSession` / paramètre `@Session`). Pilote le point d'activation unique
|
|
111
118
|
* (HTTP + WS). `null` = aucune session sauf cookie existant (reprise L1).
|
|
@@ -137,8 +144,20 @@ var Context = class extends Service {
|
|
|
137
144
|
profilerQueries = null;
|
|
138
145
|
profiling = false;
|
|
139
146
|
securityTrace = null;
|
|
147
|
+
/**
|
|
148
|
+
* Message du canal `nodefony.http.request` pour CETTE requête, ou `null` si
|
|
149
|
+
* personne n'écoutait à son début (le cas par défaut : un champ, aucune
|
|
150
|
+
* allocation). Posé par `openRequestTrace`, remis à `null` par
|
|
151
|
+
* `closeRequestTrace` — c'est ce `null` qui rend la fin idempotente.
|
|
152
|
+
*
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
requestTrace = null;
|
|
140
156
|
_timingEnabled = false;
|
|
141
157
|
_timingVerbose = false;
|
|
158
|
+
_clockEnabled = false;
|
|
159
|
+
_clockStartMs = 0;
|
|
160
|
+
_durationMs = null;
|
|
142
161
|
phases = EMPTY_PHASES;
|
|
143
162
|
_phaseIndex = null;
|
|
144
163
|
_afterResponseFns = null;
|
|
@@ -157,20 +176,24 @@ var Context = class extends Service {
|
|
|
157
176
|
const p = globalThis.__nfPerfProbe;
|
|
158
177
|
if (p && p.t0 !== 0n) p.svcNs += Number(process.hrtime.bigint() - p.t0);
|
|
159
178
|
}
|
|
179
|
+
const timing = this.kernel?.options.timing;
|
|
180
|
+
const timingSection = typeof timing === "object" && timing !== null ? timing : null;
|
|
181
|
+
const mode = resolveTimingMode(timingSection !== null && "enabled" in timingSection ? timingSection.enabled : void 0, this.kernel?.environment);
|
|
182
|
+
if (mode !== "off") {
|
|
183
|
+
this._clockEnabled = true;
|
|
184
|
+
this._clockStartMs = performance.now();
|
|
185
|
+
}
|
|
186
|
+
if (mode === "phases") {
|
|
187
|
+
this._timingEnabled = true;
|
|
188
|
+
this.phases = [];
|
|
189
|
+
this._timingVerbose = timingSection !== null && "verbose" in timingSection && timingSection.verbose === true;
|
|
190
|
+
}
|
|
160
191
|
this.serverType = serverType;
|
|
161
192
|
this.type = protocolNameOf(serverType);
|
|
162
193
|
this.set("context", this);
|
|
163
194
|
this.httpKernel = this.get("HttpKernel");
|
|
164
195
|
this.sessionService = this.get("sessions");
|
|
165
196
|
this.setMetaData();
|
|
166
|
-
const explicit = (this.kernel?.options)?.timing?.enabled;
|
|
167
|
-
if (typeof explicit === "boolean") this._timingEnabled = explicit;
|
|
168
|
-
else this._timingEnabled = this.kernel?.environment !== "production";
|
|
169
|
-
if (this._timingEnabled) {
|
|
170
|
-
this.phases = [];
|
|
171
|
-
const verbose = (this.kernel?.options)?.timing?.verbose;
|
|
172
|
-
this._timingVerbose = verbose === true;
|
|
173
|
-
}
|
|
174
197
|
this.scheme = serverType;
|
|
175
198
|
switch (serverType) {
|
|
176
199
|
case "http": {
|
|
@@ -245,6 +268,24 @@ var Context = class extends Service {
|
|
|
245
268
|
p.endMs = performance.now();
|
|
246
269
|
p.durationMs = p.endMs - p.startMs;
|
|
247
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Arrête l'horloge de la requête et rend sa durée, en millisecondes.
|
|
273
|
+
*
|
|
274
|
+
* La durée court de la construction du contexte au PREMIER appel : le
|
|
275
|
+
* démontage HTTP (`close` de la réponse, dernier octet remis — la mesure
|
|
276
|
+
* d'OpenTelemetry et du `$request_time` de nginx), la ligne d'accès pour un
|
|
277
|
+
* handshake WebSocket. Les appels suivants rendent la même valeur : journal
|
|
278
|
+
* d'accès, profiler et trace verbose lisent UNE durée, jamais trois.
|
|
279
|
+
*
|
|
280
|
+
* Active par défaut dans tous les environnements, production comprise :
|
|
281
|
+
* seul `timing.enabled: false` la coupe (cf `resolveTimingMode`).
|
|
282
|
+
*
|
|
283
|
+
* @returns la durée, ou `null` si `timing.enabled` vaut `false`
|
|
284
|
+
*/
|
|
285
|
+
stopClock() {
|
|
286
|
+
if (!this._clockEnabled) return null;
|
|
287
|
+
return this._durationMs ??= performance.now() - this._clockStartMs;
|
|
288
|
+
}
|
|
248
289
|
onAfterResponse(fn) {
|
|
249
290
|
const boundFn = AsyncResource.bind(fn);
|
|
250
291
|
if (this._afterResponseFired) {
|
|
@@ -296,10 +337,10 @@ var Context = class extends Service {
|
|
|
296
337
|
_abortIfPending(reason) {
|
|
297
338
|
if (this._abortController && !this._abortController.signal.aborted) this._abortController.abort(new Error(reason ?? "Context aborted"));
|
|
298
339
|
}
|
|
299
|
-
log(pci, severity, msgid, msg) {
|
|
340
|
+
log(pci, severity, msgid, msg, moduleName) {
|
|
300
341
|
if (!msgid) msgid = this.serverType;
|
|
301
|
-
if (this.requestId && RequestContext.getRequestId() === void 0) return RequestContext.run({ requestId: this.requestId }, () => super.log(pci, severity, msgid, msg));
|
|
302
|
-
return super.log(pci, severity, msgid, msg);
|
|
342
|
+
if (this.requestId && RequestContext.getRequestId() === void 0) return RequestContext.run({ requestId: this.requestId }, () => super.log(pci, severity, msgid, msg, moduleName));
|
|
343
|
+
return super.log(pci, severity, msgid, msg, moduleName);
|
|
303
344
|
}
|
|
304
345
|
/**
|
|
305
346
|
* Confie au contexte le magasin `RequestContext` de son unité de travail
|
|
@@ -359,7 +400,7 @@ var Context = class extends Service {
|
|
|
359
400
|
if (!logger) return;
|
|
360
401
|
if (logger.shouldSample && !logger.shouldSample(this, err)) return;
|
|
361
402
|
const entry = logger.renderHttp(this, err);
|
|
362
|
-
return this.log(entry.text, entry.severity, entry.msgid);
|
|
403
|
+
return this.log(entry.text, entry.severity, entry.msgid, void 0, this.logModule ?? this.resolver?.route?.module?.name);
|
|
363
404
|
} catch {
|
|
364
405
|
return;
|
|
365
406
|
}
|
|
@@ -387,7 +428,9 @@ var Context = class extends Service {
|
|
|
387
428
|
} else line += `${p.name}=…`;
|
|
388
429
|
i++;
|
|
389
430
|
}
|
|
390
|
-
|
|
431
|
+
const duration = this.stopClock();
|
|
432
|
+
const head = duration === null ? "" : `total ${duration.toFixed(2)}ms · `;
|
|
433
|
+
this.log(`TRACE phases [${head}Σ ${total.toFixed(2)}ms] ${line}`, "DEBUG", `${this.serverType} TIMING`);
|
|
391
434
|
}
|
|
392
435
|
addRequestCookie(cookie) {
|
|
393
436
|
if (cookie instanceof Cookie) return this.cookies[cookie.name] = cookie;
|