@nodefony/http 10.0.0-beta.5 → 10.0.0-beta.7

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.
Files changed (130) hide show
  1. package/README.md +24 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
  4. package/dist/index.js +30 -26
  5. package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
  6. package/dist/nodefony/config/config.js +29 -4
  7. package/dist/nodefony/grpc/GrpcCall.js +298 -0
  8. package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
  9. package/dist/nodefony/grpc/index.js +4 -0
  10. package/dist/nodefony/interfaces/IRpcCall.js +1 -0
  11. package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
  12. package/dist/nodefony/service/audit-logger.js +52 -6
  13. package/dist/nodefony/service/error-renderer.js +47 -13
  14. package/dist/nodefony/service/http-kernel.js +306 -49
  15. package/dist/nodefony/service/pretty-request-logger.js +5 -3
  16. package/dist/nodefony/service/request-logger.js +21 -6
  17. package/dist/nodefony/service/reverse-proxy.js +8 -3
  18. package/dist/nodefony/service/servers/clientError.js +53 -4
  19. package/dist/nodefony/service/servers/server-h2c.js +6 -6
  20. package/dist/nodefony/service/servers/server-http.js +6 -4
  21. package/dist/nodefony/service/servers/server-https.js +14 -7
  22. package/dist/nodefony/service/servers/server-static.js +1 -6
  23. package/dist/nodefony/service/servers/server-websocket-secure.js +7 -11
  24. package/dist/nodefony/service/servers/server-websocket.js +7 -11
  25. package/dist/nodefony/service/servers/serverShutdown.js +43 -6
  26. package/dist/nodefony/service/sessions/sessions-service.js +7 -7
  27. package/dist/nodefony/service/upload/upload-service.js +2 -2
  28. package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
  29. package/dist/nodefony/src/context/Context.js +12 -1
  30. package/dist/nodefony/src/context/http/ByteStream.js +364 -0
  31. package/dist/nodefony/src/context/http/HttpContext.js +139 -32
  32. package/dist/nodefony/src/context/http/Request.js +111 -22
  33. package/dist/nodefony/src/context/http/Response.js +459 -37
  34. package/dist/nodefony/src/context/http/SseStream.js +83 -148
  35. package/dist/nodefony/src/context/http/StreamLifecycle.js +582 -0
  36. package/dist/nodefony/src/context/http/parser.js +29 -11
  37. package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
  38. package/dist/nodefony/src/context/http/webResponse.js +56 -0
  39. package/dist/nodefony/src/context/http2/Request.js +1 -1
  40. package/dist/nodefony/src/context/http2/Response.js +55 -12
  41. package/dist/nodefony/src/context/requestTrace.js +6 -2
  42. package/dist/nodefony/src/context/rpcOutcome.js +20 -0
  43. package/dist/nodefony/src/context/transport.js +20 -1
  44. package/dist/nodefony/src/context/websocket/Response.js +15 -3
  45. package/dist/nodefony/src/context/websocket/WebsocketContext.js +86 -18
  46. package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
  47. package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
  48. package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
  49. package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
  50. package/dist/nodefony/src/cookies/cookie.js +57 -14
  51. package/dist/nodefony/src/errors/httpError.js +58 -25
  52. package/dist/nodefony/src/grpc/cors.js +59 -0
  53. package/dist/nodefony/src/grpc/limits.js +16 -0
  54. package/dist/nodefony/src/grpc/refusal.js +271 -0
  55. package/dist/nodefony/src/profiler/Profiler.js +14 -1
  56. package/dist/nodefony/src/profiler/aggregate.js +2 -2
  57. package/dist/nodefony/src/proxy/forward.js +109 -9
  58. package/dist/nodefony/src/proxy/generateProxyConfig.js +97 -17
  59. package/dist/nodefony/src/proxy/rules.js +29 -14
  60. package/dist/nodefony/src/servers/http2Goaway.js +76 -0
  61. package/dist/nodefony/src/servers/http2Limits.js +41 -15
  62. package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
  63. package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
  64. package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
  65. package/dist/types/index.d.ts +11 -2
  66. package/dist/types/nodefony/config/config.d.ts +14 -0
  67. package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
  68. package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
  69. package/dist/types/nodefony/grpc/index.d.ts +15 -0
  70. package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
  71. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
  72. package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
  73. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
  74. package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
  75. package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
  76. package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
  77. package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
  78. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  79. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
  80. package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
  81. package/dist/types/nodefony/service/error-renderer.d.ts +27 -5
  82. package/dist/types/nodefony/service/http-kernel.d.ts +36 -3
  83. package/dist/types/nodefony/service/request-logger.d.ts +10 -0
  84. package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
  85. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +7 -5
  86. package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
  87. package/dist/types/nodefony/src/context/Context.d.ts +9 -1
  88. package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
  89. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +99 -3
  90. package/dist/types/nodefony/src/context/http/Request.d.ts +46 -9
  91. package/dist/types/nodefony/src/context/http/Response.d.ts +188 -8
  92. package/dist/types/nodefony/src/context/http/SseStream.d.ts +33 -8
  93. package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +106 -0
  94. package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
  95. package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
  96. package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
  97. package/dist/types/nodefony/src/context/http2/Response.d.ts +40 -5
  98. package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
  99. package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
  100. package/dist/types/nodefony/src/context/transport.d.ts +17 -0
  101. package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
  102. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +48 -0
  103. package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
  104. package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
  105. package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
  106. package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
  107. package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
  108. package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
  109. package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
  110. package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
  111. package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
  112. package/dist/types/nodefony/src/profiler/Profiler.d.ts +11 -1
  113. package/dist/types/nodefony/src/proxy/forward.d.ts +37 -2
  114. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
  115. package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
  116. package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
  117. package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
  118. package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
  119. package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
  120. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
  121. package/docs/byte-stream.md +406 -0
  122. package/docs/cookies.md +13 -3
  123. package/docs/diagnostics-channel.md +45 -1
  124. package/docs/index.md +8 -0
  125. package/docs/reverse-proxy.md +15 -3
  126. package/docs/servers.md +67 -43
  127. package/docs/session.md +3 -3
  128. package/docs/sse.md +27 -20
  129. package/docs/upload.md +43 -17
  130. package/package.json +38 -18
@@ -1,8 +1,8 @@
1
- import HttpError from "../../errors/httpError.js";
2
- import { hasForwardingHeaders, resolveForwarded } from "../forwarded.js";
3
1
  import { hostnameOf } from "../domainMatcher.js";
4
2
  import { Parser, ParserJson, ParserQs, ParserRaw, ParserXml, acceptParser } from "./parser.js";
5
- import { isCanonicalAuthority, splitTarget } from "./urlFastPath.js";
3
+ import { isCanonicalAuthority, splitAbsoluteForm, splitTarget } from "./urlFastPath.js";
4
+ import HttpError from "../../errors/httpError.js";
5
+ import { hasForwardingHeaders, resolveForwarded } from "../forwarded.js";
6
6
  import { RequestContext, useConfig } from "nodefony";
7
7
  import { createHash, randomUUID } from "node:crypto";
8
8
  import { URL } from "node:url";
@@ -23,6 +23,18 @@ const reg = /(.*)[[][\]]$/u;
23
23
  const unlinkQuiet = (p) => fsp.unlink(p).catch(() => void 0);
24
24
  var BodyHandled = class {};
25
25
  const BODY_DONE = new BodyHandled();
26
+ /**
27
+ * Une réception multipart EN COURS, interruptible de l'extérieur : `fail` rompt
28
+ * la lecture comme le ferait un dépassement de limite (temporaires nettoyés).
29
+ * Posée dans `request.parser` le temps de la lecture seulement — l'allocation
30
+ * est réservée au multipart, déjà lourd.
31
+ */
32
+ var MultipartReception = class {
33
+ fail;
34
+ constructor(fail) {
35
+ this.fail = fail;
36
+ }
37
+ };
26
38
  const CHARSET_ALIASES = {
27
39
  "utf-8": "utf8",
28
40
  utf8: "utf8",
@@ -128,6 +140,9 @@ var HttpRequest = class {
128
140
  * temporaire — lus par `@UploadedFile()` / `@UploadedFiles()`. Les champs
129
141
  * texte du même corps sont dans {@link body}.
130
142
  *
143
+ * Ces temporaires sont EFFACÉS à la fin de la requête : pour garder un
144
+ * fichier, l'action le déplace (`moveAsync`) avant de répondre.
145
+ *
131
146
  * Lecture seule : une requête sans upload rend un vide partagé et gelé,
132
147
  * aucune allocation.
133
148
  */
@@ -271,6 +286,25 @@ var HttpRequest = class {
271
286
  }
272
287
  }
273
288
  /**
289
+ * Lit le plafond du corps et les limites d'envoi tels que les voit CETTE
290
+ * requête : la configuration http, surchargée par le calque que la requête a
291
+ * pu poser avant la lecture du corps (`overlayConfig`, #494), puis par les
292
+ * bornes que la route déclare pour elle-même (option de route `upload`). La
293
+ * route est déjà résolue à ce moment : une route sans bornes propres
294
+ * n'alloue rien. Hors pipeline (aucun scope ouvert), la configuration du
295
+ * module telle quelle.
296
+ */
297
+ readBodyLimits() {
298
+ const options = RequestContext.getScope() !== void 0 ? useConfig("@nodefony/http") : this.context.httpKernel?.module.options;
299
+ const base = options?.upload ?? {};
300
+ const route = this.context.resolver?.route?.upload;
301
+ this.uploadOption = route ? {
302
+ ...base,
303
+ ...route
304
+ } : base;
305
+ this.maxBodySize = options?.maxBodySize ?? 0;
306
+ }
307
+ /**
274
308
  * B1 — Pré-contrôle de taille du corps via l'en-tête `Content-Length` :
275
309
  * rejette en 413 (RFC 9110 §15.5.14 « Content Too Large ») AVANT toute lecture
276
310
  * quand le client annonce une taille supérieure à `maxBodySize`. Rideau de
@@ -279,17 +313,6 @@ var HttpRequest = class {
279
313
  *
280
314
  * @throws {HttpError} 413 si `Content-Length` dépasse `maxBodySize`.
281
315
  */
282
- /**
283
- * Lit le plafond du corps et les limites d'envoi tels que les voit CETTE
284
- * requête : la configuration http, surchargée par le calque que la requête a
285
- * pu poser avant la lecture du corps (`overlayConfig`, #494). Hors pipeline
286
- * (aucun scope ouvert), la configuration du module telle quelle.
287
- */
288
- readBodyLimits() {
289
- const options = RequestContext.getScope() !== void 0 ? useConfig("@nodefony/http") : this.context.httpKernel?.module.options;
290
- this.uploadOption = options?.upload ?? {};
291
- this.maxBodySize = options?.maxBodySize ?? 0;
292
- }
293
316
  enforceBodyLimit() {
294
317
  const max = this.maxBodySize;
295
318
  if (max <= 0) return;
@@ -366,6 +389,7 @@ var HttpRequest = class {
366
389
  return this.parser;
367
390
  }
368
391
  const { fields, files } = await this.streamMultipart(bb);
392
+ this.parser = null;
369
393
  this.body = fields;
370
394
  for (const pf of files) await this.createFileUpload(pf.field, pf.file);
371
395
  this.context.requestEnded = true;
@@ -385,18 +409,33 @@ var HttpRequest = class {
385
409
  return new Promise((resolve, reject) => {
386
410
  const fields = {};
387
411
  const files = [];
412
+ let order = 0;
388
413
  const pending = [];
389
414
  const tempPaths = [];
390
415
  const openStreams = /* @__PURE__ */ new Set();
391
416
  let totalBytes = 0;
392
417
  let aborted = false;
418
+ let received = false;
393
419
  const abort = (err) => {
394
420
  if (aborted) return;
395
421
  aborted = true;
396
422
  this.request.unpipe(bb);
397
423
  this.request.resume();
398
- for (const ws of openStreams) ws.destroy();
399
- Promise.all(tempPaths.map(unlinkQuiet)).finally(() => reject(err));
424
+ const purge = () => {
425
+ Promise.all(tempPaths.map(unlinkQuiet)).finally(() => reject(err));
426
+ };
427
+ let closing = openStreams.size;
428
+ if (closing === 0) {
429
+ purge();
430
+ return;
431
+ }
432
+ for (const ws of openStreams) {
433
+ ws.once("close", () => {
434
+ closing -= 1;
435
+ if (closing === 0) purge();
436
+ });
437
+ ws.destroy();
438
+ }
400
439
  };
401
440
  bb.on("field", (name, value) => {
402
441
  if (aborted) return;
@@ -407,6 +446,8 @@ var HttpRequest = class {
407
446
  stream.resume();
408
447
  return;
409
448
  }
449
+ const slot = order;
450
+ order += 1;
410
451
  const field = reg.exec(fieldname)?.[1] ?? fieldname;
411
452
  const ext = filename ? path.extname(filename) : "";
412
453
  const newFilename = `${randomUUID()}${ext}`;
@@ -431,7 +472,7 @@ var HttpRequest = class {
431
472
  ws.on("close", () => {
432
473
  openStreams.delete(ws);
433
474
  if (aborted) return res();
434
- files.push({
475
+ files[slot] = {
435
476
  field,
436
477
  file: {
437
478
  filepath,
@@ -443,7 +484,7 @@ var HttpRequest = class {
443
484
  hashAlgorithm: algo,
444
485
  hash: hasher ? hasher.digest("hex") : null
445
486
  }
446
- });
487
+ };
447
488
  res();
448
489
  });
449
490
  });
@@ -455,6 +496,7 @@ var HttpRequest = class {
455
496
  bb.on("partsLimit", () => abort(new HttpError("too many parts", 413, this.context)));
456
497
  bb.on("error", (e) => abort(e instanceof Error ? e : new Error(String(e))));
457
498
  bb.on("finish", () => {
499
+ received = true;
458
500
  if (aborted) return;
459
501
  Promise.all(pending).then(() => {
460
502
  if (!aborted) resolve({
@@ -463,10 +505,32 @@ var HttpRequest = class {
463
505
  });
464
506
  }).catch(abort);
465
507
  });
508
+ this.parser = new MultipartReception((error) => {
509
+ if (aborted || received) return false;
510
+ abort(error);
511
+ return true;
512
+ });
466
513
  this.request.pipe(bb);
467
514
  });
468
515
  }
469
516
  /**
517
+ * Interrompt la réception du corps en cours : la promesse de `initialize()`
518
+ * rejette avec `error`, rendue par le chemin d'erreur habituel — comme un
519
+ * corps qui dépasse sa limite (413).
520
+ *
521
+ * Sert le délai de réception HTTP/2 (`requestTimeout`), que `node:http2` ne
522
+ * borne pas. Sans effet hors réception : corps déjà reçu, lu en flux par
523
+ * l'action (`bodyStream`), ou méthode sans corps.
524
+ *
525
+ * @param error - la cause rendue au client (408 au délai dépassé)
526
+ * @returns vrai si une réception en cours a été interrompue
527
+ */
528
+ abortBody(error) {
529
+ const parser = this.parser;
530
+ if (parser instanceof Parser || parser instanceof MultipartReception) return parser.fail(error);
531
+ return false;
532
+ }
533
+ /**
470
534
  * Accumule un champ texte multipart. `foo[]` (ou répétition de `foo`) →
471
535
  * tableau ; sinon valeur scalaire. (Anciennement délégué à formidable.)
472
536
  */
@@ -499,9 +563,25 @@ var HttpRequest = class {
499
563
  }
500
564
  async createFileUpload(name, file) {
501
565
  const fileUpload = await this.context.uploadService?.createUploadFile(file, name);
502
- if (fileUpload) (this.#uploads ??= []).push(fileUpload);
566
+ if (fileUpload) {
567
+ if (this.#uploads === null) {
568
+ this.#uploads = [];
569
+ this.context.onAfterResponse(() => this.#purgeUploads());
570
+ }
571
+ this.#uploads.push(fileUpload);
572
+ }
503
573
  return fileUpload;
504
574
  }
575
+ /**
576
+ * Efface, une fois la requête terminée (réponse envoyée ou client parti),
577
+ * les temporaires que l'action n'a pas déplacés — la règle de PHP. Un
578
+ * fichier déplacé (`move`/`moveAsync`) n'est plus à son chemin temporaire :
579
+ * l'effacement n'y trouve rien, et l'erreur est ignorée.
580
+ */
581
+ #purgeUploads() {
582
+ const uploads = this.#uploads ?? NO_FILES;
583
+ return Promise.all(uploads.map((upload) => unlinkQuiet(upload.parsedFile.filepath))).then(() => void 0);
584
+ }
505
585
  getMethod() {
506
586
  return this.request.method;
507
587
  }
@@ -546,8 +626,14 @@ var HttpRequest = class {
546
626
  host = this.getHost();
547
627
  return host === this.#rawHostSource ? this.#rawHostname : hostnameOf(host);
548
628
  }
629
+ /**
630
+ * L'autorité de la requête : celle de la cible en forme absolue, sinon
631
+ * l'en-tête `Host` (RFC 9112 §3.2.2 — le serveur DOIT alors ignorer `Host`).
632
+ * HTTP/2 lit `:authority` (override).
633
+ */
549
634
  getHost() {
550
- return this.request.headers.host;
635
+ const absolute = splitAbsoluteForm(this.request.url);
636
+ return absolute === null ? this.request.headers.host : absolute.authority;
551
637
  }
552
638
  getRemoteAddress() {
553
639
  if (this.forwarded) return this.forwarded.clientIp;
@@ -556,10 +642,13 @@ var HttpRequest = class {
556
642
  /**
557
643
  * Request-target brut (origin-form) tel que reçu du transport — SOURCE
558
644
  * UNIQUE partagée par la concaténation de {@link getFullUrl} et la découpe
559
- * fast-path du ctor. HTTP/2 lit le pseudo-header `:path` (override).
645
+ * fast-path du ctor. Une cible en forme absolue est ramenée à sa forme
646
+ * origine, son autorité passant par {@link getHost}. HTTP/2 lit le
647
+ * pseudo-header `:path` (override).
560
648
  */
561
649
  getRawTarget() {
562
- return this.request.url;
650
+ const target = this.request.url;
651
+ return splitAbsoluteForm(target)?.target ?? target;
563
652
  }
564
653
  /**
565
654
  * Scheme effectif côté client : `Forwarded`/`X-Forwarded-*` résolu de façon