@nodefony/http 10.0.0-beta.2 → 10.0.0-beta.4
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.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +19 -11
- 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 +45 -6
- package/dist/nodefony/config/defineModuleConfig.js +2 -3
- package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -3
- package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
- package/dist/nodefony/service/audit-logger.js +22 -16
- package/dist/nodefony/service/certificates.js +228 -19
- package/dist/nodefony/service/error-renderer.js +30 -13
- package/dist/nodefony/service/http-kernel.js +331 -82
- package/dist/nodefony/service/pretty-request-logger.js +5 -4
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +336 -0
- 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 +7 -10
- package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
- package/dist/nodefony/service/servers/server-websocket.js +18 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
- package/dist/nodefony/service/sessions/sessions-service.js +34 -20
- 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 +51 -32
- package/dist/nodefony/src/context/domainMatcher.js +21 -4
- package/dist/nodefony/src/context/forwarded.js +3 -4
- package/dist/nodefony/src/context/http/HttpContext.js +98 -46
- package/dist/nodefony/src/context/http/Request.js +34 -38
- package/dist/nodefony/src/context/http/Response.js +39 -7
- package/dist/nodefony/src/context/http/SseStream.js +349 -0
- 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 +22 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/responseEnded.js +26 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/websocket/Response.js +42 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
- 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 +1 -2
- package/dist/nodefony/src/profiler/Profiler.js +108 -30
- package/dist/nodefony/src/profiler/aggregate.js +422 -0
- package/dist/nodefony/src/proxy/forward.js +500 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +188 -0
- 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 +128 -0
- package/dist/nodefony/src/session/session.js +30 -15
- 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 +12 -3
- package/dist/types/nodefony/config/config.d.ts +16 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
- package/dist/types/nodefony/service/certificates.d.ts +147 -5
- package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
- package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
- 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 +2 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
- package/dist/types/nodefony/src/context/Context.d.ts +35 -6
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -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 +15 -5
- package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +88 -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 +69 -0
- package/dist/types/nodefony/src/session/session.d.ts +2 -0
- package/docs/cookies.md +85 -78
- package/docs/index.md +5 -1
- package/docs/observabilite.md +81 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +522 -0
- package/docs/servers.md +378 -230
- package/docs/session.md +180 -180
- package/docs/sse.md +287 -0
- package/docs/upload.md +120 -117
- package/package.json +10 -11
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stripTrailingSlashes } from "nodefony";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/assets/collectAssets.ts
|
|
4
4
|
/** Normalise un préfixe en segment de chemin (`/_assets/x/` → `_assets/x`). */
|
|
5
5
|
const prefixToSegment = (prefix) => stripTrailingSlashes(prefix.replace(/^\/+/, ""));
|
|
6
6
|
/**
|
|
@@ -30,5 +30,4 @@ function planAssetPublish(sources, outDir) {
|
|
|
30
30
|
}
|
|
31
31
|
return plan;
|
|
32
32
|
}
|
|
33
|
-
//#endregion
|
|
34
33
|
export { planAssetPublish };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/assets/prebuiltUi.ts
|
|
4
4
|
/**
|
|
5
5
|
* Résout le mode de livraison de l'UI embarquée d'un module.
|
|
6
6
|
*
|
|
@@ -121,5 +121,4 @@ var PrebuiltUi = class {
|
|
|
121
121
|
return this.template.replaceAll("<script", `<script nonce="${nonce}"`);
|
|
122
122
|
}
|
|
123
123
|
};
|
|
124
|
-
//#endregion
|
|
125
124
|
export { PrebuiltUi, resolveUiDelivery };
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { buildMetaData } from "./metaData.js";
|
|
2
|
+
import { protocolNameOf } from "./transport.js";
|
|
2
3
|
import Cookie, { cookiesParser } from "../cookies/cookie.js";
|
|
3
4
|
import { RequestContext, Service, logColor } from "nodefony";
|
|
4
5
|
import { randomFillSync, randomUUID } from "node:crypto";
|
|
5
6
|
import { performance } from "node:perf_hooks";
|
|
6
7
|
import { AsyncResource } from "node:async_hooks";
|
|
7
|
-
|
|
8
|
+
// nodefony/src/context/Context.ts
|
|
8
9
|
const colorLogEvent = () => logColor.cyanBgBlack("EVENT CONTEXT");
|
|
9
10
|
const PERF_PROBE_SUB = process.env.NF_PERF_PROBE === "1";
|
|
10
|
-
const EVENT_SEVERITY = {
|
|
11
|
-
onRequest: "INFO",
|
|
12
|
-
onSend: "INFO",
|
|
13
|
-
onConnect: "INFO",
|
|
14
|
-
onClose: "INFO"
|
|
15
|
-
};
|
|
16
11
|
let lifecycleEventLogging = null;
|
|
17
12
|
/**
|
|
18
13
|
* Bascule runtime du gate des logs d'events lifecycle. Réservé HORS hot path :
|
|
@@ -47,7 +42,6 @@ var Context = class extends Service {
|
|
|
47
42
|
secure = false;
|
|
48
43
|
security = null;
|
|
49
44
|
cleaned = false;
|
|
50
|
-
isControlledAccess = false;
|
|
51
45
|
validDomain = false;
|
|
52
46
|
finished = false;
|
|
53
47
|
pushAllowed = false;
|
|
@@ -55,7 +49,15 @@ var Context = class extends Service {
|
|
|
55
49
|
requested = false;
|
|
56
50
|
sessionStarting = false;
|
|
57
51
|
domain = "";
|
|
52
|
+
/** Famille traitée (`network.protocol.name` d'OpenTelemetry) — union ouverte. */
|
|
58
53
|
type;
|
|
54
|
+
/** Serveur qui a reçu la requête — choisit la configuration par serveur. */
|
|
55
|
+
serverType;
|
|
56
|
+
/**
|
|
57
|
+
* Protocole du saut local (registre IANA ALPN) — union ouverte. Une connexion
|
|
58
|
+
* WebSocket naît d'un upgrade HTTP/1.1 ; `HttpContext` le constate sur la requête.
|
|
59
|
+
*/
|
|
60
|
+
alpnProtocol = "http/1.1";
|
|
59
61
|
server;
|
|
60
62
|
httpKernel;
|
|
61
63
|
request = null;
|
|
@@ -76,6 +78,14 @@ var Context = class extends Service {
|
|
|
76
78
|
session = null;
|
|
77
79
|
cookieSession = null;
|
|
78
80
|
user = null;
|
|
81
|
+
/**
|
|
82
|
+
* Identifiant de l'appelant AUTHENTIFIÉ, posé par le pare-feu ; `null` hors
|
|
83
|
+
* zone ou pour un anonyme. Lisible au démontage, hors de la bulle ALS : c'est
|
|
84
|
+
* ce que lisent le journal d'audit et le profileur. Distinct de `user`, qui
|
|
85
|
+
* lie la session à son principal de SESSION — y écrire l'identité d'un jeton
|
|
86
|
+
* rattacherait la session d'un autre compte.
|
|
87
|
+
*/
|
|
88
|
+
userIdentifier = null;
|
|
79
89
|
waitAsync = false;
|
|
80
90
|
isJson = false;
|
|
81
91
|
_isHtml = null;
|
|
@@ -133,6 +143,7 @@ var Context = class extends Service {
|
|
|
133
143
|
_phaseIndex = null;
|
|
134
144
|
_afterResponseFns = null;
|
|
135
145
|
_afterResponseFired = false;
|
|
146
|
+
requestStore = null;
|
|
136
147
|
_abortController = null;
|
|
137
148
|
metaData = {
|
|
138
149
|
nodefony: {},
|
|
@@ -140,13 +151,14 @@ var Context = class extends Service {
|
|
|
140
151
|
};
|
|
141
152
|
scheme;
|
|
142
153
|
webSocketState = null;
|
|
143
|
-
constructor(container,
|
|
144
|
-
super(
|
|
154
|
+
constructor(container, serverType) {
|
|
155
|
+
super(serverType, container);
|
|
145
156
|
if (PERF_PROBE_SUB) {
|
|
146
157
|
const p = globalThis.__nfPerfProbe;
|
|
147
158
|
if (p && p.t0 !== 0n) p.svcNs += Number(process.hrtime.bigint() - p.t0);
|
|
148
159
|
}
|
|
149
|
-
this.
|
|
160
|
+
this.serverType = serverType;
|
|
161
|
+
this.type = protocolNameOf(serverType);
|
|
150
162
|
this.set("context", this);
|
|
151
163
|
this.httpKernel = this.get("HttpKernel");
|
|
152
164
|
this.sessionService = this.get("sessions");
|
|
@@ -159,35 +171,24 @@ var Context = class extends Service {
|
|
|
159
171
|
const verbose = (this.kernel?.options)?.timing?.verbose;
|
|
160
172
|
this._timingVerbose = verbose === true;
|
|
161
173
|
}
|
|
162
|
-
this.scheme =
|
|
163
|
-
switch (
|
|
174
|
+
this.scheme = serverType;
|
|
175
|
+
switch (serverType) {
|
|
164
176
|
case "http": {
|
|
165
|
-
this.scheme = "http";
|
|
166
177
|
const server = this.get("server-http");
|
|
167
178
|
this.server = server?.server;
|
|
168
179
|
break;
|
|
169
180
|
}
|
|
170
|
-
case "http2":
|
|
171
181
|
case "https": {
|
|
172
|
-
this.scheme = "https";
|
|
173
182
|
const server = this.get("server-https");
|
|
174
183
|
this.server = server?.server;
|
|
175
184
|
break;
|
|
176
185
|
}
|
|
177
|
-
case "
|
|
178
|
-
this.scheme = "https";
|
|
179
|
-
const server = this.get("server-http3");
|
|
180
|
-
this.server = server?.server ?? null;
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
case "websocket": {
|
|
184
|
-
this.scheme = "ws";
|
|
186
|
+
case "ws": {
|
|
185
187
|
const server = this.get("server-websocket");
|
|
186
188
|
this.server = server?.server;
|
|
187
189
|
break;
|
|
188
190
|
}
|
|
189
|
-
case "
|
|
190
|
-
this.scheme = "wss";
|
|
191
|
+
case "wss": {
|
|
191
192
|
const server = this.get("server-websocket-secure");
|
|
192
193
|
this.server = server?.server;
|
|
193
194
|
break;
|
|
@@ -296,24 +297,43 @@ var Context = class extends Service {
|
|
|
296
297
|
if (this._abortController && !this._abortController.signal.aborted) this._abortController.abort(new Error(reason ?? "Context aborted"));
|
|
297
298
|
}
|
|
298
299
|
log(pci, severity, msgid, msg) {
|
|
299
|
-
if (!msgid) msgid = this.
|
|
300
|
+
if (!msgid) msgid = this.serverType;
|
|
300
301
|
if (this.requestId && RequestContext.getRequestId() === void 0) return RequestContext.run({ requestId: this.requestId }, () => super.log(pci, severity, msgid, msg));
|
|
301
302
|
return super.log(pci, severity, msgid, msg);
|
|
302
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Confie au contexte le magasin `RequestContext` de son unité de travail
|
|
306
|
+
* (requête HTTP, connexion WebSocket), pour qu'il le VIDE à son nettoyage.
|
|
307
|
+
*
|
|
308
|
+
* Un minuteur ou une promesse armé pendant l'unité de travail capture ce
|
|
309
|
+
* magasin et le garde au-delà de la réponse — avec le contexte, l'identité
|
|
310
|
+
* et le jeton du pare-feu. Le vidage ({@link RequestContext.release}) n'y
|
|
311
|
+
* laisse que la corrélation et le scope refermé.
|
|
312
|
+
*
|
|
313
|
+
* @param store - le magasin passé à `RequestContext.run`
|
|
314
|
+
*/
|
|
315
|
+
holdRequestStore(store) {
|
|
316
|
+
this.requestStore = store;
|
|
317
|
+
}
|
|
303
318
|
clean() {
|
|
304
319
|
this.cleaned = true;
|
|
305
320
|
this.httpKernel = null;
|
|
321
|
+
const store = this.requestStore;
|
|
322
|
+
if (store) {
|
|
323
|
+
RequestContext.release(store);
|
|
324
|
+
this.requestStore = null;
|
|
325
|
+
}
|
|
306
326
|
return super.clean();
|
|
307
327
|
}
|
|
308
328
|
/**
|
|
309
329
|
* Log d'un event du cycle de vie — gate boot-time (V2.1) : en production le
|
|
310
|
-
* Pdu n'est PAS construit (return avant toute allocation). Hors prod,
|
|
311
|
-
*
|
|
330
|
+
* Pdu n'est PAS construit (return avant toute allocation). Hors prod : DEBUG,
|
|
331
|
+
* pour le Suivi de requête de Studio — la ligne `req` porte le bilan.
|
|
312
332
|
*/
|
|
313
333
|
logEvent(event) {
|
|
314
334
|
lifecycleEventLogging ??= this.kernel?.environment !== "production";
|
|
315
335
|
if (!lifecycleEventLogging) return;
|
|
316
|
-
this.log(`${colorLogEvent()} ${event}`,
|
|
336
|
+
this.log(`${colorLogEvent()} ${event}`, "DEBUG");
|
|
317
337
|
}
|
|
318
338
|
fire(event, ...args) {
|
|
319
339
|
this.logEvent(event);
|
|
@@ -367,7 +387,7 @@ var Context = class extends Service {
|
|
|
367
387
|
} else line += `${p.name}=…`;
|
|
368
388
|
i++;
|
|
369
389
|
}
|
|
370
|
-
this.log(`TRACE phases [Σ ${total.toFixed(2)}ms] ${line}`, "DEBUG", `${this.
|
|
390
|
+
this.log(`TRACE phases [Σ ${total.toFixed(2)}ms] ${line}`, "DEBUG", `${this.serverType} TIMING`);
|
|
371
391
|
}
|
|
372
392
|
addRequestCookie(cookie) {
|
|
373
393
|
if (cookie instanceof Cookie) return this.cookies[cookie.name] = cookie;
|
|
@@ -426,5 +446,4 @@ var Context = class extends Service {
|
|
|
426
446
|
setContextJson(_encoding = "utf-8") {}
|
|
427
447
|
setContextHtml(_encoding = "utf-8") {}
|
|
428
448
|
};
|
|
429
|
-
//#endregion
|
|
430
449
|
export { Context as default, nextCspNonce, setLifecycleEventLogging };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { escapeRegExp } from "nodefony";
|
|
2
|
-
|
|
2
|
+
// nodefony/src/context/domainMatcher.ts
|
|
3
3
|
/**
|
|
4
4
|
* Matching de domaine (`Host`) — fonctions PURES, politique UNIQUE partagée par
|
|
5
5
|
* les deux étages de Nodefony :
|
|
@@ -15,12 +15,30 @@ import { escapeRegExp } from "nodefony";
|
|
|
15
15
|
* Politique de compilation (sûre, ancrée, ReDoS-free) :
|
|
16
16
|
* - string sans `*` → match EXACT ancré (`^...$`, le `.` est littéral).
|
|
17
17
|
* - string avec `*` → wildcard UN label (`*.example.com` → `^[^.]+\.example\.com$`,
|
|
18
|
-
* RFC
|
|
18
|
+
* RFC 9525 TLS-wildcard : matche `img.example.com`, pas `a.b.example.com` ni `example.com`).
|
|
19
19
|
* - `RegExp` → reprise telle quelle (l'auteur assume l'ancrage).
|
|
20
20
|
*
|
|
21
21
|
* Compilation faite UNE fois (boot / enregistrement de route) ; le test par
|
|
22
22
|
* requête est un simple `RegExp.test` sur une liste pré-compilée (zéro alloc hot-path).
|
|
23
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* Nom d'hôte d'une autorité brute (`host[:port]`), sans port — la valeur que
|
|
26
|
+
* rendrait `URL.hostname` pour une autorité déjà canonique, sans en payer le
|
|
27
|
+
* parse (`indexOf` + `slice`, #508). Un littéral IPv6 garde ses crochets
|
|
28
|
+
* (`[::1]:5151` → `[::1]`) : couper au premier `:` le réduirait à `[`.
|
|
29
|
+
*
|
|
30
|
+
* @param host - autorité brute (`Host`), éventuellement absente
|
|
31
|
+
* @returns le nom d'hôte, `""` si l'autorité est absente
|
|
32
|
+
*/
|
|
33
|
+
function hostnameOf(host) {
|
|
34
|
+
if (!host) return "";
|
|
35
|
+
if (host.charCodeAt(0) === 91) {
|
|
36
|
+
const end = host.indexOf("]");
|
|
37
|
+
return end === -1 ? host : host.slice(0, end + 1);
|
|
38
|
+
}
|
|
39
|
+
const colon = host.indexOf(":");
|
|
40
|
+
return colon === -1 ? host : host.slice(0, colon);
|
|
41
|
+
}
|
|
24
42
|
const DEV_LOOPBACK = [
|
|
25
43
|
"localhost",
|
|
26
44
|
"127.0.0.1",
|
|
@@ -117,5 +135,4 @@ function isDomainAllowed(regAlias, domain) {
|
|
|
117
135
|
for (const reg of regAlias) if (reg.test(domain)) return true;
|
|
118
136
|
return false;
|
|
119
137
|
}
|
|
120
|
-
|
|
121
|
-
export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, resolveTrustedHostNames };
|
|
138
|
+
export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, hostnameOf, isDomainAllowed, resolveTrustedHostNames };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { extractClientIp, resolveFromRight } from "./trustProxy.js";
|
|
2
2
|
import { isIPv4 } from "node:net";
|
|
3
|
-
|
|
3
|
+
// nodefony/src/context/forwarded.ts
|
|
4
4
|
/**
|
|
5
5
|
* Découpe une chaîne sur `sep` au **niveau supérieur uniquement**, en respectant
|
|
6
|
-
* les `quoted-string` RFC
|
|
6
|
+
* les `quoted-string` RFC 9110 §5.6.4 (`value = token / quoted-string`, RFC 7239 §4) : un
|
|
7
7
|
* séparateur à l'intérieur de guillemets (ex. un host quoté, un IPv6) n'est PAS
|
|
8
8
|
* un point de coupe. Les `\"` échappés à l'intérieur des guillemets sont préservés.
|
|
9
9
|
*/
|
|
@@ -40,7 +40,7 @@ function splitTopLevel(input, sep) {
|
|
|
40
40
|
out.push(buf);
|
|
41
41
|
return out;
|
|
42
42
|
}
|
|
43
|
-
/** Retire les guillemets d'une `quoted-string` et déséchappe `\x` (RFC
|
|
43
|
+
/** Retire les guillemets d'une `quoted-string` et déséchappe `\x` (RFC 9110 §5.6.4). */
|
|
44
44
|
function unquote(value) {
|
|
45
45
|
const v = value.trim();
|
|
46
46
|
if (v.length >= 2 && v.startsWith("\"") && v.endsWith("\"")) return v.slice(1, -1).replace(/\\(.)/gu, "$1");
|
|
@@ -181,5 +181,4 @@ function resolveForwarded(headers, socketAddress, checker) {
|
|
|
181
181
|
function hasForwardingHeaders(headers) {
|
|
182
182
|
return Boolean(headers.forwarded || headers["x-forwarded-for"] || headers["x-forwarded-proto"] || headers["x-forwarded-scheme"] || headers["x-forwarded-host"]);
|
|
183
183
|
}
|
|
184
|
-
//#endregion
|
|
185
184
|
export { forwardedNodeIp, hasForwardingHeaders, parseForwarded, resolveForwarded };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import HttpError from "../../errors/httpError.js";
|
|
2
|
+
import { responseEnded } from "../responseEnded.js";
|
|
3
|
+
import { alpnProtocolOf } from "../transport.js";
|
|
2
4
|
import Cookie from "../../cookies/cookie.js";
|
|
3
5
|
import Context from "../Context.js";
|
|
4
6
|
import { sanitizeRequestId } from "../requestId.js";
|
|
@@ -7,25 +9,63 @@ import HttpResponse from "./Response.js";
|
|
|
7
9
|
import Http2Request from "../http2/Request.js";
|
|
8
10
|
import Http2Response from "../http2/Response.js";
|
|
9
11
|
import { describeSessionStoreFailure } from "../../session/sessionStoreFailure.js";
|
|
10
|
-
import {
|
|
12
|
+
import { openSseStream } from "./SseStream.js";
|
|
13
|
+
import { finallyMaybe, isPromise, thenMaybe, typeOf } from "nodefony";
|
|
11
14
|
import url, { URL } from "node:url";
|
|
12
|
-
|
|
15
|
+
// nodefony/src/context/http/HttpContext.ts
|
|
16
|
+
/**
|
|
17
|
+
* Même URL (chemin, requête, hôte) sous un autre schéma et un autre port.
|
|
18
|
+
* `port` absent → port par défaut du schéma (omis de l'URL).
|
|
19
|
+
*
|
|
20
|
+
* @param from - URL de la requête.
|
|
21
|
+
* @param scheme - schéma cible.
|
|
22
|
+
* @param port - port du serveur cible, s'il est connu.
|
|
23
|
+
* @returns l'URL absolue de redirection.
|
|
24
|
+
*/
|
|
25
|
+
function switchUrlScheme(from, scheme, port) {
|
|
26
|
+
const target = new URL(from.href);
|
|
27
|
+
target.protocol = `${scheme}:`;
|
|
28
|
+
target.port = port === void 0 ? "" : String(port);
|
|
29
|
+
return target.href;
|
|
30
|
+
}
|
|
13
31
|
const socketActiveContext = /* @__PURE__ */ new WeakMap();
|
|
14
32
|
const socketTimeoutArmed = /* @__PURE__ */ new WeakSet();
|
|
33
|
+
/**
|
|
34
|
+
* Gestionnaire `timeout` PERMANENT d'une socket h1, fabriqué hors de toute
|
|
35
|
+
* méthode : une fermeture créée dans `HttpContext.setTimeout()` partageait
|
|
36
|
+
* l'environnement d'une autre qui lit `this`, et retenait ainsi le contexte de
|
|
37
|
+
* la PREMIÈRE requête de la connexion tant que celle-ci vivait (#561).
|
|
38
|
+
*
|
|
39
|
+
* @param socket - la socket h1 dont on route le délai d'inactivité
|
|
40
|
+
* @returns l'écouteur à attacher une fois par socket
|
|
41
|
+
*/
|
|
42
|
+
function idleSocketTimeoutHandler(socket) {
|
|
43
|
+
return () => {
|
|
44
|
+
const ctx = socketActiveContext.get(socket);
|
|
45
|
+
if (ctx !== void 0 && !ctx.cleaned && !ctx.response.response?.writableEnded) {
|
|
46
|
+
ctx._onTimeout();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
socket.destroy();
|
|
50
|
+
};
|
|
51
|
+
}
|
|
15
52
|
const PERF_PROBE_SUB = process.env.NF_PERF_PROBE === "1";
|
|
16
53
|
var HttpContext = class extends Context {
|
|
17
54
|
proxy = null;
|
|
55
|
+
/** Socket h1 inscrite dans `socketActiveContext` — retirée par `clean()`. */
|
|
56
|
+
activeSocket = null;
|
|
18
57
|
isRedirect = false;
|
|
19
58
|
sended = false;
|
|
20
59
|
request;
|
|
21
60
|
response;
|
|
22
61
|
uploadService;
|
|
23
|
-
constructor(container, request, response,
|
|
24
|
-
super(container,
|
|
62
|
+
constructor(container, request, response, serverType) {
|
|
63
|
+
super(container, serverType);
|
|
64
|
+
this.alpnProtocol = alpnProtocolOf(request);
|
|
25
65
|
this.uploadService = this.get("upload");
|
|
26
66
|
const perfSub = PERF_PROBE_SUB ? globalThis.__nfPerfProbe : void 0;
|
|
27
67
|
if (perfSub && perfSub.t0 !== 0n) perfSub.uploadNs += Number(process.hrtime.bigint() - perfSub.t0);
|
|
28
|
-
if (this.
|
|
68
|
+
if (this.alpnProtocol === "h2" || this.alpnProtocol === "h2c") {
|
|
29
69
|
this.request = new Http2Request(request, this);
|
|
30
70
|
this.response = new Http2Response(response, this);
|
|
31
71
|
} else {
|
|
@@ -86,7 +126,7 @@ var HttpContext = class extends Context {
|
|
|
86
126
|
_onTimeout() {
|
|
87
127
|
if (this.listenerCount("onTimeout")) this.fire("onTimeout", this);
|
|
88
128
|
this._abortIfPending("Request timeout");
|
|
89
|
-
let error
|
|
129
|
+
let error;
|
|
90
130
|
if (this.response.stream) error = new HttpError("Gateway Timeout", 504, this);
|
|
91
131
|
else error = new HttpError("Request Timeout", 408, this);
|
|
92
132
|
this.httpKernel?.onError(error, this);
|
|
@@ -126,24 +166,39 @@ var HttpContext = class extends Context {
|
|
|
126
166
|
if (!res) return;
|
|
127
167
|
if (this.response.stream) {
|
|
128
168
|
res.setTimeout(this.response.timeout, () => {
|
|
129
|
-
if (!
|
|
169
|
+
if (!responseEnded(res)) this._onTimeout();
|
|
130
170
|
});
|
|
131
171
|
return;
|
|
132
172
|
}
|
|
133
173
|
const socket = res.socket;
|
|
134
174
|
if (!socket) return;
|
|
135
175
|
socketActiveContext.set(socket, this);
|
|
176
|
+
this.activeSocket = socket;
|
|
136
177
|
const ms = this.response.timeout;
|
|
137
178
|
if (socket.timeout !== ms) socket.setTimeout(ms);
|
|
138
179
|
if (!socketTimeoutArmed.has(socket)) {
|
|
139
180
|
socketTimeoutArmed.add(socket);
|
|
140
|
-
socket.on("timeout", ()
|
|
141
|
-
const ctx = socketActiveContext.get(socket);
|
|
142
|
-
if (ctx && !ctx.response.response?.writableEnded) ctx._onTimeout();
|
|
143
|
-
});
|
|
181
|
+
socket.on("timeout", idleSocketTimeoutHandler(socket));
|
|
144
182
|
}
|
|
145
183
|
}
|
|
146
184
|
/**
|
|
185
|
+
* Nettoie le contexte, et le DÉTACHE de sa socket h1 : la connexion
|
|
186
|
+
* persistante survit à la requête, et l'entrée de `socketActiveContext`
|
|
187
|
+
* retiendrait sinon tout le contexte jusqu'à sa fermeture. Seule l'entrée qui
|
|
188
|
+
* pointe encore sur CE contexte est retirée — une requête suivante sur la
|
|
189
|
+
* même socket a pu la réécrire. Le gestionnaire `timeout` de la socket ne
|
|
190
|
+
* trouve alors rien et prend le chemin « socket inactive → `destroy()` »,
|
|
191
|
+
* celui qu'il prenait déjà pour un contexte nettoyé.
|
|
192
|
+
*/
|
|
193
|
+
clean() {
|
|
194
|
+
const socket = this.activeSocket;
|
|
195
|
+
if (socket) {
|
|
196
|
+
if (socketActiveContext.get(socket) === this) socketActiveContext.delete(socket);
|
|
197
|
+
this.activeSocket = null;
|
|
198
|
+
}
|
|
199
|
+
super.clean();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
147
202
|
* Sérialise la donnée selon son type (JSON, HTML, texte), pose statut et
|
|
148
203
|
* en-têtes, puis l'envoie ({@link send}). Synchrone quand l'envoi l'est (#505).
|
|
149
204
|
*
|
|
@@ -229,7 +284,7 @@ var HttpContext = class extends Context {
|
|
|
229
284
|
}
|
|
230
285
|
/** Hook `onSend` (attendu seulement s'il a un écouteur), puis en-têtes et corps. */
|
|
231
286
|
emitAndWrite(body, encoding) {
|
|
232
|
-
if (body) this.response.setBody(body);
|
|
287
|
+
if (body || body === "") this.response.setBody(body);
|
|
233
288
|
if (this.listenerCount("onSend") > 0) return this.fireAsync("onSend", this.response, this).then(() => this.writeHeadAndBody(body, encoding));
|
|
234
289
|
return this.writeHeadAndBody(body, encoding);
|
|
235
290
|
}
|
|
@@ -265,6 +320,18 @@ var HttpContext = class extends Context {
|
|
|
265
320
|
return this.close();
|
|
266
321
|
});
|
|
267
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Répond par un flux d'événements serveur (`text/event-stream`).
|
|
325
|
+
*
|
|
326
|
+
* La requête ne se termine qu'à `close()` du flux, ou au départ du client.
|
|
327
|
+
*
|
|
328
|
+
* @param options - battement de cœur, délai `retry:` initial.
|
|
329
|
+
* @returns le flux, ou sa promesse quand une session est à sauver d'abord.
|
|
330
|
+
* @throws HttpError 500 quand la réponse est déjà partie.
|
|
331
|
+
*/
|
|
332
|
+
openSse(options) {
|
|
333
|
+
return openSseStream(this, options);
|
|
334
|
+
}
|
|
268
335
|
flush(chunk, encoding) {
|
|
269
336
|
return this.response.flush(chunk, encoding);
|
|
270
337
|
}
|
|
@@ -288,41 +355,27 @@ var HttpContext = class extends Context {
|
|
|
288
355
|
if (typeof Url === "object") return this.response.redirect(url.format(Url), status, headers);
|
|
289
356
|
return this.response.redirect(Url, status, headers);
|
|
290
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Redirige vers la même URL en HTTPS, sur le port du serveur HTTPS lié —
|
|
360
|
+
* ou le port par défaut derrière un proxy, dont le port public est inconnu.
|
|
361
|
+
*
|
|
362
|
+
* @param status - code de redirection (défaut de `redirect`).
|
|
363
|
+
* @param headers - en-têtes ajoutés à la réponse.
|
|
364
|
+
*/
|
|
291
365
|
redirectHttps(status, headers) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (this.proxy) urlExtend = {
|
|
295
|
-
protocol: "https",
|
|
296
|
-
href: "",
|
|
297
|
-
host: ""
|
|
298
|
-
};
|
|
299
|
-
else urlExtend = {
|
|
300
|
-
protocol: "https",
|
|
301
|
-
port: this.httpKernel?.httpsPort || 443,
|
|
302
|
-
href: "",
|
|
303
|
-
host: ""
|
|
304
|
-
};
|
|
305
|
-
const urlChange = extend({}, this.request.url, urlExtend);
|
|
306
|
-
const newUrl = url.format(urlChange);
|
|
307
|
-
return this.redirect(newUrl, status, headers);
|
|
366
|
+
const port = this.proxy ? void 0 : this.httpKernel?.httpsPort;
|
|
367
|
+
return this.redirect(switchUrlScheme(this.request.url, "https", port), status, headers);
|
|
308
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Redirige vers la même URL en HTTP, sur le port du serveur HTTP lié — ou
|
|
371
|
+
* le port par défaut derrière un proxy.
|
|
372
|
+
*
|
|
373
|
+
* @param status - code de redirection (défaut de `redirect`).
|
|
374
|
+
* @param headers - en-têtes ajoutés à la réponse.
|
|
375
|
+
*/
|
|
309
376
|
redirectHttp(status, headers) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (this.proxy) urlExtend = {
|
|
313
|
-
protocol: "http",
|
|
314
|
-
href: "",
|
|
315
|
-
host: ""
|
|
316
|
-
};
|
|
317
|
-
else urlExtend = {
|
|
318
|
-
protocol: "http",
|
|
319
|
-
port: this.httpKernel?.httpPort || 80,
|
|
320
|
-
href: "",
|
|
321
|
-
host: ""
|
|
322
|
-
};
|
|
323
|
-
const urlChange = extend({}, this.request.url, urlExtend);
|
|
324
|
-
const newUrl = url.format(urlChange);
|
|
325
|
-
return this.redirect(newUrl, status, headers);
|
|
377
|
+
const port = this.proxy ? void 0 : this.httpKernel?.httpPort;
|
|
378
|
+
return this.redirect(switchUrlScheme(this.request.url, "http", port), status, headers);
|
|
326
379
|
}
|
|
327
380
|
getHostName() {
|
|
328
381
|
return this.request.getHostName();
|
|
@@ -363,5 +416,4 @@ var HttpContext = class extends Context {
|
|
|
363
416
|
this.response.setContentType("html", encoding);
|
|
364
417
|
}
|
|
365
418
|
};
|
|
366
|
-
|
|
367
|
-
export { HttpContext as default };
|
|
419
|
+
export { HttpContext as default, switchUrlScheme };
|