@nodefony/http 10.0.0-beta.3 → 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.
Files changed (124) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +12 -10
  4. package/dist/nodefony/command/assetsPublishCommand.js +5 -3
  5. package/dist/nodefony/command/certificatesCommand.js +5 -3
  6. package/dist/nodefony/command/networkCommand.js +5 -3
  7. package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
  8. package/dist/nodefony/config/config.js +1 -2
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  11. package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
  12. package/dist/nodefony/service/audit-logger.js +20 -13
  13. package/dist/nodefony/service/certificates.js +97 -13
  14. package/dist/nodefony/service/error-renderer.js +29 -12
  15. package/dist/nodefony/service/http-kernel.js +118 -43
  16. package/dist/nodefony/service/pretty-request-logger.js +5 -4
  17. package/dist/nodefony/service/request-logger.js +24 -16
  18. package/dist/nodefony/service/reverse-proxy.js +3 -3
  19. package/dist/nodefony/service/servers/clientError.js +22 -3
  20. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  21. package/dist/nodefony/service/servers/server-http.js +5 -8
  22. package/dist/nodefony/service/servers/server-https.js +12 -24
  23. package/dist/nodefony/service/servers/server-static.js +3 -3
  24. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  25. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  26. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  27. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  28. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  29. package/dist/nodefony/service/trace.js +1 -2
  30. package/dist/nodefony/service/upload/upload-service.js +5 -8
  31. package/dist/nodefony/service/x509.js +1 -2
  32. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  33. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  34. package/dist/nodefony/src/context/Context.js +23 -31
  35. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  36. package/dist/nodefony/src/context/forwarded.js +1 -2
  37. package/dist/nodefony/src/context/http/HttpContext.js +7 -6
  38. package/dist/nodefony/src/context/http/Request.js +33 -29
  39. package/dist/nodefony/src/context/http/Response.js +23 -6
  40. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  41. package/dist/nodefony/src/context/http/parser.js +36 -10
  42. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  43. package/dist/nodefony/src/context/http2/Request.js +4 -8
  44. package/dist/nodefony/src/context/http2/Response.js +22 -9
  45. package/dist/nodefony/src/context/metaData.js +1 -2
  46. package/dist/nodefony/src/context/requestId.js +1 -2
  47. package/dist/nodefony/src/context/responseEnded.js +1 -2
  48. package/dist/nodefony/src/context/transport.js +45 -0
  49. package/dist/nodefony/src/context/trustProxy.js +1 -2
  50. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  51. package/dist/nodefony/src/context/websocket/WebsocketContext.js +92 -17
  52. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  53. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  54. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  55. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  56. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  57. package/dist/nodefony/src/cookies/cookie.js +3 -7
  58. package/dist/nodefony/src/errors/httpError.js +1 -2
  59. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  60. package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
  61. package/dist/nodefony/src/profiler/Profiler.js +102 -20
  62. package/dist/nodefony/src/profiler/aggregate.js +422 -0
  63. package/dist/nodefony/src/proxy/forward.js +29 -5
  64. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  65. package/dist/nodefony/src/proxy/rules.js +12 -6
  66. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  67. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  68. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  69. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  70. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  71. package/dist/nodefony/src/servers/portBinder.js +41 -10
  72. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  73. package/dist/nodefony/src/session/session.js +2 -3
  74. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  75. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  76. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  77. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  78. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  79. package/dist/types/index.d.ts +7 -3
  80. package/dist/types/nodefony/interfaces/IContext.d.ts +63 -4
  81. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  82. package/dist/types/nodefony/interfaces/IRouting.d.ts +10 -0
  83. package/dist/types/nodefony/interfaces/index.d.ts +2 -2
  84. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  85. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  86. package/dist/types/nodefony/service/http-kernel.d.ts +46 -10
  87. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  88. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  89. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  90. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  91. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  92. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  93. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  94. package/dist/types/nodefony/src/context/Context.d.ts +13 -4
  95. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +1 -1
  96. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  97. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  98. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  99. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  100. package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
  101. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  102. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  103. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
  104. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  105. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
  106. package/dist/types/nodefony/src/profiler/Profiler.d.ts +68 -15
  107. package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
  108. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  109. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  110. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  111. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  112. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  113. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  114. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  115. package/docs/cookies.md +85 -78
  116. package/docs/observabilite.md +80 -80
  117. package/docs/rate-limit.md +83 -83
  118. package/docs/reverse-proxy.md +54 -54
  119. package/docs/servers.md +338 -224
  120. package/docs/session.md +179 -179
  121. package/docs/sse.md +24 -20
  122. package/docs/upload.md +120 -117
  123. package/package.json +8 -9
  124. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -11,11 +11,13 @@ import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
11
11
  import Ws from "ws";
12
12
  import httpServer from "../service/servers/server-http.js";
13
13
  import httpsServer from "../service/servers/server-https.js";
14
+ import h2cServer from "../service/servers/server-h2c.js";
14
15
  import websocketServer from "../service/servers/server-websocket.js";
15
16
  import websocketSecureServer from "../service/servers/server-websocket-secure.js";
16
17
  import Statics from "./servers/server-static.js";
17
18
  import type ReverseProxy from "./reverse-proxy.js";
18
19
  import WebsocketContext from "../src/context/websocket/WebsocketContext.js";
20
+ import type { IUpgradeAdmission } from "../src/servers/upgradeDispatch.js";
19
21
  import HttpContext from "../src/context/http/HttpContext.js";
20
22
  import Context, { HTTPMethod, WebSocketState } from "../src/context/Context.js";
21
23
  import Certicates from "./certificates.js";
@@ -42,9 +44,8 @@ export type httpRequest = http.IncomingMessage | http2.Http2ServerRequest;
42
44
  export type httpResponse = http.ServerResponse | http2.Http2ServerResponse;
43
45
  export { responseEnded } from "../src/context/responseEnded.js";
44
46
  export type ContextType = WebsocketContext | HttpContext | Context;
45
- export type ServerType = "http" | "https" | "http2" | "http3" | "websocket" | "websocket-secure";
46
- export type responseTimeoutType = "http" | "https" | "http2" | "http3";
47
- export type SchemeType = "http" | "https" | "ws" | "wss";
47
+ export type { ServerType, SchemeType } from "../interfaces/IContext.js";
48
+ import type { ServerType, SchemeType } from "../interfaces/IContext.js";
48
49
  export interface WsMetaData {
49
50
  type?: "message" | "handshake" | undefined;
50
51
  state?: WebSocketState | undefined;
@@ -119,8 +120,6 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
119
120
  responseTimeout: {
120
121
  http: number;
121
122
  https: number;
122
- http2: number;
123
- http3: number;
124
123
  };
125
124
  closeTimeOutWs: {
126
125
  ws: number;
@@ -383,7 +382,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
383
382
  * pipeline.
384
383
  */
385
384
  private dispatchHttpRequest;
386
- initServers(): Promise<(httpServer | httpsServer | websocketServer | websocketSecureServer)[]>;
385
+ initServers(): Promise<(httpServer | httpsServer | h2cServer | websocketServer | websocketSecureServer)[]>;
387
386
  /**
388
387
  * Publie les ports EFFECTIFS du runtime (state file), une fois l'écoute établie.
389
388
  *
@@ -542,7 +541,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
542
541
  * @throws (rejet) le refus du pare-feu, après `onAuthFailure`.
543
542
  */
544
543
  private runFirewall;
545
- createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws, type: ServerType): WebsocketContext;
544
+ createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws | null, type: ServerType): WebsocketContext;
546
545
  /**
547
546
  * Teardown d'une connexion WS à sa fermeture : signal d'abandon, hooks
548
547
  * post-réponse, persistance de la session, puis libération du scope.
@@ -550,10 +549,47 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
550
549
  * écouteur d'événement synchrone, qui n'attend rien).
551
550
  */
552
551
  private finishWebsocket;
553
- onWebsocketRequest(ws: Ws, req: IncomingMessage, type: ServerType): Promise<unknown>;
554
- handleWebsocket(scope: Scope, ws: Ws, req: IncomingMessage, type: ServerType): Promise<unknown>;
552
+ /**
553
+ * Point d'entrée d'un upgrade vers une route WebSocket de Nodefony, appelé
554
+ * par le répartiteur d'upgrade AVANT le `101` (#596) : rien ne s'ouvre tant
555
+ * que le pipeline n'a pas dit oui.
556
+ *
557
+ * Les bornes par IP passent en premier, avant tout scope, ALS ou pipeline
558
+ * (symétrie avec le 429 HTTP et avec le proxy inverse) : le vrai vecteur DoS
559
+ * est d'accumuler des sockets ouvertes. La place est rendue à la fermeture du
560
+ * socket du client — même règle que pour un tunnel relayé.
561
+ *
562
+ * @param req - requête d'upgrade
563
+ * @param admission - le verdict à rendre : accepter (`101`) ou refuser (HTTP)
564
+ * @param type - serveur qui a reçu l'upgrade
565
+ * @returns la promesse du traitement de la connexion
566
+ */
567
+ onWebsocketUpgrade(req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
568
+ /**
569
+ * Pipeline d'un handshake WebSocket, dans la bulle ALS de la connexion :
570
+ * jugement (`onConnect`), pare-feu, gardes de l'action — PUIS seulement
571
+ * l'acceptation, la socket et l'action de handshake. Tout refus antérieur à
572
+ * l'acceptation part en statut HTTP (`onError` → `refuseHandshake`).
573
+ *
574
+ * @param scope - scope DI de la connexion
575
+ * @param req - requête d'upgrade
576
+ * @param admission - le verdict à rendre
577
+ * @param type - serveur qui a reçu l'upgrade
578
+ * @returns ce que rend l'action de handshake
579
+ */
580
+ handleWebsocket(scope: Scope, req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
555
581
  private releaseOrphanWsScope;
556
- onConnect(context: WebsocketContext | null, error?: unknown): Promise<Ws | number>;
582
+ /**
583
+ * Juge un handshake WebSocket AVANT son acceptation : hôte, origine,
584
+ * `beforeResolve`, contrôleur (protocole négocié), session. N'ouvre rien —
585
+ * l'acceptation n'a lieu qu'après le pare-feu et les gardes de l'action
586
+ * (cf {@link handleWebsocket}).
587
+ *
588
+ * @param context - contexte du handshake, encore sans socket
589
+ * @param error - erreur levée à la construction du contexte, relancée telle quelle
590
+ * @throws la première erreur du jugement — rendue en statut HTTP par `onError`
591
+ */
592
+ onConnect(context: WebsocketContext | null, error?: unknown): Promise<void>;
557
593
  checkValidDomain(context: ContextType): number;
558
594
  isValidDomain(context: ContextType): boolean;
559
595
  /**
@@ -12,3 +12,19 @@ import type { Duplex } from "node:stream";
12
12
  * @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
13
13
  */
14
14
  export declare function handleClientError(error: NodeJS.ErrnoException, socket: Duplex): void;
15
+ /**
16
+ * Gère l'event `sessionError` d'un serveur HTTP/2 (h2c ou TLS) : un pair PARTI
17
+ * se tait, toute autre erreur est rapportée.
18
+ *
19
+ * Une session réinitialisée par le pair n'a rien à diagnostiquer. C'est la
20
+ * signature d'une sonde de port — `nodefony status`, `doctor`, le superviseur
21
+ * de développement, la sonde `tcpSocket` d'un orchestrateur — qui se connecte
22
+ * puis ferme sans lire le `SETTINGS` que le serveur envoie dès l'ouverture : le
23
+ * système ferme alors par un RST plutôt qu'un FIN, et Node lève `ECONNRESET`.
24
+ * La rapporter imprimait une pile entière par sonde. La règle « client parti »
25
+ * est celle des frames WebSocket ({@link isPeerGoneError}), pas une copie.
26
+ *
27
+ * @param error - erreur de session émise par `node:http2`
28
+ * @param report - rapporte une erreur réelle, à la sévérité choisie par le serveur
29
+ */
30
+ export declare function handleSessionError(error: Error, report: (error: Error) => void): void;
@@ -0,0 +1,51 @@
1
+ import { Service, type Module, FamilyType } from "nodefony";
2
+ import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
+ import http2 from "node:http2";
4
+ import { AddressInfo } from "node:net";
5
+ import { type IDrainTerminator } from "./serverShutdown.js";
6
+ import type { IHttpConfig } from "../../config/config.js";
7
+ import type { DefaultOptionsService } from "nodefony";
8
+ /**
9
+ * Serveur HTTP/2 en clair (h2c, connaissance préalable — RFC 9113 §3.3), sur un
10
+ * port DÉDIÉ (`servers.h2c`, 5153 par défaut), pour un proxy inverse qui parle
11
+ * HTTP/2 à ses amonts.
12
+ *
13
+ * Il partage le chemin HTTP/2 du 5152 — même `onHttpRequest`, mêmes limites
14
+ * anti-DoS (`http2ServerLimits`), même drain par GOAWAY — sans TLS : `serverType`
15
+ * vaut `"http"`, le schéma vient du transport ou d'un proxy de confiance, jamais
16
+ * du pseudo-en-tête `:scheme`. Le protocole du saut (`"h2c"`) se constate sur la
17
+ * requête (`transport.ts#alpnProtocolOf`).
18
+ *
19
+ * Hors de son périmètre, délibérément : HTTP/1.1 (refusé par la préface HTTP/2 —
20
+ * sondes et clients HTTP/1.1 restent sur le port HTTP), WebSocket (aucun CONNECT
21
+ * étendu, RFC 8441 : un proxy route `Upgrade: websocket` vers le port HTTP) et la
22
+ * bascule `Upgrade: h2c` (retirée par RFC 9113 §3.1).
23
+ */
24
+ declare class ServerH2c extends Service {
25
+ private readonly httpKernel;
26
+ options: IHttpConfig["http"] & DefaultOptionsService;
27
+ module: Module<IHttpConfig>;
28
+ server: http2.Http2Server | null;
29
+ httpTerminator: IDrainTerminator | null;
30
+ port: number;
31
+ protocol: ProtocolType;
32
+ ready: boolean;
33
+ type: ServerType;
34
+ domain: string | null;
35
+ scheme: SchemeType;
36
+ address: string | null;
37
+ family: FamilyType | null;
38
+ active: boolean;
39
+ infos: AddressInfo | null;
40
+ constructor(module: Module<IHttpConfig>, httpKernel: HttpKernel);
41
+ /**
42
+ * Crée et met en écoute le serveur h2c.
43
+ *
44
+ * @returns le serveur à l'écoute
45
+ * @throws BootConfigurationError si `servers.h2c.port` est celui du serveur
46
+ * HTTP ou HTTPS — un port partagé n'est pas un conflit à contourner
47
+ */
48
+ createServer(): Promise<http2.Http2Server>;
49
+ showBanner(): void;
50
+ }
51
+ export default ServerH2c;
@@ -1,9 +1,9 @@
1
- import { Service, Module, FamilyType } from "nodefony";
1
+ import { Service, type Module, FamilyType } from "nodefony";
2
2
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
3
  import http from "node:http";
4
4
  import http2 from "node:http2";
5
5
  import { AddressInfo } from "node:net";
6
- import { HttpTerminator } from "./serverShutdown.js";
6
+ import { type IDrainTerminator } from "./serverShutdown.js";
7
7
  import type { IHttpConfig } from "../../config/config.js";
8
8
  import type { DefaultOptionsService } from "nodefony";
9
9
  declare class ServerHttp extends Service {
@@ -11,7 +11,7 @@ declare class ServerHttp extends Service {
11
11
  options: IHttpConfig["http"] & DefaultOptionsService;
12
12
  module: Module;
13
13
  server: http.Server | http2.Http2Server | null;
14
- httpTerminator: HttpTerminator | null;
14
+ httpTerminator: IDrainTerminator | null;
15
15
  port: number;
16
16
  protocol: ProtocolType;
17
17
  ready: boolean;
@@ -1,15 +1,15 @@
1
- import { Service, Module, FamilyType } from "nodefony";
1
+ import { Service, type Module, FamilyType } from "nodefony";
2
2
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
3
  import https from "node:https";
4
4
  import http2 from "node:http2";
5
- import { HttpTerminator } from "./serverShutdown.js";
5
+ import { type IDrainTerminator } from "./serverShutdown.js";
6
6
  import { AddressInfo } from "node:net";
7
7
  import type { IHttpConfig } from "../../config/config.js";
8
8
  import type { DefaultOptionsService } from "nodefony";
9
9
  declare class ServerHttps extends Service {
10
10
  private readonly httpKernel;
11
11
  options: IHttpConfig["https"] & DefaultOptionsService;
12
- httpTerminator: HttpTerminator | null;
12
+ httpTerminator: IDrainTerminator | null;
13
13
  module: Module;
14
14
  server: https.Server | http2.Http2SecureServer | null;
15
15
  port: number;
@@ -23,7 +23,7 @@ declare class ServerHttps extends Service {
23
23
  active: boolean;
24
24
  infos: AddressInfo | null;
25
25
  constructor(module: Module, httpKernel: HttpKernel);
26
- terminator(): HttpTerminator;
26
+ terminator(): IDrainTerminator;
27
27
  setPort(): number;
28
28
  createServer(): Promise<https.Server | http2.Http2SecureServer>;
29
29
  createServerH2(): Promise<http2.Http2SecureServer>;
@@ -1,10 +1,9 @@
1
- import Ws, { WebSocketServer } from "ws";
1
+ import { WebSocketServer } from "ws";
2
2
  import { Service, Module, FamilyType } from "nodefony";
3
3
  import type { DefaultOptionsService } from "nodefony";
4
4
  import type { IHttpConfig } from "../../config/config.js";
5
5
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
6
6
  import { AddressInfo } from "node:net";
7
- import type { IncomingMessage } from "node:http";
8
7
  import httpsServers from "./server-https.js";
9
8
  declare class WebsocketSecure extends Service {
10
9
  #private;
@@ -26,7 +25,6 @@ declare class WebsocketSecure extends Service {
26
25
  constructor(module: Module, httpKernel: HttpKernel);
27
26
  setPort(): number;
28
27
  createServer(serverHttps: httpsServers): Promise<WebSocketServer>;
29
- onConnection(ws: Ws, req: IncomingMessage): void;
30
28
  terminate(): Promise<boolean>;
31
29
  showBanner(): void;
32
30
  }
@@ -1,10 +1,9 @@
1
- import Ws, { WebSocketServer } from "ws";
1
+ import { WebSocketServer } from "ws";
2
2
  import { Service, Module, FamilyType } from "nodefony";
3
3
  import type { DefaultOptionsService } from "nodefony";
4
4
  import type { IHttpConfig } from "../../config/config.js";
5
5
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
6
6
  import { AddressInfo } from "node:net";
7
- import type { IncomingMessage } from "node:http";
8
7
  import httpServer from "./server-http.js";
9
8
  declare class Websocket extends Service {
10
9
  #private;
@@ -26,7 +25,6 @@ declare class Websocket extends Service {
26
25
  constructor(module: Module, httpKernel: HttpKernel);
27
26
  setPort(): number;
28
27
  createServer(serverHttp: httpServer): Promise<WebSocketServer>;
29
- onConnection(ws: Ws, req: IncomingMessage): void;
30
28
  terminate(): Promise<boolean>;
31
29
  showBanner(): void;
32
30
  }
@@ -1,27 +1,41 @@
1
- import { HttpTerminator } from "http-terminator";
2
1
  import type { Server as HttpServer } from "node:http";
3
2
  import type { Server as HttpsServer } from "node:https";
4
- import type { Http2SecureServer } from "node:http2";
3
+ import type { Http2SecureServer, Http2Server } from "node:http2";
4
+ /** Serveur que `createDrainTerminator` sait drainer. */
5
+ export type DrainableServer = HttpServer | HttpsServer | Http2Server | Http2SecureServer;
6
+ /** Arrêt gracieux d'un serveur, rendu par `createDrainTerminator`. */
7
+ export interface IDrainTerminator {
8
+ /**
9
+ * Draine puis ferme le serveur ; idempotent (le deuxième appel rend la même promesse).
10
+ *
11
+ * @returns une promesse résolue quand toutes les connexions sont fermées
12
+ */
13
+ terminate(): Promise<void>;
14
+ }
5
15
  /**
6
- * Fabrique le terminator de drain graceful d'un serveur HTTP/HTTPS/HTTP2.
16
+ * Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
7
17
  *
8
- * Au `terminate()` (shutdown SIGTERM/docker stop) : les requêtes in-flight se
9
- * terminent (header `connection: close` injecté sur les réponses en cours), les
10
- * sockets idle sont fermées immédiatement, et tout ce qui reste après
11
- * `shutdownTimeout` ms est détruit de force. Le terminator appelle lui-même
12
- * `server.close()` — ne pas le rappeler derrière.
18
+ * Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
19
+ * - **HTTP/2** : chaque session reçoit un GOAWAY (`session.close()`) — les flux
20
+ * en vol se terminent, les nouveaux sont refusés, et le client sait qu'il doit
21
+ * rouvrir ailleurs au lieu de constater une coupure ;
22
+ * - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
23
+ * inactive est fermée tout de suite, une requête qui arrive encore sur une
24
+ * socket gardée ouverte reçoit aussi `Connection: close`.
25
+ *
26
+ * Ce qui reste après `shutdownTimeout` ms est détruit. `server.close()` est
27
+ * appelé ici : ne pas le rappeler derrière.
13
28
  *
14
29
  * ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
15
- * SANS frame Close par le terminator → les serveurs WS doivent fermer leurs
16
- * clients (close 1001) AVANT ce drain. Garanti par l'ordre des listeners
17
- * `onTerminate` : WS/WSS s'attachent en `prependOnceListener`, les serveurs
18
- * HTTP en `once`.
30
+ * SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
31
+ * AVANT ce drain. Garanti par l'ordre des listeners `onTerminate` : WS/WSS
32
+ * s'attachent en `prependOnceListener`, les serveurs HTTP en `once`.
19
33
  *
20
- * @param server - serveur Node à drainer (créé, pas forcément listening)
34
+ * @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
35
+ * connexions commence ici)
21
36
  * @param shutdownTimeout - délai de drain en ms avant destruction forcée
22
- * @returns le terminator à invoquer au shutdown
37
+ * @returns l'arrêt à invoquer au shutdown
23
38
  */
24
- export declare function createDrainTerminator(server: HttpServer | HttpsServer | Http2SecureServer, shutdownTimeout?: number): HttpTerminator;
39
+ export declare function createDrainTerminator(server: DrainableServer, shutdownTimeout?: number): IDrainTerminator;
25
40
  /** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
26
41
  export declare const DEFAULT_SHUTDOWN_TIMEOUT = 5000;
27
- export type { HttpTerminator };
@@ -7,6 +7,7 @@ import http2 from "node:http2";
7
7
  import http from "node:http";
8
8
  import https from "node:https";
9
9
  import HttpKernel, { ServerType, Data, SchemeType } from "../../service/http-kernel.js";
10
+ import type { AlpnProtocol, ProtocolName } from "../../interfaces/IContext.js";
10
11
  import HttpResponse from "./http/Response.js";
11
12
  import Http2Response from "./http2/Response.js";
12
13
  import WebsocketResponse from "./websocket/Response.js";
@@ -50,7 +51,15 @@ declare class Context extends Service implements IContextInterface {
50
51
  requested: boolean;
51
52
  sessionStarting: boolean;
52
53
  domain: string;
53
- type: ServerType;
54
+ /** Famille traitée (`network.protocol.name` d'OpenTelemetry) — union ouverte. */
55
+ type: ProtocolName;
56
+ /** Serveur qui a reçu la requête — choisit la configuration par serveur. */
57
+ serverType: ServerType;
58
+ /**
59
+ * Protocole du saut local (registre IANA ALPN) — union ouverte. Une connexion
60
+ * WebSocket naît d'un upgrade HTTP/1.1 ; `HttpContext` le constate sur la requête.
61
+ */
62
+ alpnProtocol: AlpnProtocol;
54
63
  server?: WebSocketServer | http.Server | https.Server | http2.Http2SecureServer | null | undefined;
55
64
  httpKernel: HttpKernel | null;
56
65
  request: contextRequest | null;
@@ -128,7 +137,7 @@ declare class Context extends Service implements IContextInterface {
128
137
  metaData: Data;
129
138
  scheme: SchemeType;
130
139
  webSocketState: WebSocketState;
131
- constructor(container: Container | Scope, type: ServerType);
140
+ constructor(container: Container | Scope, serverType: ServerType);
132
141
  /**
133
142
  * Assemble l'enveloppe `metaData` de la requête (`nodefony.*` + `route`
134
143
  * snapshot per-requête + overrides appelant). Délègue au builder monomorphe
@@ -183,8 +192,8 @@ declare class Context extends Service implements IContextInterface {
183
192
  clean(): void;
184
193
  /**
185
194
  * Log d'un event du cycle de vie — gate boot-time (V2.1) : en production le
186
- * Pdu n'est PAS construit (return avant toute allocation). Hors prod, jalon
187
- * notable → INFO (EVENT_SEVERITY), event technique → DEBUG.
195
+ * Pdu n'est PAS construit (return avant toute allocation). Hors prod : DEBUG,
196
+ * pour le Suivi de requête de Studio — la ligne `req` porte le bilan.
188
197
  */
189
198
  private logEvent;
190
199
  fire(event: KernelEventsType, ...args: unknown[]): boolean;
@@ -53,7 +53,7 @@ declare class HttpContext extends Context implements IHttpContextInterface {
53
53
  request: HttpRequestType;
54
54
  response: HttpRsponseType;
55
55
  uploadService: uploadService | null;
56
- constructor(container: Container | Scope, request: http.IncomingMessage | http2.Http2ServerRequest, response: http.ServerResponse | http2.Http2ServerResponse, type: ServerType);
56
+ constructor(container: Container | Scope, request: http.IncomingMessage | http2.Http2ServerRequest, response: http.ServerResponse | http2.Http2ServerResponse, serverType: ServerType);
57
57
  /**
58
58
  * URL d'origine effective, construite À LA DEMANDE (1ʳᵉ lecture, puis
59
59
  * mémoïsée). `Origin` invalide ou `null` (RFC 6454) → repli sur l'URL de la
@@ -5,6 +5,7 @@ import { URL } from "node:url";
5
5
  import { HTTPMethod, Cookies } from "../Context.js";
6
6
  import QS from "qs";
7
7
  import { ParserXml, ParserQs, Parser, acceptParser } from "./parser.js";
8
+ import type { RequestBody } from "../../../interfaces/IRequest.js";
8
9
  import { UploadedFile } from "../../../service/upload/upload-service.js";
9
10
  import type { IParsedUploadFile, IUploadOptions } from "../../../interfaces/IUpload.js";
10
11
  import { Pci, Pdu, Message, Severity, Msgid } from "nodefony";
@@ -75,22 +76,24 @@ declare class HttpRequest {
75
76
  get url(): URL;
76
77
  set url(u: URL);
77
78
  parser: ParserType | null;
78
- queryPost: Record<string, unknown>;
79
- queryGet: Record<string, unknown>;
80
- queryFile: UploadedFile[];
81
- query: Record<string, unknown>;
82
79
  /**
83
- * Le CORPS de la requête, parsé — nom universel de l'écosystème (Express,
84
- * Fastify, NestJS), alias de {@link queryPost}.
85
- *
86
- * Ne pas confondre avec {@link query}, qui FUSIONNE la query string et le
87
- * corps : `body` ne rend jamais un paramètre d'URL. Le data plane admin
88
- * expose déjà cette sémantique (`IAdminRequest.body`) ; ce getter la rend
89
- * disponible partout.
80
+ * Le CORPS de la requête, parsé (JSON, urlencoded, XML, champs multipart) —
81
+ * nom universel de l'écosystème (Express, Fastify, NestJS) et celui
82
+ * d'`IAdminRequest.body`. Jamais un paramètre d'URL : ceux-là sont dans
83
+ * {@link query}, et les deux ne fusionnent pas.
84
+ */
85
+ body: RequestBody;
86
+ /**
87
+ * Les fichiers reçus dans un corps multipart, déjà écrits dans le répertoire
88
+ * temporaire — lus par `@UploadedFile()` / `@UploadedFiles()`. Les champs
89
+ * texte du même corps sont dans {@link body}.
90
90
  *
91
- * Getter (aucune allocation) : `queryPost` reste la source unique.
91
+ * Lecture seule : une requête sans upload rend un vide partagé et gelé,
92
+ * aucune allocation.
92
93
  */
93
- get body(): Record<string, unknown>;
94
+ get files(): readonly UploadedFile[];
95
+ /** Les paramètres d'URL, seuls — jamais un champ du corps ({@link body}). */
96
+ query: Record<string, unknown>;
94
97
  queryStringOptions: (QS.IParseOptions & {
95
98
  decoder?: undefined;
96
99
  }) | undefined;
@@ -112,7 +115,7 @@ declare class HttpRequest {
112
115
  * remplace le `once("onRequestEnd")` du ctor qui allouait 1 onceWrapper +
113
116
  * 1 closure à CHAQUE requête.
114
117
  *
115
- * Il porte le CORPS (`queryPost`), pas la fusion `query` : un seul sens de
118
+ * Il porte le CORPS (`body`), jamais les paramètres d'URL : un seul sens de
116
119
  * `body` dans tout le framework, celui de l'écosystème et d'`IAdminRequest`.
117
120
  */
118
121
  private fireRequestEnd;
@@ -228,7 +231,9 @@ declare class HttpRequest {
228
231
  /**
229
232
  * Scheme effectif côté client : `Forwarded`/`X-Forwarded-*` résolu de façon
230
233
  * canonique (this.forwarded, gated proxy de confiance) ; sinon le transport
231
- * réel. HTTP/2 remplace la lecture du socket par le pseudo-header `:scheme`.
234
+ * réel. Vaut pour HTTP/2 aussi : le pseudo-en-tête `:scheme` vient du client
235
+ * (RFC 9113 §8.3.1) et n'est jamais cru — sur h2c, `:scheme: https` ferait
236
+ * passer une connexion claire pour chiffrée.
232
237
  */
233
238
  protected resolveScheme(request: http.IncomingMessage | http2.Http2ServerRequest): string;
234
239
  getFullUrl(request?: http.IncomingMessage | http2.Http2ServerRequest): string;
@@ -50,6 +50,17 @@ declare class HttpResponse {
50
50
  deleteCookieByName(name: string): boolean;
51
51
  setCookies(): void | http.ServerResponse<http.IncomingMessage>;
52
52
  setCookie(cookie: Cookie): void | http.ServerResponse<http.IncomingMessage>;
53
+ /**
54
+ * Journalise en DEBUG un cookie posé, sa VALEUR MASQUÉE : nom et attributs
55
+ * seulement. Le journal de développement se lit par le plan d'administration
56
+ * — un identifiant de session y entrerait chez quiconque le consulte.
57
+ * La ligne n'est construite que si DEBUG passe le gate du syslog pour ce
58
+ * `msgid` (debug ciblé compris) : rien n'est alloué en production.
59
+ *
60
+ * @param cookie - le cookie posé.
61
+ * @param serialized - sa forme `Set-Cookie`, déjà calculée pour l'en-tête.
62
+ */
63
+ private logCookie;
53
64
  setHeader(name: string, value: number | string | readonly string[]): void | http.ServerResponse<http.IncomingMessage>;
54
65
  setHeaders(obj: OutgoingHttpHeaders): OutgoingHttpHeaders;
55
66
  setContentType(type?: string, encoding?: BufferEncoding): void | http.ServerResponse<http.IncomingMessage>;
@@ -22,6 +22,22 @@ declare class Parser {
22
22
  protected ended(): Promise<void>;
23
23
  parse(): Promise<this>;
24
24
  }
25
+ /**
26
+ * Corps d'un type que le framework ne décode pas : livré TEL QU'IL EST REÇU,
27
+ * jamais remplacé par un objet vide qui mentirait.
28
+ *
29
+ * - `text/*` → une chaîne, décodée au `charset` du `Content-Type` (UTF-8 par
30
+ * défaut) — comme une trame texte RFC 6455 ;
31
+ * - tout autre type, et un corps sans `Content-Type` (RFC 9110 §8.3 : supposé
32
+ * `application/octet-stream`) → les OCTETS : le `Buffer` déjà assemblé dans
33
+ * `request.data`, sans copie — donc un `Uint8Array`, le type d'octets que
34
+ * la socket livrera aussi (#618). Un même schéma passe par les deux portes.
35
+ *
36
+ * Un corps VIDE laisse `body` tel quel : une requête sans charge ne change pas.
37
+ */
38
+ declare class ParserRaw extends Parser {
39
+ parse(): Promise<this>;
40
+ }
25
41
  declare class ParserQs extends Parser {
26
42
  parserOptions: QS.IParseOptions;
27
43
  charset: BufferEncoding;
@@ -35,7 +51,7 @@ declare class ParserXml extends Parser {
35
51
  parse(): Promise<this>;
36
52
  }
37
53
  /**
38
- * Parse un corps `application/json` (ou `*+json`) en objet → `queryPost`
54
+ * Parse un corps `application/json` (ou `*+json`) en objet → `body`
39
55
  * (source lue par le décorateur `@Body`). Reprend le rôle de l'ancien plugin
40
56
  * `json` de formidable, supprimé avec le passage à busboy (multipart seul).
41
57
  * Lenient : un corps JSON malformé est ignoré (pas de throw — `parse()` est
@@ -62,4 +78,4 @@ type AcceptEntry = {
62
78
  [key: string]: RegExp | number | string | undefined;
63
79
  };
64
80
  declare const acceptParser: (acc?: string) => AcceptEntry[];
65
- export { Parser, ParserXml, ParserQs, ParserJson, acceptParser };
81
+ export { Parser, ParserRaw, ParserXml, ParserQs, ParserJson, acceptParser };
@@ -5,10 +5,10 @@ import { HTTPMethod } from "../Context.js";
5
5
  declare class Http2Request extends HttpRequest {
6
6
  request: http2.Http2ServerRequest;
7
7
  constructor(request: http2.Http2ServerRequest, context: HttpContext);
8
+ /** `:authority`, sinon `Host` — cf `requestAuthority` (RFC 9113 §8.3.1). */
8
9
  getHost(): string | undefined;
9
10
  getUserAgent(): string | undefined;
10
11
  getMethod(): HTTPMethod;
11
12
  getRawTarget(): string | undefined;
12
- protected resolveScheme(): string;
13
13
  }
14
14
  export default Http2Request;
@@ -3,6 +3,11 @@ import type { MaybePromise } from "nodefony";
3
3
  import http from "node:http";
4
4
  import HttpContext from "../http/HttpContext.js";
5
5
  import HttpResponse from "../http/Response.js";
6
+ /** Ce que la fusion lit d'une réponse Node : ses en-têtes posés, nom par nom. */
7
+ interface IHeaderSource {
8
+ getHeaderNames(): string[];
9
+ getHeader(name: string): http.OutgoingHttpHeader | undefined;
10
+ }
6
11
  /**
7
12
  * Compose les en-têtes d'une réponse HTTP/2 : ceux posés sur la réponse, puis
8
13
  * ceux passés à `writeHead`, qui gagnent.
@@ -13,12 +18,17 @@ import HttpResponse from "../http/Response.js";
13
18
  * (`ERR_HTTP2_HEADER_SINGLE_VALUE`), soit une 500 sur tout média servi en
14
19
  * HTTP/2. HTTP/2 n'a de toute façon que des noms en minuscules (RFC 9113 §8.2.1).
15
20
  *
21
+ * Les en-têtes posés se lisent nom par nom (`getHeaderNames`, déjà en
22
+ * minuscules) : `getHeaders()` les recopiait d'abord dans un objet sans
23
+ * prototype, avant cette fusion — 3,8 µs par réponse pour 12 en-têtes,
24
+ * contre 1,1 µs en une passe.
25
+ *
16
26
  * @param statusMessage - message de statut, recopié en `x-status-message`
17
- * @param current - en-têtes déjà posés sur la réponse
27
+ * @param current - la réponse Node qui porte les en-têtes déjà posés
18
28
  * @param extra - en-têtes passés à `writeHead` (objet, ou liste plate clé/valeur)
19
29
  * @returns les en-têtes à transmettre à `respond()`
20
30
  */
21
- export declare function mergeResponseHeaders(statusMessage: string, current: http.OutgoingHttpHeaders, extra?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): http.OutgoingHttpHeaders;
31
+ export declare function mergeResponseHeaders(statusMessage: string, current: IHeaderSource, extra?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): http.OutgoingHttpHeaders;
22
32
  declare class Http2Response extends HttpResponse {
23
33
  statusCode: number;
24
34
  stream: http2.ServerHttp2Stream | null;
@@ -27,9 +37,9 @@ declare class Http2Response extends HttpResponse {
27
37
  isHeaderSent(): boolean;
28
38
  writeHead(statusCode?: number, headers?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): void;
29
39
  /**
30
- * Écrit le corps sur le flux HTTP/2. Synchrone pour une redirection, un flux
31
- * fermé ou le repli HTTP/1 ; une promesse quand le corps est écrit sur le
32
- * flux, réglée par le rappel d'écriture (#505).
40
+ * Écrit le corps sur le flux HTTP/2 et, pour une réponse unique, le termine
41
+ * d'un seul `end(corps)`. Synchrone, sauf en streaming (`flush()`) : une
42
+ * promesse réglée par le rappel d'écriture, la contre-pression (#505).
33
43
  *
34
44
  * @returns la réponse, ou sa promesse.
35
45
  */
@@ -0,0 +1,53 @@
1
+ import type { AlpnProtocol, ProtocolName, ServerType } from "../../interfaces/IContext.js";
2
+ /**
3
+ * Famille du protocole traité par un contexte, déduite du serveur qui l'a
4
+ * reçu. Règle UNIQUE : `Context` la pose à la construction.
5
+ *
6
+ * @param serverType - identité du serveur (`http`, `https`, `ws`, `wss`)
7
+ * @returns `"websocket"` pour `ws`/`wss`, `"http"` sinon
8
+ */
9
+ export declare function protocolNameOf(serverType: ServerType): ProtocolName;
10
+ /**
11
+ * Ce que la détection lit d'une requête Node, et rien d'autre : `http.IncomingMessage`
12
+ * (HTTP/1.1) et `http2.Http2ServerRequest` (h2, h2c) le satisfont par leur forme, sans
13
+ * transtypage — et une doublure de test s'écrit en littéral.
14
+ */
15
+ export interface IAlpnSource {
16
+ readonly httpVersionMajor: number;
17
+ readonly stream?: {
18
+ readonly session?: {
19
+ readonly alpnProtocol?: string | undefined;
20
+ } | undefined;
21
+ } | undefined;
22
+ }
23
+ /**
24
+ * Protocole du saut qui a porté cette requête jusqu'au processus, au sens du
25
+ * registre IANA des identifiants ALPN. Règle UNIQUE : `HttpContext` la lit pour
26
+ * choisir ses classes de requête et de réponse.
27
+ *
28
+ * La valeur se CONSTATE sur la requête, elle ne se déduit pas du serveur : un
29
+ * même serveur TLS reçoit HTTP/1.1 et h2 (`allowHTTP1`). Pour h2, c'est la
30
+ * session de Node qui tranche — `Http2Session.alpnProtocol` vaut `"h2c"` sur
31
+ * une session en clair, l'ALPN négocié (`"h2"`) sur une session TLS.
32
+ *
33
+ * @param request - la requête Node reçue par le serveur
34
+ * @returns `"http/1.1"`, `"h2"` ou `"h2c"`
35
+ */
36
+ export declare function alpnProtocolOf(request: IAlpnSource): AlpnProtocol;
37
+ /**
38
+ * Autorité (hôte et port) d'une requête, HTTP/1.1 comme HTTP/2. Règle UNIQUE
39
+ * des lecteurs du serveur.
40
+ *
41
+ * `:authority` d'abord, puis `Host` (RFC 9113 §8.3.1). Le repli n'est pas un
42
+ * confort : un intermédiaire qui convertit une requête HTTP/1.1 de forme origine
43
+ * (`GET /chemin`) en HTTP/2 ne DOIT PAS fabriquer `:authority` — il garde
44
+ * `Host`. C'est ce que fait HAProxy `proto h2` vers un port h2c : sans ce repli,
45
+ * l'hôte est introuvable et la requête part en 421.
46
+ *
47
+ * @param headers - en-têtes de la requête Node (`IncomingMessage` ou `Http2ServerRequest`).
48
+ * @returns l'autorité, ou `undefined` si la requête n'en porte aucune.
49
+ */
50
+ export declare function requestAuthority(headers: {
51
+ host?: string | undefined;
52
+ ":authority"?: string | undefined;
53
+ }): string | undefined;