@nodefony/http 10.0.0-alpha.9 → 10.0.0-beta.2
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 +12 -3
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +6 -4
- package/dist/nodefony/command/assetsPublishCommand.js +1 -1
- package/dist/nodefony/command/certificatesCommand.js +1 -2
- package/dist/nodefony/command/networkCommand.js +1 -1
- package/dist/nodefony/command/proxyGenerateCommand.js +1 -1
- package/dist/nodefony/config/config.js +26 -4
- package/dist/nodefony/interfaces/IRouting.js +1 -0
- package/dist/nodefony/interfaces/ISecurity.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +2 -1
- package/dist/nodefony/service/audit-logger.js +16 -13
- package/dist/nodefony/service/certificates.js +87 -144
- package/dist/nodefony/service/error-renderer.js +11 -8
- package/dist/nodefony/service/http-kernel.js +342 -165
- package/dist/nodefony/service/pretty-request-logger.js +3 -10
- package/dist/nodefony/service/servers/clientError.js +2 -2
- package/dist/nodefony/service/servers/server-http.js +23 -23
- package/dist/nodefony/service/servers/server-https.js +41 -42
- package/dist/nodefony/service/servers/server-static.js +13 -17
- package/dist/nodefony/service/servers/server-websocket-secure.js +11 -10
- package/dist/nodefony/service/servers/server-websocket.js +11 -10
- package/dist/nodefony/service/servers/wsHeartbeat.js +2 -2
- package/dist/nodefony/service/sessions/sessions-service.js +22 -15
- package/dist/nodefony/service/trace.js +3 -2
- package/dist/nodefony/service/upload/upload-service.js +11 -10
- package/dist/nodefony/service/x509.js +324 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -1
- package/dist/nodefony/src/context/Context.js +34 -19
- package/dist/nodefony/src/context/forwarded.js +4 -4
- package/dist/nodefony/src/context/http/HttpContext.js +133 -88
- package/dist/nodefony/src/context/http/Request.js +108 -46
- package/dist/nodefony/src/context/http/Response.js +134 -54
- package/dist/nodefony/src/context/http/parser.js +9 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +35 -11
- package/dist/nodefony/src/context/http2/Response.js +53 -25
- package/dist/nodefony/src/context/metaData.js +1 -1
- package/dist/nodefony/src/context/trustProxy.js +4 -3
- package/dist/nodefony/src/context/websocket/Response.js +13 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +59 -26
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +41 -0
- package/dist/nodefony/src/cookies/cookie.js +3 -3
- package/dist/nodefony/src/errors/httpError.js +6 -6
- package/dist/nodefony/src/errors/thrownMessage.js +27 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +4 -3
- package/dist/nodefony/src/profiler/Profiler.js +4 -4
- package/dist/nodefony/src/servers/kernelServers.js +29 -0
- package/dist/nodefony/src/servers/portBinder.js +65 -23
- package/dist/nodefony/src/session/session.js +18 -10
- package/dist/nodefony/src/session/sessionStoreFailure.js +2 -1
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +24 -9
- package/dist/types/index.d.ts +16 -0
- package/dist/types/nodefony/config/config.d.ts +23 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +24 -7
- package/dist/types/nodefony/interfaces/ICookie.d.ts +18 -18
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -4
- package/dist/types/nodefony/interfaces/IResponse.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRouting.d.ts +87 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +45 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +8 -8
- package/dist/types/nodefony/interfaces/IUpload.d.ts +9 -9
- package/dist/types/nodefony/interfaces/index.d.ts +2 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +14 -10
- package/dist/types/nodefony/service/certificates.d.ts +42 -57
- package/dist/types/nodefony/service/http-kernel.d.ts +192 -42
- package/dist/types/nodefony/service/servers/server-http.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +4 -1
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +6 -2
- package/dist/types/nodefony/service/upload/upload-service.d.ts +1 -1
- package/dist/types/nodefony/service/x509.d.ts +86 -0
- package/dist/types/nodefony/src/context/Context.d.ts +21 -10
- package/dist/types/nodefony/src/context/forwarded.d.ts +3 -3
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +75 -18
- package/dist/types/nodefony/src/context/http/Request.d.ts +39 -2
- package/dist/types/nodefony/src/context/http/Response.d.ts +47 -7
- package/dist/types/nodefony/src/context/http/parser.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +32 -2
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +4 -4
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +11 -2
- package/dist/types/nodefony/src/context/websocket/wsCloseReason.d.ts +21 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +9 -9
- package/dist/types/nodefony/src/errors/httpError.d.ts +7 -7
- package/dist/types/nodefony/src/errors/thrownMessage.d.ts +12 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +28 -28
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +20 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +16 -65
- package/dist/types/nodefony/src/session/session.d.ts +12 -12
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +1 -1
- package/docs/cookies.md +25 -25
- package/docs/index.md +4 -2
- package/docs/observabilite.md +27 -27
- package/docs/rate-limit.md +20 -20
- package/docs/servers.md +82 -79
- package/docs/session.md +74 -69
- package/docs/upload.md +49 -43
- package/package.json +9 -12
|
@@ -4,7 +4,6 @@ import { resolveForwarded } from "../src/context/forwarded.js";
|
|
|
4
4
|
import { MemoryRateLimitStore } from "../src/rateLimit/MemoryRateLimitStore.js";
|
|
5
5
|
import { WsConnectionCounter } from "../src/rateLimit/WsConnectionCounter.js";
|
|
6
6
|
import { compileDomainPatterns, compileTrustedHosts, isDomainAllowed } from "../src/context/domainMatcher.js";
|
|
7
|
-
import Context from "../src/context/Context.js";
|
|
8
7
|
import WebsocketContext from "../src/context/websocket/WebsocketContext.js";
|
|
9
8
|
import HttpContext from "../src/context/http/HttpContext.js";
|
|
10
9
|
import DefaultErrorRenderer from "./error-renderer.js";
|
|
@@ -12,10 +11,12 @@ import DefaultRequestLogger from "./request-logger.js";
|
|
|
12
11
|
import JsonAuditLogger from "./audit-logger.js";
|
|
13
12
|
import PrettyRequestLogger from "./pretty-request-logger.js";
|
|
14
13
|
import { resolveTraceparent } from "./trace.js";
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
14
|
+
import { asServersConfig, configuredServerPort } from "../src/servers/kernelServers.js";
|
|
15
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
|
|
16
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
|
|
17
|
+
import { GcScheduler, Module, RequestContext, Service, finallyMaybe, injectable, isPromise, logColor, nodefonyError, servedUrl, thenMaybe, writeRuntimeState } from "nodefony";
|
|
18
18
|
import cluster from "node:cluster";
|
|
19
|
+
import http from "node:http";
|
|
19
20
|
import Ws from "ws";
|
|
20
21
|
//#region nodefony/service/http-kernel.ts
|
|
21
22
|
const HEALTH_HEADERS = {
|
|
@@ -46,6 +47,11 @@ const perfProbe = process.env.NF_PERF_PROBE === "1" ? {
|
|
|
46
47
|
reqAcceptNs: 0
|
|
47
48
|
} : null;
|
|
48
49
|
if (perfProbe) globalThis.__nfPerfProbe = perfProbe;
|
|
50
|
+
/**
|
|
51
|
+
* Règlement ignoré d'une promesse dont personne n'attend l'issue — UNE fonction
|
|
52
|
+
* de module, jamais une fermeture allouée par requête.
|
|
53
|
+
*/
|
|
54
|
+
const ignoreSettled = () => void 0;
|
|
49
55
|
const serviceName = "HttpKernel";
|
|
50
56
|
const WS_DEV_LOOPBACK = /* @__PURE__ */ new Set([
|
|
51
57
|
"localhost",
|
|
@@ -153,7 +159,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
153
159
|
this.computeSecurityHeaderCaches();
|
|
154
160
|
this._trustProxyChecker = null;
|
|
155
161
|
this._wsOriginPolicy = Object.create(null);
|
|
156
|
-
this.trustedHosts = this.options
|
|
162
|
+
this.trustedHosts = this.options.trustedHosts;
|
|
157
163
|
this.regAlias = this.compileAlias();
|
|
158
164
|
this.configureRateLimit();
|
|
159
165
|
this.configureWsConnectionLimit();
|
|
@@ -239,7 +245,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
239
245
|
this.serviceCerticats = this.get("certificates");
|
|
240
246
|
this.serverStatic = this.get("server-static");
|
|
241
247
|
this.domain = this.kernel?.domain;
|
|
242
|
-
this.trustedHosts = this.options
|
|
248
|
+
this.trustedHosts = this.options.trustedHosts;
|
|
243
249
|
this.regAlias = this.compileAlias();
|
|
244
250
|
this.sessionService = this.get("sessions");
|
|
245
251
|
this.profiler = this.get("profiler");
|
|
@@ -271,7 +277,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
271
277
|
* @returns le {@link TrustProxyChecker} partagé.
|
|
272
278
|
*/
|
|
273
279
|
getTrustProxyChecker() {
|
|
274
|
-
|
|
280
|
+
this._trustProxyChecker ??= buildTrustProxy(this.options.trustProxy);
|
|
275
281
|
return this._trustProxyChecker;
|
|
276
282
|
}
|
|
277
283
|
/**
|
|
@@ -287,7 +293,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
287
293
|
getWsOriginPolicy(cfgKey) {
|
|
288
294
|
let policy = this._wsOriginPolicy[cfgKey];
|
|
289
295
|
if (!policy) {
|
|
290
|
-
const raw = this.options[cfgKey]
|
|
296
|
+
const raw = this.options[cfgKey].allowedOrigins;
|
|
291
297
|
if (raw === true) policy = {
|
|
292
298
|
disabled: true,
|
|
293
299
|
extra: []
|
|
@@ -352,7 +358,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
352
358
|
* gagne toujours sur la config (idempotent — last setter wins).
|
|
353
359
|
*/
|
|
354
360
|
applyRequestLoggerFromConfig() {
|
|
355
|
-
const kernelLog = this.kernel?.options
|
|
361
|
+
const kernelLog = this.kernel?.options.log ?? {};
|
|
356
362
|
let format = kernelLog.requestFormat ?? "auto";
|
|
357
363
|
if (format === "auto") {
|
|
358
364
|
const env = this.kernel?.environment;
|
|
@@ -370,7 +376,17 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
370
376
|
this.requestLogger = new JsonAuditLogger(opts);
|
|
371
377
|
}
|
|
372
378
|
}
|
|
373
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Ouvre le scope `request` de la requête et la confie au pipeline HTTP.
|
|
381
|
+
*
|
|
382
|
+
* Pas `async` : rien n'y est attendu — la fonction rend directement ce que
|
|
383
|
+
* rend le pipeline, sans l'envelopper dans une promesse (#505).
|
|
384
|
+
*
|
|
385
|
+
* @returns le contexte servi, ou sa promesse (celle de {@link handleHttp}).
|
|
386
|
+
* @throws de façon synchrone si le scope `request` n'est pas déclaré — faute
|
|
387
|
+
* de configuration ; l'appelant ({@link onHttpRequest}) l'absorbe.
|
|
388
|
+
*/
|
|
389
|
+
handle(request, response, type) {
|
|
374
390
|
let scope;
|
|
375
391
|
if (perfProbe) {
|
|
376
392
|
const t0 = process.hrtime.bigint();
|
|
@@ -378,7 +394,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
378
394
|
perfProbe.enterScopeNs += Number(process.hrtime.bigint() - t0);
|
|
379
395
|
perfProbe.count++;
|
|
380
396
|
} else scope = this.container?.enterScope("request");
|
|
381
|
-
if (this.kernel?.debug) this.log(logColor.cyanBgBlue(`${request.url}`), "DEBUG",
|
|
397
|
+
if (this.kernel?.debug) this.log(logColor.cyanBgBlue(`${request.url}`), "DEBUG", type);
|
|
382
398
|
return this.handleHttp(scope, request, response, type);
|
|
383
399
|
}
|
|
384
400
|
/**
|
|
@@ -392,13 +408,16 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
392
408
|
* elle, exécute du code utilisateur (`initialize()`) et résout des dépendances
|
|
393
409
|
* DI : elle n'a rien à faire avant que la requête soit autorisée.
|
|
394
410
|
*
|
|
411
|
+
* Synchrone : le match est un calcul pur, rien n'y est attendu (#505).
|
|
412
|
+
*
|
|
395
413
|
* @param checkFirewall - calcule `context.secure` (zone protégée) ; laissé à
|
|
396
414
|
* `false` par un appelant qui l'a déjà tranché.
|
|
415
|
+
* @returns le résolveur de la route, posé sur `context.resolver`.
|
|
397
416
|
* @throws HttpError 404 quand aucune route ne matche, ou l'exception portée
|
|
398
|
-
* par le résolveur (405…) —
|
|
399
|
-
* route inexistante ne devient pas un 401.
|
|
417
|
+
* par le résolveur (405…) — de façon SYNCHRONE, et toujours avant le
|
|
418
|
+
* firewall : une route inexistante ne devient pas un 401.
|
|
400
419
|
*/
|
|
401
|
-
|
|
420
|
+
prepareFrontController(context, checkFirewall = true) {
|
|
402
421
|
if (!this.router) throw new Error("kernel HTTP not ready");
|
|
403
422
|
if (this.firewall && checkFirewall) context.secure = this.firewall.isSecure(context);
|
|
404
423
|
let resolver;
|
|
@@ -429,7 +448,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
429
448
|
* `Resolver.executeAction` créer l'instance une fois la requête autorisée.
|
|
430
449
|
*/
|
|
431
450
|
async handleFrontController(context, checkFirewall = true) {
|
|
432
|
-
return
|
|
451
|
+
return this.prepareFrontController(context, checkFirewall).newController(context);
|
|
433
452
|
}
|
|
434
453
|
/**
|
|
435
454
|
* Handles errors by setting appropriate response codes and messages.
|
|
@@ -484,49 +503,77 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
484
503
|
}
|
|
485
504
|
async onError(error, context, _extraHeaders) {
|
|
486
505
|
if (context) context.error = error;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
this.log(error.toString(), "DEBUG", "onError on closed context");
|
|
496
|
-
return context;
|
|
497
|
-
}
|
|
498
|
-
if (!context.response.isHeaderSent()) {
|
|
499
|
-
try {
|
|
500
|
-
await context.render(result.body);
|
|
501
|
-
} catch (e) {
|
|
502
|
-
this.log(e, "CRITIC");
|
|
503
|
-
throw e;
|
|
504
|
-
}
|
|
505
|
-
return context;
|
|
506
|
-
}
|
|
507
|
-
await context.close();
|
|
506
|
+
if (context instanceof HttpContext) {
|
|
507
|
+
const result = this.errorRenderer.renderHttp(error, context);
|
|
508
|
+
if (error instanceof HttpError || error instanceof nodefonyError) error.code = result.status;
|
|
509
|
+
context.response.setStatusCode(result.status, http.STATUS_CODES[result.status] ?? "Unknown Error");
|
|
510
|
+
if (result.headers) context.response.setHeaders(result.headers);
|
|
511
|
+
if (this.kernel?.debug) this.log(error.toString(), "ERROR");
|
|
512
|
+
if (context.finished || context.sended) {
|
|
513
|
+
this.log(error.toString(), "DEBUG", "onError on closed context");
|
|
508
514
|
return context;
|
|
509
515
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (context.request && !context.rejected) {
|
|
517
|
-
context.reject(wsResult.code, wsResult.reason);
|
|
518
|
-
return context;
|
|
516
|
+
if (!context.response.isHeaderSent()) {
|
|
517
|
+
try {
|
|
518
|
+
await context.render(result.body);
|
|
519
|
+
} catch (e) {
|
|
520
|
+
this.log(e, "CRITIC");
|
|
521
|
+
throw e;
|
|
519
522
|
}
|
|
520
|
-
|
|
521
|
-
|
|
523
|
+
return context;
|
|
524
|
+
}
|
|
525
|
+
await context.close();
|
|
526
|
+
return context;
|
|
527
|
+
}
|
|
528
|
+
if (context instanceof WebsocketContext) try {
|
|
529
|
+
const wsResult = this.errorRenderer.renderWebsocket(error, context);
|
|
530
|
+
if (context.response?.connection) {
|
|
531
|
+
context.close(wsResult.code, wsResult.reason);
|
|
532
|
+
return context;
|
|
522
533
|
}
|
|
523
|
-
|
|
534
|
+
if (context.request && !context.rejected) {
|
|
535
|
+
context.reject(wsResult.code, wsResult.reason);
|
|
536
|
+
return context;
|
|
537
|
+
}
|
|
538
|
+
} catch {
|
|
539
|
+
throw error;
|
|
524
540
|
}
|
|
541
|
+
throw error;
|
|
525
542
|
}
|
|
526
543
|
compileAlias() {
|
|
527
544
|
return compileTrustedHosts(this.domain, this.trustedHosts, this.kernel?.environment === "development");
|
|
528
545
|
}
|
|
529
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Point d'entrée d'une requête HTTP, appelé par les serveurs depuis leur
|
|
548
|
+
* écouteur `request`.
|
|
549
|
+
*
|
|
550
|
+
* Ne lève et ne rejette JAMAIS : dans un écouteur d'événement, une exception
|
|
551
|
+
* deviendrait une erreur non capturée du processus. Ce qui échappe au
|
|
552
|
+
* pipeline a déjà été traité — rendu au client par `onError`, qui journalise
|
|
553
|
+
* en CRITIC l'échec de son propre rendu avant de le relever. C'était le
|
|
554
|
+
* `.catch` vide des serveurs ; il vit désormais ici, une seule fois.
|
|
555
|
+
*
|
|
556
|
+
* @returns `undefined` quand la requête est servie sans pipeline (sonde de
|
|
557
|
+
* santé, 429) ; sinon la promesse de la fin du pipeline — elle ne rejette
|
|
558
|
+
* pas, et personne n'est tenu de l'attendre.
|
|
559
|
+
*/
|
|
560
|
+
onHttpRequest(request, response, type) {
|
|
561
|
+
let pipeline;
|
|
562
|
+
try {
|
|
563
|
+
pipeline = this.dispatchHttpRequest(request, response, type);
|
|
564
|
+
} catch {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
return isPromise(pipeline) ? Promise.resolve(pipeline).then(ignoreSettled, ignoreSettled) : void 0;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Le travail d'{@link onHttpRequest} : en-têtes de transport, sondes de
|
|
571
|
+
* santé, rate-limit, hook `onServerRequest`, puis le pipeline.
|
|
572
|
+
*
|
|
573
|
+
* @returns `undefined` quand la requête est servie ici ; sinon la promesse du
|
|
574
|
+
* pipeline.
|
|
575
|
+
*/
|
|
576
|
+
dispatchHttpRequest(request, response, type) {
|
|
530
577
|
const headerServer = this.options.headerServer;
|
|
531
578
|
if (headerServer != null && headerServer !== "") response.setHeader("Server", headerServer);
|
|
532
579
|
if (this.secContentTypeOptions !== null) response.setHeader("X-Content-Type-Options", this.secContentTypeOptions);
|
|
@@ -534,11 +581,17 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
534
581
|
if (this.secHsts !== null && (type === "https" || type === "http2")) response.setHeader("Strict-Transport-Security", this.secHsts);
|
|
535
582
|
if (this.#healthPaths !== null) {
|
|
536
583
|
const url = request.url;
|
|
537
|
-
if (url === this.#healthPaths.liveness)
|
|
538
|
-
|
|
584
|
+
if (url === this.#healthPaths.liveness) {
|
|
585
|
+
this.#respondHealth(response, true);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (url === this.#healthPaths.readiness) {
|
|
589
|
+
this.#respondHealth(response, !this.#terminating && this.kernel?.servable === true);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
539
592
|
}
|
|
540
593
|
if (this.rateLimiter !== null) {
|
|
541
|
-
const ip = resolveForwarded(request.headers, request.socket
|
|
594
|
+
const ip = resolveForwarded(request.headers, request.socket.remoteAddress, this.getTrustProxyChecker()).clientIp;
|
|
542
595
|
if (ip !== null) {
|
|
543
596
|
const verdict = this.rateLimiter.hit(ip);
|
|
544
597
|
response.setHeader("X-RateLimit-Limit", verdict.limit);
|
|
@@ -553,12 +606,8 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
553
606
|
}
|
|
554
607
|
}
|
|
555
608
|
}
|
|
556
|
-
if (this.listenerCount("onServerRequest"))
|
|
557
|
-
|
|
558
|
-
});
|
|
559
|
-
return this.handle(request, response, type).catch((e) => {
|
|
560
|
-
throw e;
|
|
561
|
-
});
|
|
609
|
+
if (this.listenerCount("onServerRequest")) return this.fireAsync("onServerRequest", request, response, type).then(() => this.handle(request, response, type));
|
|
610
|
+
return this.handle(request, response, type);
|
|
562
611
|
}
|
|
563
612
|
async initServers() {
|
|
564
613
|
const servers = [];
|
|
@@ -623,18 +672,18 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
623
672
|
if (serverHttp?.active && serverHttp.port > 0) {
|
|
624
673
|
ports.push(serverHttp.port);
|
|
625
674
|
urls.push(servedUrl("http", serverHttp.domain, serverHttp.port));
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
675
|
+
const port = configuredServerPort(asServersConfig(this.kernel?.options.servers), "http");
|
|
676
|
+
if (port) desired.push(port);
|
|
628
677
|
}
|
|
629
678
|
if (serverHttps?.active && serverHttps.port > 0) {
|
|
630
679
|
ports.push(serverHttps.port);
|
|
631
680
|
urls.push(servedUrl("https", serverHttps.domain, serverHttps.port));
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
681
|
+
const port = configuredServerPort(asServersConfig(this.kernel?.options.servers), "https");
|
|
682
|
+
if (port) desired.push(port);
|
|
634
683
|
}
|
|
635
684
|
if (ports.length === 0) return;
|
|
636
685
|
writeRuntimeState(process.cwd(), {
|
|
637
|
-
pid: cluster.isWorker ? process.ppid
|
|
686
|
+
pid: cluster.isWorker ? process.ppid : process.pid,
|
|
638
687
|
ports,
|
|
639
688
|
desiredPorts: desired.length > 0 ? desired : void 0,
|
|
640
689
|
urls
|
|
@@ -648,16 +697,22 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
648
697
|
* session (lazy). Remplace l'ancien `sessionAutoStart` global « démarre sur
|
|
649
698
|
* toutes les routes » (le moteur du ×23).
|
|
650
699
|
*
|
|
700
|
+
* Le contrat de retour suit ce qui est réellement attendu : `null` tout de
|
|
701
|
+
* suite quand aucune session n'est à ouvrir (le cas nominal — ni intent, ni
|
|
702
|
+
* cookie), la promesse de la session seulement quand le store est interrogé
|
|
703
|
+
* (#505).
|
|
704
|
+
*
|
|
651
705
|
* @param context - contexte HTTP/HTTP2/WS courant.
|
|
652
|
-
* @returns
|
|
706
|
+
* @returns `null` si la requête n'en requiert aucune ; sinon la promesse de
|
|
707
|
+
* la session active.
|
|
653
708
|
*/
|
|
654
|
-
|
|
709
|
+
startSession(context) {
|
|
655
710
|
if (!this.sessionService) return null;
|
|
656
711
|
const intent = context.sessionIntent;
|
|
657
712
|
if (context.security?.stateless === true) {
|
|
658
713
|
if (intent) {
|
|
659
714
|
const name = context.security.name;
|
|
660
|
-
|
|
715
|
+
this.#statelessIntentVues ??= /* @__PURE__ */ new Set();
|
|
661
716
|
if (!this.#statelessIntentVues.has(name)) {
|
|
662
717
|
this.#statelessIntentVues.add(name);
|
|
663
718
|
this.log(`Zone "${name}" is stateless: the session requested by this route is IGNORED (no session opened, no cookie sent). Drop \`stateless: true\` on the area, or stop requiring a session on its routes.`, "WARNING", "SESSION");
|
|
@@ -666,36 +721,71 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
666
721
|
return null;
|
|
667
722
|
}
|
|
668
723
|
if (!intent && !context.hasSession()) return null;
|
|
669
|
-
return
|
|
724
|
+
return this.sessionService.start(context, intent?.readOnly);
|
|
670
725
|
}
|
|
671
726
|
/**
|
|
672
|
-
* Teardown post-réponse — fire-and-forget depuis
|
|
673
|
-
* `createHttpContext
|
|
674
|
-
*
|
|
727
|
+
* Teardown post-réponse — fire-and-forget depuis l'écouteur `close` posé par
|
|
728
|
+
* `createHttpContext`. Loggue la requête, draine les hooks
|
|
729
|
+
* afterResponse/onFinish, libère le scope DI.
|
|
730
|
+
*
|
|
731
|
+
* Synchrone dans le cas nominal — aucun hook `onAfterResponse`, aucun écouteur
|
|
732
|
+
* `onFinish` : rien n'y est attendu (#505). Ne lève et ne rejette jamais —
|
|
733
|
+
* l'appelant est un écouteur d'événement : une faute est journalisée, et le
|
|
734
|
+
* scope TOUJOURS libéré.
|
|
735
|
+
*
|
|
736
|
+
* @returns `undefined` quand le démontage est terminé ; sinon la promesse de
|
|
737
|
+
* sa fin, qui ne rejette pas.
|
|
675
738
|
*/
|
|
676
|
-
|
|
677
|
-
if (context.finished) return;
|
|
739
|
+
teardownHttp(context, scope) {
|
|
740
|
+
if (context.finished) return void 0;
|
|
678
741
|
try {
|
|
679
742
|
if (context.waitAsync && !context.sended && this.kernel?.environment === "development") this.log(`Action "${context.resolver?.route?.name ?? context.url}" returned a non-renderable value (void/null/class instance) and never sent a response. Use 'return <object|string|number|boolean|Buffer>' (auto-JSON) or send/stream manually.`, "WARNING");
|
|
680
743
|
context.logRequest();
|
|
681
744
|
context.logPhasesVerbose();
|
|
682
745
|
this.profiler?.collect(context);
|
|
683
|
-
|
|
746
|
+
const afterResponse = context._runAfterResponse();
|
|
747
|
+
if (afterResponse !== void 0 || context.listenerCount("onFinish")) return this.finishTeardownHttp(context, scope, afterResponse);
|
|
748
|
+
this.releaseHttpContext(context, scope);
|
|
749
|
+
} catch (e) {
|
|
750
|
+
this.failTeardownHttp(e, context, scope);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Suite ASYNCHRONE du démontage, quand il y a des hooks à attendre :
|
|
755
|
+
* `onAfterResponse` puis `onFinish`, avant de libérer le contexte. Ne rejette
|
|
756
|
+
* jamais (cf {@link teardownHttp}).
|
|
757
|
+
*/
|
|
758
|
+
async finishTeardownHttp(context, scope, afterResponse) {
|
|
759
|
+
try {
|
|
760
|
+
if (afterResponse !== void 0) await afterResponse;
|
|
684
761
|
if (context.listenerCount("onFinish")) await context.fireAsync("onFinish", context);
|
|
762
|
+
this.releaseHttpContext(context, scope);
|
|
763
|
+
} catch (e) {
|
|
764
|
+
this.failTeardownHttp(e, context, scope);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/** Fin d'un contexte HTTP : marqué fini, scope DI libéré, contexte nettoyé. */
|
|
768
|
+
releaseHttpContext(context, scope) {
|
|
769
|
+
context.finished = true;
|
|
770
|
+
if (perfProbe) {
|
|
771
|
+
const t0 = process.hrtime.bigint();
|
|
772
|
+
this.container?.leaveScope(scope);
|
|
773
|
+
perfProbe.leaveScopeNs += Number(process.hrtime.bigint() - t0);
|
|
774
|
+
} else this.container?.leaveScope(scope);
|
|
775
|
+
context.clean();
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* R2 — le démontage est fire-and-forget : un throw (hook onFinish /
|
|
779
|
+
* afterResponse) serait une erreur non capturée du processus. On journalise,
|
|
780
|
+
* et on GARANTIT la libération du scope DI (sinon le scope `request` fuit à
|
|
781
|
+
* chaque hook qui lève).
|
|
782
|
+
*/
|
|
783
|
+
failTeardownHttp(e, context, scope) {
|
|
784
|
+
this.log(e, "ERROR", "TEARDOWN");
|
|
785
|
+
if (!context.finished) {
|
|
685
786
|
context.finished = true;
|
|
686
|
-
|
|
687
|
-
const t0 = process.hrtime.bigint();
|
|
688
|
-
this.container?.leaveScope(scope);
|
|
689
|
-
perfProbe.leaveScopeNs += Number(process.hrtime.bigint() - t0);
|
|
690
|
-
} else this.container?.leaveScope(scope);
|
|
787
|
+
this.container?.leaveScope(scope);
|
|
691
788
|
context.clean();
|
|
692
|
-
} catch (e) {
|
|
693
|
-
this.log(e, "ERROR", "TEARDOWN");
|
|
694
|
-
if (!context.finished) {
|
|
695
|
-
context.finished = true;
|
|
696
|
-
this.container?.leaveScope(scope);
|
|
697
|
-
context.clean();
|
|
698
|
-
}
|
|
699
789
|
}
|
|
700
790
|
}
|
|
701
791
|
createHttpContext(scope, request, response, type) {
|
|
@@ -708,7 +798,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
708
798
|
perfProbe.ctxNs += Number(process.hrtime.bigint() - t0);
|
|
709
799
|
perfProbe.t0 = 0n;
|
|
710
800
|
} else context = new HttpContext(scope, request, response, type);
|
|
711
|
-
response.
|
|
801
|
+
response.on("close", () => {
|
|
712
802
|
if (!response.writableEnded) {
|
|
713
803
|
context._abortIfPending("Connection closed before response finished");
|
|
714
804
|
if (!context.sended) context.response.statusCode = 499;
|
|
@@ -721,87 +811,176 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
721
811
|
throw e;
|
|
722
812
|
}
|
|
723
813
|
}
|
|
724
|
-
|
|
814
|
+
/**
|
|
815
|
+
* Crée le contexte HTTP de la requête et la sert dans sa bulle ALS ; toute
|
|
816
|
+
* erreur — synchrone ou rejet — passe par {@link onError}.
|
|
817
|
+
*
|
|
818
|
+
* Pas `async` : un GET servi par une action synchrone traverse le pipeline
|
|
819
|
+
* sans une seule promesse (#505). Une promesse seulement quand une étape
|
|
820
|
+
* attend, ou quand l'erreur est rendue (`onError`).
|
|
821
|
+
*
|
|
822
|
+
* @returns le contexte servi, ou sa promesse.
|
|
823
|
+
*/
|
|
824
|
+
handleHttp(scope, request, response, type) {
|
|
725
825
|
let context = null;
|
|
726
826
|
try {
|
|
727
827
|
context = this.createHttpContext(scope, request, response, type);
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
context.profiling = profilerQueries !== null;
|
|
733
|
-
return await RequestContext.run({
|
|
734
|
-
requestId: context.requestId,
|
|
735
|
-
scheme: context.scheme,
|
|
736
|
-
traceparent: context.traceparent,
|
|
737
|
-
queries: profilerQueries ?? void 0,
|
|
738
|
-
context
|
|
739
|
-
}, async () => {
|
|
740
|
-
if (this.firewall?.handleCors(context) === 204) {
|
|
741
|
-
context.response.writeHead(204);
|
|
742
|
-
context.response.end();
|
|
743
|
-
return context;
|
|
744
|
-
}
|
|
745
|
-
context.phaseStart("resolve");
|
|
746
|
-
context.resolver = this.router ? this.router.resolve(context) : null;
|
|
747
|
-
context.phaseEnd("resolve");
|
|
748
|
-
this.firewall?.applySecurityHeaders(context);
|
|
749
|
-
if (this.serverStatic && context.resolver?.resolve !== true && !context.resolver?.exception && (this.kernel?.options.servers.statics || this.kernel?.options.statics || this.serverStatic.hasMounts())) {
|
|
750
|
-
response.removeHeader("Content-Type");
|
|
751
|
-
await this.serverStatic.handle(request, response).catch(() => void 0);
|
|
752
|
-
}
|
|
753
|
-
const streamBody = context.resolver?.resolve === true && context.resolver.route?.bodyStream === true;
|
|
754
|
-
context.phaseStart("parse");
|
|
755
|
-
try {
|
|
756
|
-
if (!streamBody) await context.request.initialize();
|
|
757
|
-
} finally {
|
|
758
|
-
context.phaseEnd("parse");
|
|
759
|
-
}
|
|
760
|
-
const ctx = await this.onRequestEnd(context);
|
|
761
|
-
if (ctx instanceof Context) {
|
|
762
|
-
ctx.phaseStart("action");
|
|
763
|
-
try {
|
|
764
|
-
return await ctx.handle();
|
|
765
|
-
} finally {
|
|
766
|
-
ctx.phaseEnd("action");
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return context;
|
|
770
|
-
});
|
|
828
|
+
const created = context;
|
|
829
|
+
const served = this.listenerCount("onCreateContext") ? this.fireAsync("onCreateContext", created).then(() => this.serveHttpContext(created, scope, request, response)) : this.serveHttpContext(created, scope, request, response);
|
|
830
|
+
if (!isPromise(served)) return served;
|
|
831
|
+
return served.catch((e) => this.failHttp(e, created, scope));
|
|
771
832
|
} catch (e) {
|
|
772
|
-
return
|
|
833
|
+
return this.failHttp(e, context, scope);
|
|
773
834
|
}
|
|
774
835
|
}
|
|
775
|
-
|
|
776
|
-
|
|
836
|
+
/**
|
|
837
|
+
* Traceparent, tampon du profileur, puis le pipeline dans la bulle ALS de la
|
|
838
|
+
* requête (`RequestContext.run`).
|
|
839
|
+
*/
|
|
840
|
+
serveHttpContext(context, scope, request, response) {
|
|
841
|
+
context.traceparent = resolveTraceparent(request.headers.traceparent);
|
|
842
|
+
const profilerQueries = this.profiler ? [] : null;
|
|
843
|
+
context.profilerQueries = profilerQueries;
|
|
844
|
+
context.profiling = profilerQueries !== null;
|
|
845
|
+
return RequestContext.run({
|
|
846
|
+
requestId: context.requestId,
|
|
847
|
+
scheme: context.scheme,
|
|
848
|
+
traceparent: context.traceparent,
|
|
849
|
+
queries: profilerQueries ?? void 0,
|
|
850
|
+
context,
|
|
851
|
+
scope
|
|
852
|
+
}, () => this.runHttpPipeline(context, request, response));
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Chemin d'erreur de {@link handleHttp} : libère le scope d'un contexte qui
|
|
856
|
+
* n'a pas pu se construire, puis rend l'erreur au client (`onError`).
|
|
857
|
+
*/
|
|
858
|
+
failHttp(e, context, scope) {
|
|
859
|
+
if (context === null) this.container?.leaveScope(scope);
|
|
860
|
+
return this.onError(e, context);
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Le trajet d'une requête HTTP DANS la bulle ALS : CORS, calque de
|
|
864
|
+
* configuration, route, en-têtes de sécurité, repli statique, corps, gardes,
|
|
865
|
+
* action.
|
|
866
|
+
*
|
|
867
|
+
* Chaque étape rend sa valeur, ou une promesse quand elle attend réellement
|
|
868
|
+
* (préflight à terminer, hook asynchrone, fichier statique, corps à lire,
|
|
869
|
+
* session, pare-feu, action asynchrone) — et la suite ne s'enchaîne par
|
|
870
|
+
* `then` que dans ce cas. Un `await` sur une valeur déjà connue suspendait le
|
|
871
|
+
* pipeline pour rien : ~50 évènements asynchrones par GET (#505).
|
|
872
|
+
*
|
|
873
|
+
* @returns le contexte servi, ou la promesse de ce contexte.
|
|
874
|
+
* @throws l'erreur d'une étape synchrone — {@link handleHttp} la traite comme
|
|
875
|
+
* un rejet (même `catch`, même `onError`).
|
|
876
|
+
*/
|
|
877
|
+
runHttpPipeline(httpContext, request, response) {
|
|
878
|
+
if (this.firewall?.handleCors(httpContext) === 204) {
|
|
879
|
+
httpContext.response.writeHead(204);
|
|
880
|
+
return thenMaybe(httpContext.response.end(), () => httpContext);
|
|
881
|
+
}
|
|
882
|
+
if (this.kernel && this.kernel.listenerCount("onRequestScope") > 0) return this.kernel.fireAsync("onRequestScope", httpContext).then(() => this.routeHttpRequest(httpContext, request, response));
|
|
883
|
+
return this.routeHttpRequest(httpContext, request, response);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Route, en-têtes de sécurité applicatifs, repli statique — puis le corps.
|
|
887
|
+
* N'attend que le repli statique, et seulement quand aucune route ne matche.
|
|
888
|
+
*/
|
|
889
|
+
routeHttpRequest(httpContext, request, response) {
|
|
890
|
+
httpContext.phaseStart("resolve");
|
|
891
|
+
httpContext.resolver = this.router ? this.router.resolve(httpContext) : null;
|
|
892
|
+
httpContext.phaseEnd("resolve");
|
|
893
|
+
this.firewall?.applySecurityHeaders(httpContext);
|
|
894
|
+
if (this.serverStatic && httpContext.resolver?.resolve !== true && !httpContext.resolver?.exception && (asServersConfig(this.kernel?.options.servers)?.statics || this.kernel?.options.statics || this.serverStatic.hasMounts())) {
|
|
895
|
+
response.removeHeader("Content-Type");
|
|
896
|
+
return this.serverStatic.handle(request, response).catch(() => void 0).then(() => this.serveHttpRequest(httpContext));
|
|
897
|
+
}
|
|
898
|
+
return this.serveHttpRequest(httpContext);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Le corps — lu seulement s'il y en a un à lire —, puis les gardes
|
|
902
|
+
* ({@link onRequestEnd}) et l'action. Chacun n'est attendu que s'il attend.
|
|
903
|
+
*/
|
|
904
|
+
serveHttpRequest(httpContext) {
|
|
905
|
+
const streamBody = httpContext.resolver?.resolve === true && httpContext.resolver.route?.bodyStream === true;
|
|
906
|
+
httpContext.phaseStart("parse");
|
|
907
|
+
const parsed = finallyMaybe(() => streamBody ? void 0 : httpContext.request.initialize(), () => httpContext.phaseEnd("parse"));
|
|
908
|
+
return thenMaybe(parsed, () => thenMaybe(this.onRequestEnd(httpContext), (guarded) => {
|
|
909
|
+
guarded.phaseStart("action");
|
|
910
|
+
return finallyMaybe(() => guarded.handle(), () => guarded.phaseEnd("action"));
|
|
911
|
+
}));
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Les gardes d'une requête HTTP, corps lu : en-têtes de scheme, domaine, hook
|
|
915
|
+
* `beforeResolve`, armement de la route, CSRF, session, pare-feu — dans cet
|
|
916
|
+
* ordre.
|
|
917
|
+
*
|
|
918
|
+
* Synchrone tant qu'aucune garde n'attend (#505) : le hook `beforeResolve`
|
|
919
|
+
* n'est attendu que s'il a un écouteur, la session que si la requête en
|
|
920
|
+
* ouvre une, le pare-feu que sur une zone protégée ou à accès contrôlé.
|
|
921
|
+
*
|
|
922
|
+
* @param context - le contexte de la requête.
|
|
923
|
+
* @param error - une erreur survenue en amont : relevée telle quelle.
|
|
924
|
+
* @returns le contexte une fois les gardes passées, ou la promesse de ce
|
|
925
|
+
* contexte quand une garde attend.
|
|
926
|
+
* @throws de façon SYNCHRONE l'erreur reçue, l'exception du résolveur (404,
|
|
927
|
+
* 405…) et le refus CSRF ; un refus du pare-feu ou de session rompt la
|
|
928
|
+
* promesse rendue.
|
|
929
|
+
*/
|
|
930
|
+
onRequestEnd(context, error) {
|
|
777
931
|
if (error) throw error;
|
|
778
|
-
|
|
932
|
+
const schemeHeaders = context.scheme === "https" ? this.options.https.headers : context.scheme === "http" ? this.options.http.headers : null;
|
|
933
|
+
if (schemeHeaders) context.response.setHeaders(schemeHeaders);
|
|
779
934
|
if (this.kernel?.options.domainCheck) this.checkValidDomain(context);
|
|
780
|
-
if (this.listenerCount("beforeResolve"))
|
|
781
|
-
|
|
935
|
+
if (this.listenerCount("beforeResolve")) return this.fireAsync("beforeResolve", context).then(() => this.armAndGuard(context));
|
|
936
|
+
return this.armAndGuard(context);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Suite d'{@link onRequestEnd} : armement de la route, CSRF, session, puis
|
|
940
|
+
* pare-feu. Synchrone tant que ni la session ni le pare-feu n'attendent.
|
|
941
|
+
*/
|
|
942
|
+
armAndGuard(context) {
|
|
943
|
+
this.prepareFrontController(context);
|
|
782
944
|
this.firewall?.enforceCsrf(context);
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
945
|
+
return thenMaybe(this.startSession(context), () => context.secure || context.isControlledAccess ? this.runFirewall(context) : context);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Le pare-feu d'une requête en zone protégée : `handleSecurity`, puis les
|
|
949
|
+
* hooks `afterAuth` (succès) ou `onAuthFailure` (refus, relevé ensuite).
|
|
950
|
+
*
|
|
951
|
+
* @returns la promesse du contexte autorisé.
|
|
952
|
+
* @throws (rejet) le refus du pare-feu, après `onAuthFailure`.
|
|
953
|
+
*/
|
|
954
|
+
async runFirewall(context) {
|
|
955
|
+
context.phaseStart("firewall");
|
|
956
|
+
try {
|
|
786
957
|
try {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
throw authError;
|
|
793
|
-
}
|
|
794
|
-
} finally {
|
|
795
|
-
context.phaseEnd("firewall");
|
|
958
|
+
await this.firewall?.handleSecurity(context);
|
|
959
|
+
if (this.listenerCount("afterAuth")) await this.fireAsync("afterAuth", context);
|
|
960
|
+
} catch (authError) {
|
|
961
|
+
await this.fireAsync("onAuthFailure", context, authError).catch((e) => this.log(e, "ERROR", "onAuthFailure"));
|
|
962
|
+
throw authError;
|
|
796
963
|
}
|
|
797
|
-
|
|
964
|
+
} finally {
|
|
965
|
+
context.phaseEnd("firewall");
|
|
798
966
|
}
|
|
799
967
|
return context;
|
|
800
968
|
}
|
|
801
969
|
createWebsocketContext(scope, req, ws, type) {
|
|
802
970
|
const context = new WebsocketContext(scope, req, ws, type);
|
|
803
|
-
context.once("onFinish",
|
|
804
|
-
|
|
971
|
+
context.once("onFinish", () => {
|
|
972
|
+
this.finishWebsocket(context, scope);
|
|
973
|
+
});
|
|
974
|
+
return context;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Teardown d'une connexion WS à sa fermeture : signal d'abandon, hooks
|
|
978
|
+
* post-réponse, persistance de la session, puis libération du scope.
|
|
979
|
+
* Ne rejette jamais : toute erreur est journalisée (l'appelant est un
|
|
980
|
+
* écouteur d'événement synchrone, qui n'attend rien).
|
|
981
|
+
*/
|
|
982
|
+
async finishWebsocket(context, scope) {
|
|
983
|
+
try {
|
|
805
984
|
if (context.finished) return;
|
|
806
985
|
context._abortIfPending("WebSocket closed");
|
|
807
986
|
await context._runAfterResponse();
|
|
@@ -810,18 +989,19 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
810
989
|
} catch (e) {
|
|
811
990
|
this.log(e, "ERROR", "WS onFinish saveSession");
|
|
812
991
|
}
|
|
813
|
-
this.container?.leaveScope(
|
|
992
|
+
this.container?.leaveScope(scope);
|
|
814
993
|
context.clean();
|
|
815
994
|
context.finished = true;
|
|
816
|
-
})
|
|
817
|
-
|
|
995
|
+
} catch (e) {
|
|
996
|
+
this.log(e, "ERROR", "WS onFinish");
|
|
997
|
+
}
|
|
818
998
|
}
|
|
819
999
|
async onWebsocketRequest(ws, req, type) {
|
|
820
1000
|
const wsConnCounter = this.#wsConnCounter;
|
|
821
1001
|
if (this.rateLimiter !== null || wsConnCounter !== null) {
|
|
822
|
-
const ip = resolveForwarded(req.headers, req.socket
|
|
1002
|
+
const ip = resolveForwarded(req.headers, req.socket.remoteAddress, this.getTrustProxyChecker()).clientIp;
|
|
823
1003
|
if (ip !== null) {
|
|
824
|
-
if (this.rateLimiter
|
|
1004
|
+
if (this.rateLimiter?.hit(ip).limited) {
|
|
825
1005
|
if (ws.readyState === Ws.OPEN) ws.close(1013, "rate limit");
|
|
826
1006
|
return;
|
|
827
1007
|
}
|
|
@@ -834,13 +1014,9 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
834
1014
|
}
|
|
835
1015
|
}
|
|
836
1016
|
}
|
|
837
|
-
await this.fireAsync("onServerRequest", req, null, type)
|
|
838
|
-
throw e;
|
|
839
|
-
});
|
|
1017
|
+
await this.fireAsync("onServerRequest", req, null, type);
|
|
840
1018
|
const scope = this.container?.enterScope("request");
|
|
841
|
-
return this.handleWebsocket(scope, ws, req, type)
|
|
842
|
-
throw e;
|
|
843
|
-
});
|
|
1019
|
+
return this.handleWebsocket(scope, ws, req, type);
|
|
844
1020
|
}
|
|
845
1021
|
async handleWebsocket(scope, ws, req, type) {
|
|
846
1022
|
let context = null;
|
|
@@ -852,7 +1028,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
852
1028
|
}
|
|
853
1029
|
try {
|
|
854
1030
|
if (context) {
|
|
855
|
-
context.traceparent = resolveTraceparent(req.headers
|
|
1031
|
+
context.traceparent = resolveTraceparent(req.headers.traceparent);
|
|
856
1032
|
context.profiling = this.profiler !== null;
|
|
857
1033
|
}
|
|
858
1034
|
const wsRunId = context?.requestId ?? "ws-no-ctx";
|
|
@@ -862,7 +1038,8 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
862
1038
|
requestId: wsRunId,
|
|
863
1039
|
scheme: wsScheme,
|
|
864
1040
|
...wsTrace ? { traceparent: wsTrace } : {},
|
|
865
|
-
context
|
|
1041
|
+
context,
|
|
1042
|
+
scope
|
|
866
1043
|
}, async () => {
|
|
867
1044
|
await this.onConnect(context, error);
|
|
868
1045
|
if (this.firewall && (context?.secure || context?.isControlledAccess)) {
|
|
@@ -922,7 +1099,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
922
1099
|
throw e;
|
|
923
1100
|
}
|
|
924
1101
|
if (!context.sessionStarting) await this.startSession(context);
|
|
925
|
-
return
|
|
1102
|
+
return context.connect();
|
|
926
1103
|
}
|
|
927
1104
|
checkValidDomain(context) {
|
|
928
1105
|
if (context.validDomain) return 200;
|