@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +13 -10
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +3 -3
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -2
- package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
- package/dist/nodefony/service/audit-logger.js +23 -30
- package/dist/nodefony/service/certificates.js +97 -13
- package/dist/nodefony/service/error-renderer.js +29 -12
- package/dist/nodefony/service/http-kernel.js +198 -50
- package/dist/nodefony/service/pretty-request-logger.js +15 -13
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +3 -3
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +3 -3
- package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
- package/dist/nodefony/service/servers/server-websocket.js +10 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
- package/dist/nodefony/service/sessions/sessions-service.js +7 -14
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +78 -43
- package/dist/nodefony/src/context/domainMatcher.js +1 -2
- package/dist/nodefony/src/context/forwarded.js +1 -2
- package/dist/nodefony/src/context/http/HttpContext.js +28 -6
- package/dist/nodefony/src/context/http/Request.js +33 -29
- package/dist/nodefony/src/context/http/Response.js +23 -6
- package/dist/nodefony/src/context/http/SseStream.js +1 -2
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +41 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/responseEnded.js +1 -2
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +33 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
- package/dist/nodefony/src/profiler/Profiler.js +104 -32
- package/dist/nodefony/src/profiler/aggregate.js +442 -0
- package/dist/nodefony/src/proxy/forward.js +29 -5
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +12 -6
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
- package/dist/nodefony/src/session/session.js +2 -3
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +9 -3
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/certificates.d.ts +55 -4
- package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/src/context/Context.d.ts +38 -5
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
- package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
- package/docs/cookies.md +85 -78
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +6 -1
- package/docs/observabilite.md +159 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +72 -70
- package/docs/servers.md +338 -224
- package/docs/session.md +179 -179
- package/docs/sse.md +24 -20
- package/docs/upload.md +120 -117
- package/package.json +8 -9
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/servers/kernelServers.ts
|
|
2
2
|
/**
|
|
3
3
|
* Rétrécit la section `servers` de la configuration du kernel (lue dans le sac
|
|
4
4
|
* de config non typé du cœur) en {@link ServersConfig}, sans propager `any`.
|
|
@@ -13,6 +13,15 @@ function asServersConfig(servers) {
|
|
|
13
13
|
return servers !== null && typeof servers === "object" ? servers : void 0;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
+
* Le serveur h2c démarre-t-il ? Seul un `enabled: true` EXPLICITE l'allume : la
|
|
17
|
+
* section existe toujours (défauts), un port seul ne vaut pas activation.
|
|
18
|
+
*
|
|
19
|
+
* @param entry - `servers.h2c`.
|
|
20
|
+
*/
|
|
21
|
+
function isH2cEnabled(entry) {
|
|
22
|
+
return entry?.enabled === true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
16
25
|
* Port DEMANDÉ pour un serveur (`servers.<type>.port`), ou `undefined` si le
|
|
17
26
|
* serveur est désactivé, absent, ou sans port numérique.
|
|
18
27
|
*
|
|
@@ -22,8 +31,8 @@ function asServersConfig(servers) {
|
|
|
22
31
|
function configuredServerPort(servers, type) {
|
|
23
32
|
const entry = servers?.[type];
|
|
24
33
|
if (!entry) return void 0;
|
|
34
|
+
if (type === "h2c" && !isH2cEnabled(servers.h2c)) return void 0;
|
|
25
35
|
const port = entry.port;
|
|
26
36
|
return typeof port === "number" ? port : void 0;
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
export { asServersConfig, configuredServerPort };
|
|
38
|
+
export { asServersConfig, configuredServerPort, isH2cEnabled };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isH2cEnabled } from "./kernelServers.js";
|
|
1
2
|
import { isPortListening, resolvePortPolicy } from "nodefony";
|
|
2
3
|
import cluster from "node:cluster";
|
|
3
|
-
|
|
4
|
+
// nodefony/src/servers/portBinder.ts
|
|
4
5
|
/**
|
|
5
6
|
* **portBinder** — écoute sur le port désiré, ou sur le prochain port libre.
|
|
6
7
|
*
|
|
@@ -48,27 +49,58 @@ import cluster from "node:cluster";
|
|
|
48
49
|
*/
|
|
49
50
|
/** Nombre de ports essayés après le désiré, en `auto`. */
|
|
50
51
|
const DEFAULT_PORT_RETRY_ATTEMPTS = 20;
|
|
52
|
+
const BOUND_SERVERS = [
|
|
53
|
+
"http",
|
|
54
|
+
"https",
|
|
55
|
+
"h2c"
|
|
56
|
+
];
|
|
51
57
|
/** Port configuré d'un serveur, ou `0` (désactivé / non précisé → choix noyau). */
|
|
52
|
-
function configuredPort(
|
|
58
|
+
function configuredPort(servers, which) {
|
|
59
|
+
if (which === "h2c") {
|
|
60
|
+
const entry = servers?.h2c;
|
|
61
|
+
return isH2cEnabled(entry) ? entry?.port ?? 0 : 0;
|
|
62
|
+
}
|
|
63
|
+
const entry = servers?.[which];
|
|
53
64
|
if (!entry) return 0;
|
|
54
65
|
return entry.port ?? 0;
|
|
55
66
|
}
|
|
56
67
|
/**
|
|
68
|
+
* Serveur actif qui convoite le MÊME port que h2c, ou `null`.
|
|
69
|
+
*
|
|
70
|
+
* Un port partagé avec h2c n'est pas un conflit à contourner : sur ce port, le
|
|
71
|
+
* client HTTP/1.1 et le proxy h2c se marchent dessus, et le repli `auto` ferait
|
|
72
|
+
* glisser l'un des deux là où personne ne l'attend. Le boot le refuse.
|
|
73
|
+
*
|
|
74
|
+
* @param servers - `kernel.options.servers`.
|
|
75
|
+
* @returns `"http"` ou `"https"` en conflit, sinon `null` (h2c inactif, port 0).
|
|
76
|
+
*/
|
|
77
|
+
function h2cPortConflict(servers) {
|
|
78
|
+
const port = configuredPort(servers, "h2c");
|
|
79
|
+
if (port === 0) return null;
|
|
80
|
+
if (configuredPort(servers, "http") === port) return "http";
|
|
81
|
+
if (configuredPort(servers, "https") === port) return "https";
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
57
85
|
* Compose le plan de bind d'un serveur depuis la config du kernel — source UNIQUE
|
|
58
|
-
* (les
|
|
86
|
+
* (les serveurs l'appellent ; la politique n'est décidée qu'ici).
|
|
59
87
|
*
|
|
60
88
|
* @param which - le serveur qu'on borne.
|
|
61
89
|
* @param servers - `kernel.options.servers`.
|
|
62
90
|
* @param environment - `kernel.environment` (arbitre le défaut de la politique).
|
|
63
91
|
*/
|
|
64
92
|
function buildBindPlan(which, servers, environment) {
|
|
65
|
-
const desired = configuredPort(servers
|
|
66
|
-
const
|
|
67
|
-
const
|
|
93
|
+
const desired = configuredPort(servers, which);
|
|
94
|
+
const reserved = [];
|
|
95
|
+
for (const other of BOUND_SERVERS) {
|
|
96
|
+
if (other === which) continue;
|
|
97
|
+
const port = configuredPort(servers, other);
|
|
98
|
+
if (port > 0 && port !== desired) reserved.push(port);
|
|
99
|
+
}
|
|
68
100
|
return {
|
|
69
101
|
desired,
|
|
70
|
-
reserved
|
|
71
|
-
attempts:
|
|
102
|
+
reserved,
|
|
103
|
+
attempts: resolvePortPolicy(environment, servers?.portPolicy) === "auto" ? servers?.portRetryAttempts ?? 20 : 0
|
|
72
104
|
};
|
|
73
105
|
}
|
|
74
106
|
/** Adresses qui désignent « toutes les interfaces » — rien ne s'y connecte. */
|
|
@@ -224,5 +256,4 @@ async function bindWithFallback(server, host, plan, probe = isPortListening, sha
|
|
|
224
256
|
candidate = next;
|
|
225
257
|
}
|
|
226
258
|
}
|
|
227
|
-
|
|
228
|
-
export { DEFAULT_PORT_RETRY_ATTEMPTS, bindWithFallback, buildBindPlan, conflictProbeTargets, detectPortConflict, resolvePortPolicy };
|
|
259
|
+
export { DEFAULT_PORT_RETRY_ATTEMPTS, bindWithFallback, buildBindPlan, conflictProbeTargets, detectPortConflict, h2cPortConflict, resolvePortPolicy };
|
|
@@ -1,6 +1,79 @@
|
|
|
1
|
+
import { websocketHandshakeProblem } from "../proxy/rules.js";
|
|
1
2
|
import { refuseUpgrade } from "../proxy/forward.js";
|
|
2
3
|
import { STATUS_CODES } from "node:http";
|
|
3
|
-
|
|
4
|
+
// nodefony/src/servers/upgradeDispatch.ts
|
|
5
|
+
/** Défaut sans noyau : tout upgrade conforme est accepté puis émis en `connection`. */
|
|
6
|
+
function acceptAll(wss) {
|
|
7
|
+
return (req, admission) => {
|
|
8
|
+
const accepted = admission.accept();
|
|
9
|
+
if (accepted instanceof Promise) accepted.then((ws) => {
|
|
10
|
+
if (ws !== null) wss.emit("connection", ws, req);
|
|
11
|
+
});
|
|
12
|
+
else if (accepted !== null) wss.emit("connection", accepted, req);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Le verdict d'UN upgrade. Pendant que le noyau décide, le socket n'a plus
|
|
17
|
+
* d'écouteur `error` (`node:http` retire les siens à l'émission d'`upgrade`,
|
|
18
|
+
* `ws` ne pose les siens qu'à `handleUpgrade`) : une coupure du client
|
|
19
|
+
* (`ECONNRESET`) y deviendrait une erreur non gérée qui abat le process. D'où
|
|
20
|
+
* l'écouteur de la fenêtre d'attente, retiré quand le verdict tombe.
|
|
21
|
+
*
|
|
22
|
+
* Une classe, pas un objet de fermetures : une allocation par handshake (plus
|
|
23
|
+
* l'écouteur), méthodes partagées par le prototype.
|
|
24
|
+
*/
|
|
25
|
+
var UpgradeAdmission = class {
|
|
26
|
+
wss;
|
|
27
|
+
req;
|
|
28
|
+
socket;
|
|
29
|
+
head;
|
|
30
|
+
#accepted = void 0;
|
|
31
|
+
#settled = false;
|
|
32
|
+
#onPendingError;
|
|
33
|
+
constructor(wss, req, socket, head) {
|
|
34
|
+
this.wss = wss;
|
|
35
|
+
this.req = req;
|
|
36
|
+
this.socket = socket;
|
|
37
|
+
this.head = head;
|
|
38
|
+
this.#onPendingError = () => socket.destroy();
|
|
39
|
+
socket.on("error", this.#onPendingError);
|
|
40
|
+
}
|
|
41
|
+
#settle() {
|
|
42
|
+
if (this.#settled) return false;
|
|
43
|
+
this.#settled = true;
|
|
44
|
+
this.socket.removeListener("error", this.#onPendingError);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
accept() {
|
|
48
|
+
if (this.#accepted !== void 0) return this.#accepted;
|
|
49
|
+
if (!this.#settle()) return this.#accepted = null;
|
|
50
|
+
const { socket } = this;
|
|
51
|
+
if (socket.destroyed || !socket.writable) {
|
|
52
|
+
socket.destroy();
|
|
53
|
+
return this.#accepted = null;
|
|
54
|
+
}
|
|
55
|
+
let ws;
|
|
56
|
+
let late = null;
|
|
57
|
+
const onClose = () => late?.(null);
|
|
58
|
+
this.wss.handleUpgrade(this.req, socket, this.head, (accepted) => {
|
|
59
|
+
if (late === null) {
|
|
60
|
+
ws = accepted;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
socket.removeListener("close", onClose);
|
|
64
|
+
late(accepted);
|
|
65
|
+
});
|
|
66
|
+
if (ws !== void 0) return this.#accepted = ws;
|
|
67
|
+
socket.once("close", onClose);
|
|
68
|
+
return this.#accepted = new Promise((resolve) => {
|
|
69
|
+
late = resolve;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
refuse(status, headers) {
|
|
73
|
+
if (!this.#settle()) return;
|
|
74
|
+
refuseUpgrade(this.socket, status, STATUS_CODES[status] ?? "Refused", headers);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
4
77
|
/**
|
|
5
78
|
* Pose l'UNIQUE écouteur `upgrade` d'un serveur : le proxy inverse d'abord,
|
|
6
79
|
* puis le serveur WebSocket de Nodefony.
|
|
@@ -14,7 +87,10 @@ import { STATUS_CODES } from "node:http";
|
|
|
14
87
|
*
|
|
15
88
|
* Entre les deux, `refusal` tranche ce qui doit être refusé AVANT le `101`
|
|
16
89
|
* (RFC 6455 §4.2.2) : un chemin sans route WebSocket reçoit un statut HTTP,
|
|
17
|
-
* jamais une ouverture suivie d'une fermeture.
|
|
90
|
+
* jamais une ouverture suivie d'une fermeture. Puis la FORME du handshake est
|
|
91
|
+
* contrôlée (`websocketHandshakeProblem`, la règle du proxy inverse) : un
|
|
92
|
+
* handshake malformé reçoit `400` sans jamais atteindre le noyau. Enfin
|
|
93
|
+
* `admit` décide — pare-feu, garde de l'action — et seul son verdict ouvre.
|
|
18
94
|
*
|
|
19
95
|
* Sans montage, le coût par connexion est une lecture de champ.
|
|
20
96
|
*
|
|
@@ -23,9 +99,11 @@ import { STATUS_CODES } from "node:http";
|
|
|
23
99
|
* @param relay - proxy inverse, lu à chaque upgrade (il peut naître après)
|
|
24
100
|
* @param refusal - statut HTTP qui refuse l'upgrade, `null` pour l'admettre
|
|
25
101
|
* (cf `HttpKernel.websocketUpgradeRefusal`)
|
|
102
|
+
* @param admit - décide d'un upgrade admis (cf `HttpKernel.onWebsocketUpgrade`) ;
|
|
103
|
+
* par défaut, tout upgrade conforme est accepté et émis en `connection`
|
|
26
104
|
* @returns la fonction qui retire l'écouteur — à appeler à l'arrêt
|
|
27
105
|
*/
|
|
28
|
-
function attachUpgradeDispatch(server, wss, relay, refusal) {
|
|
106
|
+
function attachUpgradeDispatch(server, wss, relay, refusal, admit = acceptAll(wss)) {
|
|
29
107
|
const onUpgrade = (req, socket, head) => {
|
|
30
108
|
const proxy = relay();
|
|
31
109
|
if (proxy !== null && proxy.mounts !== null) {
|
|
@@ -36,14 +114,15 @@ function attachUpgradeDispatch(server, wss, relay, refusal) {
|
|
|
36
114
|
refuseUpgrade(socket, status, STATUS_CODES[status] ?? "Refused");
|
|
37
115
|
return;
|
|
38
116
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
if (websocketHandshakeProblem(req.method, req.headers) !== null) {
|
|
118
|
+
refuseUpgrade(socket, 400, STATUS_CODES[400] ?? "Bad Request");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
admit(req, new UpgradeAdmission(wss, req, socket, head));
|
|
42
122
|
};
|
|
43
123
|
server.on("upgrade", onUpgrade);
|
|
44
124
|
return () => {
|
|
45
125
|
server.removeListener("upgrade", onUpgrade);
|
|
46
126
|
};
|
|
47
127
|
}
|
|
48
|
-
//#endregion
|
|
49
128
|
export { attachUpgradeDispatch };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Cookie from "../cookies/cookie.js";
|
|
2
2
|
import { extend } from "nodefony";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
|
-
|
|
4
|
+
// nodefony/src/session/session.ts
|
|
5
5
|
const defaultSessionOptions = {
|
|
6
6
|
name: "nodefony",
|
|
7
7
|
strictMode: true,
|
|
@@ -238,7 +238,7 @@ var Session = class {
|
|
|
238
238
|
}
|
|
239
239
|
isValidSession(_data, context) {
|
|
240
240
|
if (this.options.refererCheck) {
|
|
241
|
-
let sameHost
|
|
241
|
+
let sameHost;
|
|
242
242
|
try {
|
|
243
243
|
sameHost = this.checkSecureReferer(context);
|
|
244
244
|
} catch {
|
|
@@ -410,5 +410,4 @@ var Session = class {
|
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
};
|
|
413
|
-
//#endregion
|
|
414
413
|
export { Session as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { thrownMessage } from "../errors/thrownMessage.js";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/session/sessionStoreFailure.ts
|
|
3
3
|
/**
|
|
4
4
|
* Reconnaît une table de session absente, quel que soit le dialecte.
|
|
5
5
|
*
|
|
@@ -26,5 +26,4 @@ function describeSessionStoreFailure(error) {
|
|
|
26
26
|
message: `La session n'a pas pu être persistée — la requête est servie en 500 plutôt que laissée sans réponse. Cause : ${cause}.${looksLikeMissingTable(cause) ? " La table de session est probablement absente : appliquer les migrations (`nodefony orm:migrate`) avant de servir, ou choisir un autre stockage (`http.session.store`)." : " Vérifier que le stockage de session déclaré sous `http.session.store` est joignable ; à défaut, appliquer les migrations (`nodefony orm:migrate`)."}`
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
//#endregion
|
|
30
29
|
export { describeSessionStoreFailure };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SESSION_DEFAULT_ORDER, SESSION_SORTABLE_FIELDS } from "./sessionSort.js";
|
|
2
2
|
import { assertPageQuery, compareByOrder, pickOrder } from "nodefony";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/session/storage/MemorySessionStorage.ts
|
|
4
4
|
/**
|
|
5
5
|
* Store de sessions **en mémoire** (Map process) — implémentation de référence
|
|
6
6
|
* d'{@link ISessionStorage}, pendant `session` des `Memory*Store` de sécurité.
|
|
@@ -181,5 +181,4 @@ var MemorySessionStorage = class {
|
|
|
181
181
|
return Promise.resolve(users.size);
|
|
182
182
|
}
|
|
183
183
|
};
|
|
184
|
-
//#endregion
|
|
185
184
|
export { MemorySessionStorage as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/session/storage/RevocationGuardStorage.ts
|
|
2
2
|
/**
|
|
3
3
|
* Durée de vie (ms) d'une pierre tombale de révocation. Couvre la fenêtre
|
|
4
4
|
* pendant laquelle une requête « en vol » — qui avait chargé la session AVANT
|
|
@@ -148,5 +148,4 @@ var RevocationGuardStorage = class {
|
|
|
148
148
|
if (t.size === 0) this.#tombstones = null;
|
|
149
149
|
}
|
|
150
150
|
};
|
|
151
|
-
//#endregion
|
|
152
151
|
export { RevocationGuardStorage as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// nodefony/src/session/storage/sessionFilters.ts
|
|
2
2
|
/**
|
|
3
3
|
* **Le vocabulaire de filtre des sessions**, en noms PUBLICS — celui que la
|
|
4
4
|
* console d'administration écrit dans l'URL (`?user=alice`).
|
|
@@ -79,5 +79,4 @@ const SESSION_FACETS = {
|
|
|
79
79
|
* rouvert du même geste.
|
|
80
80
|
*/
|
|
81
81
|
const SESSION_STATS_FILTERS = { user: "string" };
|
|
82
|
-
//#endregion
|
|
83
82
|
export { SESSION_FACETS, SESSION_FILTERS, SESSION_STATS_FILTERS };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renameOrderFields } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/session/storage/sessionSort.ts
|
|
3
3
|
/**
|
|
4
4
|
* **Le vocabulaire de tri des sessions**, en noms PUBLICS — ceux qu'un client
|
|
5
5
|
* écrit dans l'URL (`?order=updatedAt:DESC`), jamais des noms de colonne.
|
|
@@ -49,5 +49,4 @@ const SESSION_COLUMN_ALIASES = { id: "session_id" };
|
|
|
49
49
|
* `session_id` — {@link SESSION_DEFAULT_ORDER} déjà traduit.
|
|
50
50
|
*/
|
|
51
51
|
const SESSION_DEFAULT_ORDER_SQL = renameOrderFields(SESSION_DEFAULT_ORDER, SESSION_COLUMN_ALIASES);
|
|
52
|
-
//#endregion
|
|
53
52
|
export { SESSION_COLUMN_ALIASES, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_SORTABLE_FIELDS };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -71,6 +71,9 @@ export { defineHttpConfig, httpConfigJsonSchema, } from "./nodefony/config/defin
|
|
|
71
71
|
export { httpConfigSchema } from "./nodefony/config/config.js";
|
|
72
72
|
export type { IHttpConfig, IHttpConfigInput, } from "./nodefony/interfaces/IHttpConfig.js";
|
|
73
73
|
export { decideSend, readBackpressureOptions, type WsBackpressurePolicy, type WsSendDecision, type IWsBackpressureOptions, type IBackpressureSocket, type IBackpressureTarget, } from "./nodefony/src/context/websocket/wsBackpressure.js";
|
|
74
|
+
export { isWebsocketContext } from "./nodefony/src/context/websocket/isWebsocketContext.js";
|
|
75
|
+
export { REQUEST_TRACE_CHANNEL } from "./nodefony/src/context/requestTrace.js";
|
|
76
|
+
export type { IRequestTrace, RequestTraceProtocol, } from "./nodefony/interfaces/IRequestTrace.js";
|
|
74
77
|
export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, } from "./nodefony/src/context/domainMatcher.js";
|
|
75
78
|
export type { DomainPattern, ITrustedHostsConfig, } from "./nodefony/src/context/domainMatcher.js";
|
|
76
79
|
export { MemoryRateLimitStore } from "./nodefony/src/rateLimit/MemoryRateLimitStore.js";
|
|
@@ -79,7 +82,10 @@ export { Context, Session, SessionsService, HttpError, HttpKernel, Response, Htt
|
|
|
79
82
|
export { SseStream, openSseStream, acceptsEventStream, SSE_HEARTBEAT_MS, } from "./nodefony/src/context/http/SseStream.js";
|
|
80
83
|
export type { ISseStreamOptions, ISseSendOptions, } from "./nodefony/src/context/http/SseStream.js";
|
|
81
84
|
export { schemaMismatchOf, type SchemaMismatchConfidence, } from "./nodefony/service/error-renderer.js";
|
|
82
|
-
export
|
|
85
|
+
export { toValidationFields, type IValidationField, } from "./nodefony/service/error-renderer.js";
|
|
86
|
+
export type { ProfileEntry, ProfileSummary, ProfilePhase, ProfileQuery, ProfileSecurity, IProfileBatch, IProfilerReader, } from "./nodefony/src/profiler/Profiler.js";
|
|
87
|
+
export { aggregateProfiles, createProfileAggregator, isFailedProfile, sqlShape, N_PLUS_ONE_MIN_REPEATS, ACTION_SELF_PHASE, NO_ROUTE, TOOLING_URL_PREFIX, } from "./nodefony/src/profiler/aggregate.js";
|
|
88
|
+
export type { IProfileAggregator, IProfileAggregatorOptions, IProfileReport, IRouteStats, IStatusBreakdown, IPhaseTotal, INPlusOne, ISqlShapeStats, IErrorSample, } from "./nodefony/src/profiler/aggregate.js";
|
|
83
89
|
export { FrameProfile } from "./nodefony/src/profiler/FrameProfile.js";
|
|
84
90
|
export type { FrameProfileInit, ProfiledArea, ProfiledResolver, } from "./nodefony/src/profiler/FrameProfile.js";
|
|
85
91
|
export { default as ReverseProxy } from "./nodefony/service/reverse-proxy.js";
|
|
@@ -87,11 +93,11 @@ export { parseProxyOrigin, normalizePrefix, proxyMountProblems, } from "./nodefo
|
|
|
87
93
|
export type { IProxyMount, IProxyMountOptions, ProxyTarget, } from "./nodefony/interfaces/IReverseProxy.js";
|
|
88
94
|
export { resolveUiDelivery, PrebuiltUi, } from "./nodefony/src/assets/prebuiltUi.js";
|
|
89
95
|
export type { UiDeliveryMode, UiDeliveryResolved, IUiDeliveryResolution, IUiDeliveryOptions, IPrebuiltUiOptions, } from "./nodefony/src/assets/prebuiltUi.js";
|
|
90
|
-
export type { IContext, IHttpContext, IWebsocketContext, ServerType, SchemeType, WebSocketStateType, CookiesMap, ISecurityTrace, SecurityOutcome, } from "./nodefony/interfaces/IContext.js";
|
|
96
|
+
export type { IContext, IHttpContext, IWebsocketContext, IWebsocketConnectionState, ServerType, ProtocolName, AlpnProtocol, SchemeType, WebSocketStateType, CookiesMap, ISecurityTrace, SecurityOutcome, } from "./nodefony/interfaces/IContext.js";
|
|
91
97
|
export type { ICookie, ICookieOptions, IWsCookie, SameSiteType, PriorityType, } from "./nodefony/interfaces/ICookie.js";
|
|
92
98
|
export type { ISession, ISessionStorage, ISerializedSession, ISessionSummary, ISessionRecord, ISessionListFilter, ISessionListQuery, SessionIntent, SessionStatusType, SessionStrategyType, FlashBagType, MetaBagType, } from "./nodefony/interfaces/ISession.js";
|
|
93
99
|
export { SESSION_SORTABLE_FIELDS, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_COLUMN_ALIASES, } from "./nodefony/src/session/storage/sessionSort.js";
|
|
94
|
-
export type { IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
|
|
100
|
+
export type { IHttpRequest, RequestBody, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
|
|
95
101
|
export type { IHttpResponse, IWebsocketResponse, } from "./nodefony/interfaces/IResponse.js";
|
|
96
102
|
export type { IHttpKernel } from "./nodefony/interfaces/IHttpKernel.js";
|
|
97
103
|
export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./nodefony/interfaces/IRouting.js";
|
|
@@ -198,6 +198,10 @@ export declare const httpConfigSchema: z.ZodObject<{
|
|
|
198
198
|
stripHeaders: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
199
199
|
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
200
200
|
secure: z.ZodDefault<z.ZodBoolean>;
|
|
201
|
+
accessLog: z.ZodDefault<z.ZodEnum<{
|
|
202
|
+
all: "all";
|
|
203
|
+
errors: "errors";
|
|
204
|
+
}>>;
|
|
201
205
|
}, z.core.$strict>>>;
|
|
202
206
|
}, z.core.$strict>>;
|
|
203
207
|
wsMaxConnectionsPerIp: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -3,7 +3,35 @@ import type { MaybePromise } from "nodefony";
|
|
|
3
3
|
import type { ICookie } from "./ICookie.js";
|
|
4
4
|
import type { ISession } from "./ISession.js";
|
|
5
5
|
import type { HTTPMethodType } from "./IRequest.js";
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Identité du serveur qui a reçu la requête, nommée par le schéma d'URI qu'il
|
|
8
|
+
* sert en LOCAL (RFC 9110 §4.2, RFC 6455 §3) : elle choisit la section de
|
|
9
|
+
* configuration du serveur (`http`, `https`, `websocket`, `websocketSecure`).
|
|
10
|
+
* Ce n'est PAS un protocole — `https` sert HTTP/1.1 et h2 sur le même port ;
|
|
11
|
+
* le protocole du saut se lit dans {@link AlpnProtocol}.
|
|
12
|
+
*/
|
|
13
|
+
export type ServerType = "http" | "https" | "ws" | "wss";
|
|
14
|
+
/**
|
|
15
|
+
* Famille du protocole que le contrôleur traite, nommée comme l'attribut
|
|
16
|
+
* OpenTelemetry `network.protocol.name`.
|
|
17
|
+
*
|
|
18
|
+
* Union OUVERTE : une valeur peut apparaître en version mineure
|
|
19
|
+
* (`"webtransport"` sur HTTP/3) — pas de `switch` exhaustif sans `default`.
|
|
20
|
+
*/
|
|
21
|
+
export type ProtocolName = "http" | "websocket";
|
|
22
|
+
/**
|
|
23
|
+
* Protocole du saut qui atteint CE processus, nommé par le registre IANA des
|
|
24
|
+
* identifiants ALPN — la valeur même que Node expose (`TLSSocket.alpnProtocol`,
|
|
25
|
+
* `Http2Session.alpnProtocol`, qui vaut `"h2c"` sur une session en clair).
|
|
26
|
+
*
|
|
27
|
+
* Union OUVERTE : `"h3"` s'ajoutera en version mineure, sans rupture.
|
|
28
|
+
*/
|
|
29
|
+
export type AlpnProtocol = "http/1.1" | "h2" | "h2c";
|
|
30
|
+
/**
|
|
31
|
+
* Schéma d'URI vu par le CLIENT (RFC 9110 §4.2, RFC 6455 §3) : derrière un
|
|
32
|
+
* proxy de confiance, c'est le schéma qu'il a annoncé (`Forwarded`), pas
|
|
33
|
+
* celui du saut local. Toute décision de sécurité lit ce champ.
|
|
34
|
+
*/
|
|
7
35
|
export type SchemeType = "http" | "https" | "ws" | "wss";
|
|
8
36
|
export type WebSocketStateType = "handshake" | "connected" | "closed" | "error" | "message" | null;
|
|
9
37
|
export type CookiesMap = Record<string, ICookie>;
|
|
@@ -67,8 +95,14 @@ export interface IContext {
|
|
|
67
95
|
csrfProtect: boolean;
|
|
68
96
|
csrfExempt: boolean;
|
|
69
97
|
csrfToken: string | null;
|
|
70
|
-
|
|
98
|
+
/** Famille traitée : `"http"` ou `"websocket"` (union ouverte). */
|
|
99
|
+
type: ProtocolName;
|
|
100
|
+
/** Schéma vu par le client — seul champ que lit une décision de sécurité. */
|
|
71
101
|
scheme: SchemeType;
|
|
102
|
+
/** Protocole du saut local : `"http/1.1"`, `"h2"`, `"h2c"` (union ouverte). */
|
|
103
|
+
alpnProtocol: AlpnProtocol;
|
|
104
|
+
/** Serveur qui a reçu la requête — choisit la configuration par serveur. */
|
|
105
|
+
serverType: ServerType;
|
|
72
106
|
method: HTTPMethodType | null;
|
|
73
107
|
url: string;
|
|
74
108
|
domain: string;
|
|
@@ -93,6 +127,15 @@ export interface IContext {
|
|
|
93
127
|
readonly phases: PhaseTiming[];
|
|
94
128
|
phaseStart(name: PhaseName): void;
|
|
95
129
|
phaseEnd(name: PhaseName): void;
|
|
130
|
+
/**
|
|
131
|
+
* Arrête l'horloge de la requête (au premier appel) et rend sa durée en ms —
|
|
132
|
+
* la SEULE durée de requête du framework, mesurée partout par défaut ; `null`
|
|
133
|
+
* seulement si `timing.enabled` vaut `false`.
|
|
134
|
+
*
|
|
135
|
+
* Membre REQUIS : le journal d'accès et le profileur le lisent sans repli.
|
|
136
|
+
* Une implémentation maison d'`IContext` qui ne mesure rien rend `null`.
|
|
137
|
+
*/
|
|
138
|
+
stopClock(): number | null;
|
|
96
139
|
profiling: boolean;
|
|
97
140
|
securityTrace: ISecurityTrace | null;
|
|
98
141
|
onAfterResponse(fn: AfterResponseHandler): void;
|
|
@@ -139,13 +182,38 @@ export interface IHttpContext extends IContext {
|
|
|
139
182
|
getHost(): string | undefined;
|
|
140
183
|
getUserAgent(): string | undefined;
|
|
141
184
|
}
|
|
142
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Ce qu'un module attache à une connexion WebSocket pour toute sa durée — interface
|
|
187
|
+
* VIDE par construction, qu'un module AUGMENTE par fusion de déclarations.
|
|
188
|
+
*
|
|
189
|
+
* `@nodefony/http` ignore les protocoles bâtis au-dessus de la socket (le JSON-RPC
|
|
190
|
+
* de `@nodefony/realtime`, par exemple) et ne peut pas les importer. Le module qui
|
|
191
|
+
* en porte un déclare son champ ici : {@link IWebsocketContext} et `WebsocketContext`
|
|
192
|
+
* le reçoivent typé, sans conversion chez le lecteur et sans rien coûter à
|
|
193
|
+
* l'exécution — la lecture reste un accès de champ ordinaire.
|
|
194
|
+
*
|
|
195
|
+
* Chaque champ se déclare OPTIONNEL : il n'existe qu'une fois posé par son module
|
|
196
|
+
* (à la poignée de main, en général), et vaut `undefined` avant, comme sur toute
|
|
197
|
+
* connexion que ce module ne sert pas.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* declare module "@nodefony/http" {
|
|
202
|
+
* interface IWebsocketConnectionState {
|
|
203
|
+
* myProtocol?: IMyProtocolState;
|
|
204
|
+
* }
|
|
205
|
+
* }
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export interface IWebsocketConnectionState {
|
|
209
|
+
}
|
|
210
|
+
export interface IWebsocketContext extends IContext, IWebsocketConnectionState {
|
|
143
211
|
acceptedProtocol?: string | undefined;
|
|
144
212
|
connection: unknown;
|
|
145
213
|
rejected: boolean;
|
|
146
214
|
port: number | string;
|
|
147
215
|
wsUrl: URL | null;
|
|
148
|
-
|
|
216
|
+
query: Record<string, string>;
|
|
149
217
|
connect(): Promise<unknown>;
|
|
150
218
|
handle(data?: unknown[]): Promise<object>;
|
|
151
219
|
close(reasonCode: number, description: string): unknown;
|
|
@@ -8,12 +8,18 @@ export interface IRequest {
|
|
|
8
8
|
getHeader(name: string): string | string[] | undefined;
|
|
9
9
|
getHeaders(): Record<string, string | string[] | undefined>;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Le corps DÉCODÉ d'une requête : un objet (JSON, formulaire, champs
|
|
13
|
+
* multipart, XML), une chaîne (`text/*`), ou des octets (`Uint8Array` — tout
|
|
14
|
+
* autre type, et un corps sans `Content-Type`). Le même type d'octets que la
|
|
15
|
+
* socket temps réel : un schéma de route valide l'un comme l'autre.
|
|
16
|
+
*/
|
|
17
|
+
export type RequestBody = Record<string, unknown> | string | Uint8Array;
|
|
11
18
|
export interface IHttpRequest extends IRequest {
|
|
12
19
|
url: URL;
|
|
13
20
|
query: Record<string, unknown>;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
queryFile: unknown[];
|
|
21
|
+
body: RequestBody;
|
|
22
|
+
files: readonly unknown[];
|
|
17
23
|
remoteAddress: string | null | undefined;
|
|
18
24
|
acceptHtml: boolean;
|
|
19
25
|
getRemoteAddress(): string | null | undefined;
|
|
@@ -29,7 +35,6 @@ export interface IHttp2Request extends IHttpRequest {
|
|
|
29
35
|
export interface IWsRequest extends IRequest {
|
|
30
36
|
url: URL;
|
|
31
37
|
query: Record<string, string>;
|
|
32
|
-
queryGet: Record<string, string>;
|
|
33
38
|
path: string;
|
|
34
39
|
cookies?: Record<string, ICookie>;
|
|
35
40
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SchemeType } from "./IContext.js";
|
|
2
|
+
import type { HTTPMethodType } from "./IRequest.js";
|
|
3
|
+
/**
|
|
4
|
+
* Nature de l'opération tracée : une requête HTTP, ou le handshake d'une
|
|
5
|
+
* connexion WebSocket.
|
|
6
|
+
*/
|
|
7
|
+
export type RequestTraceProtocol = "http" | "websocket";
|
|
8
|
+
/**
|
|
9
|
+
* Message publié sur le `TracingChannel` `nodefony.http.request`, le même
|
|
10
|
+
* objet de `start` à `asyncEnd` pour une requête donnée.
|
|
11
|
+
*
|
|
12
|
+
* Les champs d'identité (`requestId`, `protocol`, `method`, `scheme`, `url`)
|
|
13
|
+
* sont posés dès `start`. Les champs de résultat (`route`, `routePath`,
|
|
14
|
+
* `module`, `statusCode`, `durationMs`, `error`) valent `null` jusqu'à la fin
|
|
15
|
+
* de la requête : un abonné les lit sur `asyncEnd`. Tout est en lecture seule
|
|
16
|
+
* pour l'abonné — c'est Nodefony qui les remplit.
|
|
17
|
+
*/
|
|
18
|
+
export interface IRequestTrace {
|
|
19
|
+
/** Identifiant de corrélation de la requête (`X-Request-Id` entrant, sinon généré). */
|
|
20
|
+
readonly requestId: string;
|
|
21
|
+
/** `"http"` pour une requête, `"websocket"` pour un handshake. */
|
|
22
|
+
readonly protocol: RequestTraceProtocol;
|
|
23
|
+
/** Méthode de la requête (`"WEBSOCKET"` pour un handshake). */
|
|
24
|
+
readonly method: HTTPMethodType | null;
|
|
25
|
+
/** Schéma vu par le client, proxy de confiance compris (`url.scheme` d'OpenTelemetry). */
|
|
26
|
+
readonly scheme: SchemeType;
|
|
27
|
+
/** URL complète de la requête, chaîne de requête comprise — l'abonné en tire `url.path` et `url.query`. */
|
|
28
|
+
readonly url: string;
|
|
29
|
+
/** Nom de la route qui a répondu, `null` sans route (statique, 404). */
|
|
30
|
+
readonly route: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Gabarit de la route, préfixe compris (`/users/{id}`) : faible cardinalité,
|
|
33
|
+
* c'est l'attribut `http.route` d'OpenTelemetry. La racine vaut `/`. `null`
|
|
34
|
+
* sans route.
|
|
35
|
+
*/
|
|
36
|
+
readonly routePath: string | null;
|
|
37
|
+
/** Module qui a signé la réponse — le même que celui de la ligne d'accès. */
|
|
38
|
+
readonly module: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Statut rendu au client : celui de l'amont pour une requête relayée, `101`
|
|
41
|
+
* pour un handshake accepté, le statut du refus sinon. `null` si le client
|
|
42
|
+
* est parti avant toute réponse.
|
|
43
|
+
*/
|
|
44
|
+
readonly statusCode: number | null;
|
|
45
|
+
/**
|
|
46
|
+
* Durée de la requête en millisecondes, construction du contexte → dernier
|
|
47
|
+
* octet remis (fin du handshake en WebSocket). `null` si `timing.enabled`
|
|
48
|
+
* vaut `false`.
|
|
49
|
+
*/
|
|
50
|
+
readonly durationMs: number | null;
|
|
51
|
+
/**
|
|
52
|
+
* Exception attrapée par le pipeline, `null` sinon. Un statut 4xx rendu
|
|
53
|
+
* sans exception n'en porte pas ; c'est à l'abonné de classer un statut.
|
|
54
|
+
*/
|
|
55
|
+
readonly error: unknown;
|
|
56
|
+
}
|
|
@@ -59,6 +59,14 @@ export interface IProxyMountOptions {
|
|
|
59
59
|
* ACCEPTÉ que vers la boucle locale (refus au montage sinon).
|
|
60
60
|
*/
|
|
61
61
|
secure?: boolean | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Ligne d'accès des requêtes relayées : `"all"` (défaut) les écrit toutes,
|
|
64
|
+
* `"errors"` seulement celles en échec (statut ≥ 400). Pour un amont qui sert
|
|
65
|
+
* des centaines de fichiers par page (le relais de Vite) : un fichier bien
|
|
66
|
+
* servi n'apprend rien, et ses lignes chassaient les autres de la mémoire
|
|
67
|
+
* du journal.
|
|
68
|
+
*/
|
|
69
|
+
accessLog?: "all" | "errors" | undefined;
|
|
62
70
|
}
|
|
63
71
|
/** Un montage tel que le proxy le garde : préfixe normalisé + réglages. */
|
|
64
72
|
export interface IProxyMount extends IProxyMountOptions {
|
|
@@ -11,6 +11,15 @@ import type { ContextType } from "../service/http-kernel.js";
|
|
|
11
11
|
export interface IResolvedRoute {
|
|
12
12
|
/** Nom de la route (journalisation, diagnostic). */
|
|
13
13
|
readonly name: string;
|
|
14
|
+
/**
|
|
15
|
+
* Gabarit de la route, préfixe compris (`/users/{id}`) — faible cardinalité,
|
|
16
|
+
* publié comme `routePath` sur le canal `nodefony.http.request`.
|
|
17
|
+
*/
|
|
18
|
+
readonly path?: string | undefined;
|
|
19
|
+
/** Module propriétaire — il signe la ligne d'accès de la requête. */
|
|
20
|
+
readonly module?: {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
} | undefined;
|
|
14
23
|
/** Corps laissé en flux (`@Body({ stream: true })`) : le parse est sauté. */
|
|
15
24
|
readonly bodyStream?: boolean | undefined;
|
|
16
25
|
}
|
|
@@ -35,6 +44,13 @@ export interface IRouteResolver {
|
|
|
35
44
|
} | null | undefined;
|
|
36
45
|
/** Nom de l'action résolue. */
|
|
37
46
|
actionName?: string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Ce que l'action a rendu sans que rien ne soit envoyé (`"null"`,
|
|
49
|
+
* `"undefined"`, `"une instance de Book"`…), `null` sinon. Posé seulement
|
|
50
|
+
* sur la branche « l'action répond elle-même » : lu au démontage pour dire,
|
|
51
|
+
* en développement, POURQUOI une requête a attendu son délai d'expiration.
|
|
52
|
+
*/
|
|
53
|
+
readonly unrendered?: string | null | undefined;
|
|
38
54
|
/**
|
|
39
55
|
* Rejoue le match d'une route sur un contexte (WS : handshake puis frames).
|
|
40
56
|
*
|
|
@@ -60,6 +76,16 @@ export interface IRouteResolver {
|
|
|
60
76
|
* n'attendent, sinon sa promesse
|
|
61
77
|
*/
|
|
62
78
|
callController(data?: unknown[], reload?: boolean): MaybePromise<unknown>;
|
|
79
|
+
/**
|
|
80
|
+
* Juge les gardes de l'action résolue (`@IsGranted`, `@RequireScope`,
|
|
81
|
+
* step-up, rôle de la zone) SANS l'exécuter — le pipeline WebSocket l'appelle
|
|
82
|
+
* avant d'accepter le handshake, pour qu'un refus parte en statut HTTP. Un
|
|
83
|
+
* verdict favorable vaut pour la PROCHAINE exécution, qui ne le rejuge pas.
|
|
84
|
+
*
|
|
85
|
+
* @returns `undefined` quand l'action n'est pas gardée ; sinon la promesse du
|
|
86
|
+
* verdict, rompue par un refus (401/403)
|
|
87
|
+
*/
|
|
88
|
+
authorizeAction(): Promise<void> | undefined;
|
|
63
89
|
}
|
|
64
90
|
/**
|
|
65
91
|
* Ce que le pipeline HTTP/WS attend du service `router` — implémenté par
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export type { ICookie, ICookieOptions, IWsCookie, SameSiteType, PriorityType, } from "./ICookie.js";
|
|
2
2
|
export type { ISession, ISessionStorage, ISerializedSession, SessionIntent, SessionStatusType, SessionStrategyType, FlashBagType, MetaBagType, } from "./ISession.js";
|
|
3
3
|
export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, } from "./IUpload.js";
|
|
4
|
-
export type { IRequest, IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType, } from "./IRequest.js";
|
|
4
|
+
export type { IRequest, IHttpRequest, RequestBody, IHttp2Request, IWsRequest, HTTPMethodType, } from "./IRequest.js";
|
|
5
5
|
export type { IResponse, IHttpResponse, IWebsocketResponse } from "./IResponse.js";
|
|
6
|
-
export type { IContext, IHttpContext, IWebsocketContext, ServerType, SchemeType, WebSocketStateType, CookiesMap, } from "./IContext.js";
|
|
6
|
+
export type { IContext, IHttpContext, IWebsocketContext, ServerType, ProtocolName, AlpnProtocol, SchemeType, WebSocketStateType, CookiesMap, } from "./IContext.js";
|
|
7
7
|
export type { IHttpKernel } from "./IHttpKernel.js";
|
|
8
8
|
export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./IRouting.js";
|
|
9
9
|
export type { ISecurityZone, IFirewallGate } from "./ISecurity.js";
|
|
10
|
+
export type { IRequestTrace, RequestTraceProtocol } from "./IRequestTrace.js";
|
|
10
11
|
export type { IProxyMount, IProxyMountOptions, ProxyTarget, } from "./IReverseProxy.js";
|