@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.
- package/README.md +24 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +30 -26
- package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
- package/dist/nodefony/config/config.js +29 -4
- package/dist/nodefony/grpc/GrpcCall.js +298 -0
- package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
- package/dist/nodefony/grpc/index.js +4 -0
- package/dist/nodefony/interfaces/IRpcCall.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
- package/dist/nodefony/service/audit-logger.js +52 -6
- package/dist/nodefony/service/error-renderer.js +47 -13
- package/dist/nodefony/service/http-kernel.js +306 -49
- package/dist/nodefony/service/pretty-request-logger.js +5 -3
- package/dist/nodefony/service/request-logger.js +21 -6
- package/dist/nodefony/service/reverse-proxy.js +8 -3
- package/dist/nodefony/service/servers/clientError.js +53 -4
- package/dist/nodefony/service/servers/server-h2c.js +6 -6
- package/dist/nodefony/service/servers/server-http.js +6 -4
- package/dist/nodefony/service/servers/server-https.js +14 -7
- package/dist/nodefony/service/servers/server-static.js +1 -6
- package/dist/nodefony/service/servers/server-websocket-secure.js +7 -11
- package/dist/nodefony/service/servers/server-websocket.js +7 -11
- package/dist/nodefony/service/servers/serverShutdown.js +43 -6
- package/dist/nodefony/service/sessions/sessions-service.js +7 -7
- package/dist/nodefony/service/upload/upload-service.js +2 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
- package/dist/nodefony/src/context/Context.js +12 -1
- package/dist/nodefony/src/context/http/ByteStream.js +364 -0
- package/dist/nodefony/src/context/http/HttpContext.js +139 -32
- package/dist/nodefony/src/context/http/Request.js +111 -22
- package/dist/nodefony/src/context/http/Response.js +459 -37
- package/dist/nodefony/src/context/http/SseStream.js +83 -148
- package/dist/nodefony/src/context/http/StreamLifecycle.js +582 -0
- package/dist/nodefony/src/context/http/parser.js +29 -11
- package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
- package/dist/nodefony/src/context/http/webResponse.js +56 -0
- package/dist/nodefony/src/context/http2/Request.js +1 -1
- package/dist/nodefony/src/context/http2/Response.js +55 -12
- package/dist/nodefony/src/context/requestTrace.js +6 -2
- package/dist/nodefony/src/context/rpcOutcome.js +20 -0
- package/dist/nodefony/src/context/transport.js +20 -1
- package/dist/nodefony/src/context/websocket/Response.js +15 -3
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +86 -18
- package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
- package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
- package/dist/nodefony/src/cookies/cookie.js +57 -14
- package/dist/nodefony/src/errors/httpError.js +58 -25
- package/dist/nodefony/src/grpc/cors.js +59 -0
- package/dist/nodefony/src/grpc/limits.js +16 -0
- package/dist/nodefony/src/grpc/refusal.js +271 -0
- package/dist/nodefony/src/profiler/Profiler.js +14 -1
- package/dist/nodefony/src/profiler/aggregate.js +2 -2
- package/dist/nodefony/src/proxy/forward.js +109 -9
- package/dist/nodefony/src/proxy/generateProxyConfig.js +97 -17
- package/dist/nodefony/src/proxy/rules.js +29 -14
- package/dist/nodefony/src/servers/http2Goaway.js +76 -0
- package/dist/nodefony/src/servers/http2Limits.js +41 -15
- package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
- package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
- package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
- package/dist/types/index.d.ts +11 -2
- package/dist/types/nodefony/config/config.d.ts +14 -0
- package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
- package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
- package/dist/types/nodefony/grpc/index.d.ts +15 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
- package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
- package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
- package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
- package/dist/types/nodefony/service/error-renderer.d.ts +27 -5
- package/dist/types/nodefony/service/http-kernel.d.ts +36 -3
- package/dist/types/nodefony/service/request-logger.d.ts +10 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +7 -5
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
- package/dist/types/nodefony/src/context/Context.d.ts +9 -1
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +99 -3
- package/dist/types/nodefony/src/context/http/Request.d.ts +46 -9
- package/dist/types/nodefony/src/context/http/Response.d.ts +188 -8
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +33 -8
- package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +106 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
- package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +40 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
- package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
- package/dist/types/nodefony/src/context/transport.d.ts +17 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +48 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
- package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
- package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
- package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
- package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
- package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
- package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +11 -1
- package/dist/types/nodefony/src/proxy/forward.d.ts +37 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
- package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
- package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
- package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
- package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
- package/docs/byte-stream.md +406 -0
- package/docs/cookies.md +13 -3
- package/docs/diagnostics-channel.md +45 -1
- package/docs/index.md +8 -0
- package/docs/reverse-proxy.md +15 -3
- package/docs/servers.md +67 -43
- package/docs/session.md +3 -3
- package/docs/sse.md +27 -20
- package/docs/upload.md +43 -17
- package/package.json +38 -18
|
@@ -10,9 +10,20 @@ import { UploadedFile } from "../../../service/upload/upload-service.js";
|
|
|
10
10
|
import type { IParsedUploadFile, IUploadOptions } from "../../../interfaces/IUpload.js";
|
|
11
11
|
import { Pci, Pdu, Message, Severity, Msgid } from "nodefony";
|
|
12
12
|
import Session from "../../session/session.js";
|
|
13
|
+
import HttpError from "../../errors/httpError.js";
|
|
13
14
|
import { type ResolvedProxy } from "../forwarded.js";
|
|
14
15
|
declare class BodyHandled {
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Une réception multipart EN COURS, interruptible de l'extérieur : `fail` rompt
|
|
19
|
+
* la lecture comme le ferait un dépassement de limite (temporaires nettoyés).
|
|
20
|
+
* Posée dans `request.parser` le temps de la lecture seulement — l'allocation
|
|
21
|
+
* est réservée au multipart, déjà lourd.
|
|
22
|
+
*/
|
|
23
|
+
declare class MultipartReception {
|
|
24
|
+
readonly fail: (error: HttpError) => boolean;
|
|
25
|
+
constructor(fail: (error: HttpError) => boolean);
|
|
26
|
+
}
|
|
16
27
|
declare module "url" {
|
|
17
28
|
interface URL {
|
|
18
29
|
query: QS.ParsedQs;
|
|
@@ -31,7 +42,7 @@ declare module "http2" {
|
|
|
31
42
|
session: Session;
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
|
-
type ParserType = ParserXml | ParserQs | Parser | BodyHandled;
|
|
45
|
+
type ParserType = ParserXml | ParserQs | Parser | BodyHandled | MultipartReception;
|
|
35
46
|
declare class HttpRequest {
|
|
36
47
|
#private;
|
|
37
48
|
context: HttpContext;
|
|
@@ -88,6 +99,9 @@ declare class HttpRequest {
|
|
|
88
99
|
* temporaire — lus par `@UploadedFile()` / `@UploadedFiles()`. Les champs
|
|
89
100
|
* texte du même corps sont dans {@link body}.
|
|
90
101
|
*
|
|
102
|
+
* Ces temporaires sont EFFACÉS à la fin de la requête : pour garder un
|
|
103
|
+
* fichier, l'action le déplace (`moveAsync`) avant de répondre.
|
|
104
|
+
*
|
|
91
105
|
* Lecture seule : une requête sans upload rend un vide partagé et gelé,
|
|
92
106
|
* aucune allocation.
|
|
93
107
|
*/
|
|
@@ -150,6 +164,16 @@ declare class HttpRequest {
|
|
|
150
164
|
* @returns `undefined` quand tout est fait, sinon la promesse à attendre.
|
|
151
165
|
*/
|
|
152
166
|
private endWithoutBody;
|
|
167
|
+
/**
|
|
168
|
+
* Lit le plafond du corps et les limites d'envoi tels que les voit CETTE
|
|
169
|
+
* requête : la configuration http, surchargée par le calque que la requête a
|
|
170
|
+
* pu poser avant la lecture du corps (`overlayConfig`, #494), puis par les
|
|
171
|
+
* bornes que la route déclare pour elle-même (option de route `upload`). La
|
|
172
|
+
* route est déjà résolue à ce moment : une route sans bornes propres
|
|
173
|
+
* n'alloue rien. Hors pipeline (aucun scope ouvert), la configuration du
|
|
174
|
+
* module telle quelle.
|
|
175
|
+
*/
|
|
176
|
+
private readBodyLimits;
|
|
153
177
|
/**
|
|
154
178
|
* B1 — Pré-contrôle de taille du corps via l'en-tête `Content-Length` :
|
|
155
179
|
* rejette en 413 (RFC 9110 §15.5.14 « Content Too Large ») AVANT toute lecture
|
|
@@ -159,13 +183,6 @@ declare class HttpRequest {
|
|
|
159
183
|
*
|
|
160
184
|
* @throws {HttpError} 413 si `Content-Length` dépasse `maxBodySize`.
|
|
161
185
|
*/
|
|
162
|
-
/**
|
|
163
|
-
* Lit le plafond du corps et les limites d'envoi tels que les voit CETTE
|
|
164
|
-
* requête : la configuration http, surchargée par le calque que la requête a
|
|
165
|
-
* pu poser avant la lecture du corps (`overlayConfig`, #494). Hors pipeline
|
|
166
|
-
* (aucun scope ouvert), la configuration du module telle quelle.
|
|
167
|
-
*/
|
|
168
|
-
private readBodyLimits;
|
|
169
186
|
private enforceBodyLimit;
|
|
170
187
|
parseRequest(): Promise<ParserType | null>;
|
|
171
188
|
/**
|
|
@@ -200,6 +217,19 @@ declare class HttpRequest {
|
|
|
200
217
|
* déjà posés — à la moindre erreur ou dépassement de limite.
|
|
201
218
|
*/
|
|
202
219
|
private streamMultipart;
|
|
220
|
+
/**
|
|
221
|
+
* Interrompt la réception du corps en cours : la promesse de `initialize()`
|
|
222
|
+
* rejette avec `error`, rendue par le chemin d'erreur habituel — comme un
|
|
223
|
+
* corps qui dépasse sa limite (413).
|
|
224
|
+
*
|
|
225
|
+
* Sert le délai de réception HTTP/2 (`requestTimeout`), que `node:http2` ne
|
|
226
|
+
* borne pas. Sans effet hors réception : corps déjà reçu, lu en flux par
|
|
227
|
+
* l'action (`bodyStream`), ou méthode sans corps.
|
|
228
|
+
*
|
|
229
|
+
* @param error - la cause rendue au client (408 au délai dépassé)
|
|
230
|
+
* @returns vrai si une réception en cours a été interrompue
|
|
231
|
+
*/
|
|
232
|
+
abortBody(error: HttpError): boolean;
|
|
203
233
|
/**
|
|
204
234
|
* Accumule un champ texte multipart. `foo[]` (ou répétition de `foo`) →
|
|
205
235
|
* tableau ; sinon valeur scalaire. (Anciennement délégué à formidable.)
|
|
@@ -220,12 +250,19 @@ declare class HttpRequest {
|
|
|
220
250
|
getDomain(): string;
|
|
221
251
|
getUserAgent(): string | undefined;
|
|
222
252
|
getHostName(host?: string): string;
|
|
253
|
+
/**
|
|
254
|
+
* L'autorité de la requête : celle de la cible en forme absolue, sinon
|
|
255
|
+
* l'en-tête `Host` (RFC 9112 §3.2.2 — le serveur DOIT alors ignorer `Host`).
|
|
256
|
+
* HTTP/2 lit `:authority` (override).
|
|
257
|
+
*/
|
|
223
258
|
getHost(): string | undefined;
|
|
224
259
|
getRemoteAddress(): string | null;
|
|
225
260
|
/**
|
|
226
261
|
* Request-target brut (origin-form) tel que reçu du transport — SOURCE
|
|
227
262
|
* UNIQUE partagée par la concaténation de {@link getFullUrl} et la découpe
|
|
228
|
-
* fast-path du ctor.
|
|
263
|
+
* fast-path du ctor. Une cible en forme absolue est ramenée à sa forme
|
|
264
|
+
* origine, son autorité passant par {@link getHost}. HTTP/2 lit le
|
|
265
|
+
* pseudo-header `:path` (override).
|
|
229
266
|
*/
|
|
230
267
|
getRawTarget(): string | undefined;
|
|
231
268
|
/**
|
|
@@ -1,9 +1,35 @@
|
|
|
1
|
-
import http, { OutgoingHttpHeaders } from "node:http";
|
|
1
|
+
import http, { OutgoingHttpHeaders, OutgoingHttpHeader } from "node:http";
|
|
2
2
|
import http2 from "node:http2";
|
|
3
3
|
import HttpContext from "../http/HttpContext.js";
|
|
4
4
|
import { Pci, Pdu, Message, Severity, Msgid } from "nodefony";
|
|
5
5
|
import type { MaybePromise } from "nodefony";
|
|
6
6
|
import Cookie from "../../cookies/cookie.js";
|
|
7
|
+
import type { IFirewallGate } from "../../../interfaces/ISecurity.js";
|
|
8
|
+
/**
|
|
9
|
+
* Plafond, en octets, d'un corps de requête HTTP/1.1 non lu que le serveur
|
|
10
|
+
* accepte de vider pour garder la connexion réutilisable — 256 Kio, le geste du
|
|
11
|
+
* serveur HTTP de Go que Connect reprend (ADR-0026, D5). Au-delà, la réponse
|
|
12
|
+
* ferme la connexion.
|
|
13
|
+
*/
|
|
14
|
+
export declare const UNREAD_BODY_DRAIN_LIMIT: number;
|
|
15
|
+
/**
|
|
16
|
+
* Silence, en millisecondes, au bout duquel une connexion HTTP/1.1 fermée sur
|
|
17
|
+
* un corps non lu est détruite — 5 s, le `lingering_timeout` par défaut de
|
|
18
|
+
* nginx. La durée TOTALE reste bornée par le délai de réception de Node
|
|
19
|
+
* (`requestTimeout`), qui court tant que le corps n'est pas fini.
|
|
20
|
+
*/
|
|
21
|
+
export declare const UNREAD_BODY_LINGER_MS = 5000;
|
|
22
|
+
/**
|
|
23
|
+
* Vrai pour un statut dont la réponse n'a, par définition, aucun contenu —
|
|
24
|
+
* 204, 205 et 304 (RFC 9110 §15.3.5, §15.3.6, §15.4.5).
|
|
25
|
+
*
|
|
26
|
+
* Seule source de cette liste : la réponse s'en sert pour ne jamais écrire de
|
|
27
|
+
* corps, le résolveur pour terminer une action qui ne rend rien.
|
|
28
|
+
*
|
|
29
|
+
* @param status - le code de statut de la réponse.
|
|
30
|
+
* @returns `true` quand aucun corps ne doit partir.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isContentlessStatus(status: number): boolean;
|
|
7
33
|
declare class HttpResponse {
|
|
8
34
|
#private;
|
|
9
35
|
context: HttpContext;
|
|
@@ -16,6 +42,13 @@ declare class HttpResponse {
|
|
|
16
42
|
headers: http.OutgoingHttpHeaders;
|
|
17
43
|
timeout?: number | undefined;
|
|
18
44
|
cookies: Record<string, Cookie>;
|
|
45
|
+
/**
|
|
46
|
+
* Pare-feu qui posera les en-têtes de sécurité applicatifs juste avant
|
|
47
|
+
* l'émission des en-têtes, une fois le type de la réponse fixé : la politique
|
|
48
|
+
* suit ce type (ADR-0024 §C11). Armé par le noyau HTTP au routage, consommé
|
|
49
|
+
* une fois ; `null` sans module de sécurité.
|
|
50
|
+
*/
|
|
51
|
+
headerPolicy: IFirewallGate | null;
|
|
19
52
|
constructor(response: http.ServerResponse | http2.Http2ServerResponse, context: HttpContext);
|
|
20
53
|
/**
|
|
21
54
|
* Filet posé juste avant l'émission des en-têtes : si aucun `Content-Type`
|
|
@@ -36,7 +69,9 @@ declare class HttpResponse {
|
|
|
36
69
|
/**
|
|
37
70
|
* Corps tel qu'il part sur le fil : le texte s'il n'a jamais été encodé, sinon
|
|
38
71
|
* les octets — vide légal si rien n'a été posé (`write(null)` jetterait
|
|
39
|
-
* ERR_STREAM_NULL_VALUES).
|
|
72
|
+
* ERR_STREAM_NULL_VALUES). Vide aussi sous un statut sans contenu : un corps
|
|
73
|
+
* posé par l'application sous un 205 ne part pas (RFC 9110 §15.3.6) — Node
|
|
74
|
+
* ne l'écarte de lui-même que pour 204 et 304.
|
|
40
75
|
*/
|
|
41
76
|
protected get payload(): string | Buffer;
|
|
42
77
|
/** Encodage à passer avec {@link payload} (ignoré par Node pour des octets). */
|
|
@@ -48,8 +83,8 @@ declare class HttpResponse {
|
|
|
48
83
|
addCookie(cookie: Cookie): Cookie;
|
|
49
84
|
deleteCookie(cookie: Cookie): boolean;
|
|
50
85
|
deleteCookieByName(name: string): boolean;
|
|
51
|
-
setCookies(): void
|
|
52
|
-
setCookie(cookie: Cookie): void
|
|
86
|
+
setCookies(): void;
|
|
87
|
+
setCookie(cookie: Cookie): void;
|
|
53
88
|
/**
|
|
54
89
|
* Journalise en DEBUG un cookie posé, sa VALEUR MASQUÉE : nom et attributs
|
|
55
90
|
* seulement. Le journal de développement se lit par le plan d'administration
|
|
@@ -61,11 +96,110 @@ declare class HttpResponse {
|
|
|
61
96
|
* @param serialized - sa forme `Set-Cookie`, déjà calculée pour l'en-tête.
|
|
62
97
|
*/
|
|
63
98
|
private logCookie;
|
|
64
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Pose un en-tête de la réponse ; en flux chunké déjà ouvert, le range en
|
|
101
|
+
* trailer. Le nom et la valeur sont VALIDÉS ici, comme le ferait Node : une
|
|
102
|
+
* valeur invalide (saut de ligne…) lève tout de suite et n'entre jamais dans
|
|
103
|
+
* la réserve — sinon elle lèverait au `writeHead`, et la page d'erreur
|
|
104
|
+
* rejouerait le même en-tête.
|
|
105
|
+
*
|
|
106
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
107
|
+
* @param value - valeur ; une liste émet une ligne par élément (`Set-Cookie`).
|
|
108
|
+
* @throws TypeError `ERR_INVALID_HTTP_TOKEN` / `ERR_INVALID_CHAR` sur un nom
|
|
109
|
+
* ou une valeur qu'un en-tête HTTP ne peut pas porter.
|
|
110
|
+
*/
|
|
111
|
+
setHeader(name: string, value: number | string | readonly string[]): void;
|
|
112
|
+
/**
|
|
113
|
+
* Pose un en-tête dont la valeur a DÉJÀ été validée par l'appelant — une
|
|
114
|
+
* table figée et contrôlée au démarrage (en-têtes de sécurité). Node la
|
|
115
|
+
* validera une fois, au `writeHead` ; la revalider ici effaçait le gain de
|
|
116
|
+
* l'écriture groupée (`micro-write-head.mjs`, ligne « plat validé »).
|
|
117
|
+
*
|
|
118
|
+
* ⚠️ Jamais pour une valeur venue d'une requête : elle passe par {@link setHeader}.
|
|
119
|
+
*
|
|
120
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
121
|
+
* @param value - valeur contrôlée au démarrage.
|
|
122
|
+
*/
|
|
123
|
+
setValidatedHeader(name: string, value: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Pose un en-tête validé au démarrage SAUF si la réponse en porte déjà un :
|
|
126
|
+
* la valeur du framework est un défaut, celle que l'application a écrite
|
|
127
|
+
* gagne. Même coût que {@link setValidatedHeader} — le rangement cherche
|
|
128
|
+
* l'en-tête de toute façon.
|
|
129
|
+
*
|
|
130
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
131
|
+
* @param value - valeur contrôlée au démarrage.
|
|
132
|
+
*/
|
|
133
|
+
setDefaultHeader(name: string, value: string): void;
|
|
134
|
+
/**
|
|
135
|
+
* Pose les en-têtes de la politique armée ({@link headerPolicy}) pour une
|
|
136
|
+
* réponse de type `contentType` — une fois, juste avant l'émission.
|
|
137
|
+
*
|
|
138
|
+
* Un `304` n'en reçoit aucun : ses en-têtes REMPLACENT ceux de la
|
|
139
|
+
* représentation en cache (RFC 9111 §4.3.4). Une politique courte y
|
|
140
|
+
* réduirait la page gardée, un nonce neuf la désaccorderait de ses balises.
|
|
141
|
+
*
|
|
142
|
+
* @param contentType - type de la réponse ; `null` = sans contenu ; `undefined`
|
|
143
|
+
* = inconnu (politique complète).
|
|
144
|
+
* @param servedAsFile - corps écrit par un tiers (fichier statique, relais),
|
|
145
|
+
* qui ne peut pas porter de nonce.
|
|
146
|
+
*/
|
|
147
|
+
protected applyHeaderPolicy(contentType: string | null | undefined, servedAsFile?: boolean): void;
|
|
148
|
+
/**
|
|
149
|
+
* Type qui choisit la politique : `null` pour une réponse sans contenu (204,
|
|
150
|
+
* corps vide) — même si un `Content-Type` y a été posé, rien n'y sera rendu ;
|
|
151
|
+
* sinon le `Content-Type` posé.
|
|
152
|
+
*/
|
|
153
|
+
protected contentTypeForPolicy(): string | null;
|
|
154
|
+
/**
|
|
155
|
+
* Range les en-têtes de corrélation du contexte — `X-Request-Id` et le
|
|
156
|
+
* `traceparent` W3C — que toute réponse renvoie, quel qu'en soit l'émetteur.
|
|
157
|
+
* Validés par Node à la remise, comme le reste de la réserve.
|
|
158
|
+
*/
|
|
159
|
+
protected putCorrelationHeaders(): void;
|
|
160
|
+
/**
|
|
161
|
+
* Recopie la réserve sur la réponse Node, pour un émetteur qui écrit sans
|
|
162
|
+
* passer par {@link writeHead} — fichier statique, relais vers un amont,
|
|
163
|
+
* adaptateur RPC. La réserve se vide ; ce qui se pose ensuite y retourne, et
|
|
164
|
+
* Node fusionne les deux au `writeHead` (l'émetteur l'emporte, nom par nom).
|
|
165
|
+
*
|
|
166
|
+
* @param contentType - type de la réponse que l'émetteur écrira, quand il
|
|
167
|
+
* est connu d'avance (adaptateur RPC). Omis : il se lit à l'extension du
|
|
168
|
+
* chemin, comme le serveur statique le fera.
|
|
169
|
+
*/
|
|
170
|
+
commitHeaders(contentType?: string): void;
|
|
171
|
+
/**
|
|
172
|
+
* Recopie la réserve sur la réponse Node au PREMIER `setHeader` qu'un tiers
|
|
173
|
+
* y fera — fichier statique servi, redirection de dossier — et seulement
|
|
174
|
+
* alors : un raté du repli statique (la 404 d'une route inconnue) ne recopie
|
|
175
|
+
* rien, sa réponse part ensuite en un seul `writeHead`. À désarmer par
|
|
176
|
+
* {@link disarmNativeWrite} quand le tiers rend la main sans avoir écrit.
|
|
177
|
+
*
|
|
178
|
+
* L'interception est une propriété PROPRE de la réponse, posée une fois avec
|
|
179
|
+
* des attributs constants — toutes les réponses interceptées partagent donc
|
|
180
|
+
* la même forme — et désarmée en lui RÉAFFECTANT la méthode native, jamais
|
|
181
|
+
* en la supprimant : un `delete` fait basculer la réponse Node en mode
|
|
182
|
+
* dictionnaire (constaté par `%HasFastProperties` sur Node 26, 200 réponses
|
|
183
|
+
* sur 200), et chaque accès suivant de `node:http` le paie — mesuré in situ
|
|
184
|
+
* sur un 404 : `writeHead` +1,1 µs, `end` +4,3 µs.
|
|
185
|
+
*/
|
|
186
|
+
commitBeforeNativeWrite(): void;
|
|
187
|
+
/**
|
|
188
|
+
* Retire l'interception posée par {@link commitBeforeNativeWrite} : la
|
|
189
|
+
* propriété propre reçoit la méthode native — la forme de l'objet ne change
|
|
190
|
+
* pas, la réponse reste en mode rapide pour V8.
|
|
191
|
+
*/
|
|
192
|
+
disarmNativeWrite(): void;
|
|
193
|
+
/**
|
|
194
|
+
* Retire un en-tête, de la réserve comme de la réponse Node.
|
|
195
|
+
*
|
|
196
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
197
|
+
*/
|
|
198
|
+
removeHeader(name: string): void;
|
|
65
199
|
setHeaders(obj: OutgoingHttpHeaders): OutgoingHttpHeaders;
|
|
66
|
-
setContentType(type?: string, encoding?: BufferEncoding): void
|
|
67
|
-
setFileMimeType(type: string, encoding?: BufferEncoding): void
|
|
68
|
-
setContentTypeByExtension(extention: string): void
|
|
200
|
+
setContentType(type?: string, encoding?: BufferEncoding): void;
|
|
201
|
+
setFileMimeType(type: string, encoding?: BufferEncoding): void;
|
|
202
|
+
setContentTypeByExtension(extention: string): void;
|
|
69
203
|
getMimeType(filenameOrExt: string): string | false;
|
|
70
204
|
setEncoding(encoding: BufferEncoding): BufferEncoding;
|
|
71
205
|
setStatusCode(status: number | string, message?: string): {
|
|
@@ -88,7 +222,25 @@ declare class HttpResponse {
|
|
|
88
222
|
setBody(ele: unknown, encoding?: BufferEncoding): void;
|
|
89
223
|
setLength(body?: string | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
|
90
224
|
writeHead(statusCode?: number, headers?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): void;
|
|
225
|
+
/** La réserve amorcée — pour une remise sous une autre forme (HTTP/2). */
|
|
226
|
+
protected headerList(): readonly OutgoingHttpHeader[];
|
|
91
227
|
addTrailers(headers: http.OutgoingHttpHeaders): void;
|
|
228
|
+
/**
|
|
229
|
+
* Écrit un morceau d'une réponse en flux, sans la terminer — c'est `end()`
|
|
230
|
+
* qui la clôt après le dernier.
|
|
231
|
+
*
|
|
232
|
+
* Au premier morceau, les en-têtes partent SANS longueur ni corps posé (le
|
|
233
|
+
* patron de `SseStream`), et le cadrage revient à Node : `chunked` pour un
|
|
234
|
+
* client HTTP/1.1, fermeture de connexion pour un HTTP/1.0 (RFC 9112 §6.1 —
|
|
235
|
+
* pas de `Transfer-Encoding` à qui ne le connaît pas), trames DATA en HTTP/2
|
|
236
|
+
* où l'en-tête est interdit (RFC 9113 §8.2.2). Ne JAMAIS le poser à la main :
|
|
237
|
+
* une fois `flushing` levé, `setHeader` range en trailer, et un
|
|
238
|
+
* `Transfer-Encoding` en trailer est un message malformé (RFC 9110 §6.5.1).
|
|
239
|
+
*
|
|
240
|
+
* @param chunk - le morceau à écrire.
|
|
241
|
+
* @param encoding - l'encodage d'un morceau texte.
|
|
242
|
+
* @returns la promesse réglée quand le flux accepte l'écriture suivante.
|
|
243
|
+
*/
|
|
92
244
|
flush(chunk: unknown, encoding: BufferEncoding): MaybePromise<HttpResponse>;
|
|
93
245
|
/**
|
|
94
246
|
* Écrit le corps sur le flux et, pour une réponse unique, la termine.
|
|
@@ -113,8 +265,36 @@ declare class HttpResponse {
|
|
|
113
265
|
* @throws de façon SYNCHRONE quand la réponse Node n'existe plus.
|
|
114
266
|
*/
|
|
115
267
|
end(chunk?: string | Buffer, encoding?: BufferEncoding): MaybePromise<http.ServerResponse | http2.ServerHttp2Stream>;
|
|
268
|
+
/**
|
|
269
|
+
* Règle le sort d'un corps de requête resté NON LU au moment de répondre —
|
|
270
|
+
* route inconnue (404, 405), refus avant l'action, 408, 413. À appeler AVANT
|
|
271
|
+
* l'écriture des en-têtes ; sans effet si le corps est entièrement reçu.
|
|
272
|
+
*
|
|
273
|
+
* HTTP/1.1 : Node vide lui-même un corps non consommé une fois la réponse
|
|
274
|
+
* écrite, pour réutiliser la connexion — sans borne. On ne le laisse faire
|
|
275
|
+
* que pour un corps DÉCLARÉ d'au plus {@link UNREAD_BODY_DRAIN_LIMIT}
|
|
276
|
+
* octets ; au-delà, ou sans longueur déclarée (`transfer-encoding`), la
|
|
277
|
+
* réponse part avec `connection: close` (RFC 9112 §9.6) et la connexion
|
|
278
|
+
* n'est pas réutilisée — fermée par étapes ({@link lingerBeforeClose}),
|
|
279
|
+
* pour que le client qui écrit encore lise la réponse. La décision se prend
|
|
280
|
+
* sur la longueur ANNONCÉE, sans attendre un octet : la réponse n'est jamais
|
|
281
|
+
* retardée par le corps.
|
|
282
|
+
*
|
|
283
|
+
* HTTP/2 surcharge cette méthode (`Http2Response`) : fermeture du flux en
|
|
284
|
+
* `NO_ERROR` une fois la réponse écrite. Une seule entrée pour les deux
|
|
285
|
+
* protocoles, partagée par tout refus qui laisse un corps non lu (ADR-0026,
|
|
286
|
+
* D5).
|
|
287
|
+
*/
|
|
288
|
+
closeUnfinishedRequest(): void;
|
|
289
|
+
/**
|
|
290
|
+
* Valeur d'un en-tête posé — dans la réserve, sinon sur la réponse Node (ce
|
|
291
|
+
* qu'un tiers y a écrit directement : `send`, quota).
|
|
292
|
+
*
|
|
293
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
294
|
+
*/
|
|
116
295
|
getHeader(name: string): string | number | string[] | undefined;
|
|
117
296
|
hasHeader(name: string): boolean;
|
|
297
|
+
/** Tous les en-têtes posés, noms en minuscules — une copie, à chaque appel. */
|
|
118
298
|
getHeaders(): http.OutgoingHttpHeaders;
|
|
119
299
|
redirect(url: string, status?: number | string, headers?: Record<string, string | number>): this;
|
|
120
300
|
log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type HttpContext from "./HttpContext.js";
|
|
2
|
+
import type { DefaultEventsMap, EventNames, EventsMap } from "nodefony";
|
|
2
3
|
/**
|
|
3
4
|
* Le flux d'événements serveur (SSE) d'une réponse HTTP — `text/event-stream`.
|
|
4
5
|
*
|
|
@@ -56,27 +57,49 @@ export interface ISseStreamOptions {
|
|
|
56
57
|
*/
|
|
57
58
|
maxDuration?: number | false | undefined;
|
|
58
59
|
}
|
|
59
|
-
/**
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Ce qu'un événement porte en plus de ses données.
|
|
62
|
+
*
|
|
63
|
+
* @typeParam K - nom de l'événement : un canal du contrat quand le flux en a un.
|
|
64
|
+
*/
|
|
65
|
+
export interface ISseSendOptions<K extends string = string> {
|
|
61
66
|
/** Type de l'événement (champ `event:`) — absent : `message`. */
|
|
62
|
-
event?:
|
|
67
|
+
event?: K | undefined;
|
|
63
68
|
/** Identifiant (champ `id:`) que le client renverra en `Last-Event-ID`. */
|
|
64
69
|
id?: string | undefined;
|
|
65
70
|
}
|
|
66
71
|
/** Battement de cœur par défaut, en ms (§9.2.7 : « every 15 seconds or so »). */
|
|
67
72
|
export declare const SSE_HEARTBEAT_MS = 15000;
|
|
68
|
-
/** Délai de blocage d'écriture par défaut, en ms. */
|
|
73
|
+
/** Délai de blocage d'écriture par défaut, en ms — celui de tout flux. */
|
|
69
74
|
export declare const SSE_STALL_TIMEOUT_MS = 30000;
|
|
70
75
|
/** Taille maximale d'un événement par défaut, en octets (1 Mio). */
|
|
71
76
|
export declare const SSE_MAX_EVENT_BYTES = 1048576;
|
|
77
|
+
/**
|
|
78
|
+
* Charge d'un événement SSE : celle du canal nommé par `event`, ou — sans
|
|
79
|
+
* `event` (« un flux = un canal ») — l'une de celles de la carte. Carte non
|
|
80
|
+
* déclarée : `unknown`.
|
|
81
|
+
*/
|
|
82
|
+
export type SseEventData<Events extends EventsMap, K extends string> = [
|
|
83
|
+
K
|
|
84
|
+
] extends [never] ? Events[EventNames<Events>] : Events[K];
|
|
72
85
|
/**
|
|
73
86
|
* Un flux `text/event-stream` ouvert sur une réponse.
|
|
74
87
|
*
|
|
75
88
|
* Il s'obtient par {@link openSseStream} (ou `this.renderSse()` dans un
|
|
76
89
|
* contrôleur), jamais par `new` : l'ouverture écrit les en-têtes, et un flux
|
|
77
90
|
* sans en-têtes ne se rattrape pas.
|
|
91
|
+
*
|
|
92
|
+
* Typé par une carte d'événements (`ContractServerEmit<C>` : le MÊME contrat
|
|
93
|
+
* que la socket temps réel) : `event:` est un canal de la carte et la charge est
|
|
94
|
+
* celle du canal. Deux formes se lisent sur la même carte — **multiplexé**
|
|
95
|
+
* (`send(data, { event: canal })`, plusieurs canaux sur une connexion) ou **un
|
|
96
|
+
* flux = un canal** (carte d'un seul canal, `send(data)` sans `event`). Sans
|
|
97
|
+
* carte, le flux reste libre (porte MCP, flux hors contrat). Le type n'est pas
|
|
98
|
+
* une validation, et ne coûte rien à l'exécution.
|
|
99
|
+
*
|
|
100
|
+
* @typeParam Events - carte des événements du flux.
|
|
78
101
|
*/
|
|
79
|
-
export declare class SseStream {
|
|
102
|
+
export declare class SseStream<Events extends EventsMap = DefaultEventsMap> {
|
|
80
103
|
#private;
|
|
81
104
|
/** Le flux est-il fermé — par le serveur (`close()`) ou par le client parti ? */
|
|
82
105
|
get closed(): boolean;
|
|
@@ -89,10 +112,12 @@ export declare class SseStream {
|
|
|
89
112
|
* @returns rien quand l'écriture est acceptée ; une promesse réglée au
|
|
90
113
|
* `drain` quand le client lit moins vite qu'on écrit — l'attendre borne la
|
|
91
114
|
* mémoire du serveur. Sans effet une fois le flux fermé.
|
|
92
|
-
* @throws TypeError quand `event` ou `id` contient une fin de ligne
|
|
115
|
+
* @throws TypeError quand `event` ou `id` contient une fin de ligne, ou
|
|
116
|
+
* quand `data` est fait d'octets (`Uint8Array`, `Buffer`, `ArrayBuffer`) :
|
|
117
|
+
* un flux SSE ne porte que du texte, les encoder soi-même (base64).
|
|
93
118
|
* @throws RangeError quand l'événement dépasse `maxEventBytes`.
|
|
94
119
|
*/
|
|
95
|
-
send(data:
|
|
120
|
+
send<K extends EventNames<Events> = never>(data: SseEventData<Events, K>, options?: ISseSendOptions<K>): Promise<void> | undefined;
|
|
96
121
|
/**
|
|
97
122
|
* Envoie un commentaire — ignoré par le client, utile pour garder une
|
|
98
123
|
* connexion vivante ou marquer le flux à la lecture.
|
|
@@ -152,4 +177,4 @@ export declare function acceptsEventStream(accept: string | string[] | undefined
|
|
|
152
177
|
* @throws RangeError quand un délai n'est ni `false` ni un entier entre 1 et
|
|
153
178
|
* 2^31-1, ou que `maxEventBytes` n'est pas un entier positif.
|
|
154
179
|
*/
|
|
155
|
-
export declare function openSseStream(context: HttpContext, options?: ISseStreamOptions): SseStream | Promise<SseStream
|
|
180
|
+
export declare function openSseStream<Events extends EventsMap = DefaultEventsMap>(context: HttpContext, options?: ISseStreamOptions): SseStream<Events> | Promise<SseStream<Events>>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import type http2 from "node:http2";
|
|
3
|
+
/**
|
|
4
|
+
* Cycle de vie d'une réponse en flux — la brique commune au flux d'événements
|
|
5
|
+
* (`SseStream`) et au flux d'octets, NON exportée.
|
|
6
|
+
*
|
|
7
|
+
* Elle porte ce qu'un flux doit à la requête qui l'ouvre, quel que soit son
|
|
8
|
+
* format :
|
|
9
|
+
* - la PRISE de la réponse (`sended`), avant toute attente : le noyau ne peut
|
|
10
|
+
* plus répondre par-dessus, et l'avertissement « réponse non envoyée » se tait ;
|
|
11
|
+
* - la session sauvée AVANT les en-têtes — ses cookies partent avec eux ;
|
|
12
|
+
* - la fermeture lue sur la RÉPONSE, écouteur posé de façon SYNCHRONE à
|
|
13
|
+
* l'ouverture : un client parti pendant la sauvegarde de la session est vu ;
|
|
14
|
+
* - le délai d'inactivité de la requête désarmé, remplacé par les gardes du
|
|
15
|
+
* flux : coupure d'un client qui ne lit plus (`stallTimeout`), durée de vie
|
|
16
|
+
* maximale (`maxDuration`) ;
|
|
17
|
+
* - le plafond de connexions longues par IP (`wsMaxConnectionsPerIp`, le budget
|
|
18
|
+
* des sockets WebSocket) et la fermeture à la révocation de l'identité ;
|
|
19
|
+
* - une fin unique, et des écouteurs de fin liés au contexte asynchrone.
|
|
20
|
+
*
|
|
21
|
+
* Le FORMAT (en-têtes, écriture, battement de cœur) appartient au flux qui la
|
|
22
|
+
* porte.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Plus long délai qu'un minuteur sait tenir (2^31-1 ms). Au-delà, Node le
|
|
26
|
+
* ramène à 1 ms : un délai qui le dépasse ferait battre ou couper le flux
|
|
27
|
+
* mille fois par seconde.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_TIMER_MS = 2147483647;
|
|
30
|
+
/**
|
|
31
|
+
* Délai de blocage par défaut d'un flux, en ms : un client qui ne lit plus
|
|
32
|
+
* depuis ce délai est coupé. Valeur commune aux flux d'événements, d'octets et
|
|
33
|
+
* aux flux RPC.
|
|
34
|
+
*/
|
|
35
|
+
export declare const STREAM_STALL_TIMEOUT_MS = 30000;
|
|
36
|
+
/**
|
|
37
|
+
* Période du balayage de blocage ({@link StallSweep}) : la moitié du délai de
|
|
38
|
+
* blocage. Un client bloqué est coupé entre `stallTimeout` et
|
|
39
|
+
* `stallTimeout + période` après la première contre-pression vue — jamais
|
|
40
|
+
* avant le délai réglé.
|
|
41
|
+
*/
|
|
42
|
+
export declare const STALL_SWEEP_PERIOD_MS: number;
|
|
43
|
+
/**
|
|
44
|
+
* Attente maximale, à l'arrêt du serveur, des flux longs qu'on vient de
|
|
45
|
+
* terminer (`HttpKernel.shutdownStreams`), avant le drain des connexions.
|
|
46
|
+
*
|
|
47
|
+
* Un flux qui écoute son signal se ferme en quelques millisecondes : l'attente
|
|
48
|
+
* finit avec lui. Elle ne dure jusqu'à son terme que pour un gestionnaire qui
|
|
49
|
+
* n'écoute rien — et ce temps s'AJOUTE au drain (`shutdownTimeout`), d'où une
|
|
50
|
+
* borne courte, celle de l'annonce du GOAWAY.
|
|
51
|
+
*/
|
|
52
|
+
export declare const SHUTDOWN_STREAMS_WAIT_MS = 1000;
|
|
53
|
+
/**
|
|
54
|
+
* Qui a fermé le flux, quand ce n'est pas le client : le serveur (`close()` —
|
|
55
|
+
* fin demandée, durée de vie, révocation), ou la coupure d'un client qui ne lit
|
|
56
|
+
* plus. `null` : le client est parti, ou le flux est encore ouvert.
|
|
57
|
+
*/
|
|
58
|
+
export type StreamLifecycleCause = "server" | "stall" | null;
|
|
59
|
+
/** Ce sur quoi on écrit : la réponse HTTP/1.1, ou le flux HTTP/2. */
|
|
60
|
+
export type StreamTarget = http.ServerResponse | http2.ServerHttp2Stream;
|
|
61
|
+
/** Gardes et identité d'un flux, options résolues. */
|
|
62
|
+
export interface IStreamLifecycleSettings {
|
|
63
|
+
stallTimeout: number | false;
|
|
64
|
+
maxDuration: number | false;
|
|
65
|
+
/** Revalidateur de l'identité du flux — `null` pour un anonyme. */
|
|
66
|
+
revalidate: (() => Promise<boolean>) | null;
|
|
67
|
+
/** Identifiant de connexion de l'identité qui ouvre le flux — `null` anonyme. */
|
|
68
|
+
identifier: string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Les écritures appartiennent à un tiers (adaptateur RPC) : la brique ne les
|
|
71
|
+
* voit pas. Le blocage s'observe alors par le balayage du noyau
|
|
72
|
+
* ({@link StallSweep}), et la fermeture par le serveur détruit le transport
|
|
73
|
+
* au lieu de terminer la réponse — jamais un second écrivain.
|
|
74
|
+
*/
|
|
75
|
+
foreignWriter: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Le flux n'a pas de fin à lui (SSE, flux RPC, sortie d'un processus) : à
|
|
78
|
+
* l'arrêt du serveur, le noyau le termine AVANT le drain des connexions
|
|
79
|
+
* ({@link StreamLifecycle.shutdown}), au lieu de le laisser détruire sans un
|
|
80
|
+
* mot à `shutdownTimeout`. `false` pour une source FINIE (une archive) : elle
|
|
81
|
+
* a le drain pour finir, comme toute réponse en vol.
|
|
82
|
+
*/
|
|
83
|
+
endsAtShutdown: boolean;
|
|
84
|
+
}
|
|
85
|
+
/** Ce que l'ouverture reçoit de l'appelant. */
|
|
86
|
+
export interface IOpenStreamLifecycle {
|
|
87
|
+
/** Préfixe des messages d'erreur (`SSE`, `Flux`). */
|
|
88
|
+
label: string;
|
|
89
|
+
stallTimeout: number | false;
|
|
90
|
+
maxDuration: number | false;
|
|
91
|
+
/**
|
|
92
|
+
* Un tiers tient les écritures (adaptateur RPC) — cf
|
|
93
|
+
* {@link IStreamLifecycleSettings.foreignWriter}. Défaut `false`.
|
|
94
|
+
*/
|
|
95
|
+
foreignWriter?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Terminé par le noyau à l'arrêt du serveur — cf
|
|
98
|
+
* {@link IStreamLifecycleSettings.endsAtShutdown}. Défaut `true`.
|
|
99
|
+
*/
|
|
100
|
+
endsAtShutdown?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/** Ce que le registre de l'arrêt retient d'un flux : de quoi le terminer. */
|
|
103
|
+
export interface IShutdownStream {
|
|
104
|
+
/** Le serveur s'arrête : le flux prend fin (cf {@link StreamLifecycle.shutdown}). */
|
|
105
|
+
shutdown(): unknown;
|
|
106
|
+
}
|
|
@@ -2,22 +2,36 @@ import HttpRequest from "./Request.js";
|
|
|
2
2
|
import Http2Request from "../http2/Request.js";
|
|
3
3
|
import QS from "qs";
|
|
4
4
|
import xml2js from "xml2js";
|
|
5
|
+
import HttpError from "../../errors/httpError.js";
|
|
5
6
|
declare class Parser {
|
|
6
7
|
request: HttpRequest | Http2Request;
|
|
7
8
|
chunks: Buffer[];
|
|
8
9
|
private received;
|
|
9
10
|
private aborted;
|
|
10
|
-
private
|
|
11
|
-
private
|
|
11
|
+
private failure;
|
|
12
|
+
private onFailure;
|
|
12
13
|
constructor(request: HttpRequest | Http2Request);
|
|
13
14
|
write(buffer: Buffer): Buffer;
|
|
15
|
+
/**
|
|
16
|
+
* Interrompt la réception du corps : `parse()` rejette avec `error`, que
|
|
17
|
+
* l'interruption survienne avant ou pendant l'attente de la fin du flux.
|
|
18
|
+
*
|
|
19
|
+
* La RAM déjà bufferisée est libérée et la lecture du flux suspendue ; la
|
|
20
|
+
* réponse d'erreur peut encore partir. Sans effet si le corps est déjà
|
|
21
|
+
* entièrement reçu, ou déjà interrompu.
|
|
22
|
+
*
|
|
23
|
+
* @param error - la cause rendue au client (413 dépassement, 408 délai)
|
|
24
|
+
* @returns vrai si la réception a été interrompue
|
|
25
|
+
*/
|
|
26
|
+
fail(error: HttpError): boolean;
|
|
14
27
|
/**
|
|
15
28
|
* Résout quand le flux requête est **entièrement reçu** (`end`). No-op si déjà
|
|
16
29
|
* terminé (corps vide / déjà drainé) → pas de hang d'un `once("end")` tardif.
|
|
17
30
|
* Indispensable avant de concaténer les chunks pour un corps à décoder
|
|
18
31
|
* (JSON…) : sinon on lit un buffer encore incomplet. (formidable drainait via
|
|
19
32
|
* son `await form.parse()` ; ses remplaçants doivent drainer explicitement.)
|
|
20
|
-
* Rejette
|
|
33
|
+
* Rejette avec la cause de {@link Parser.fail} (413 dépassement de
|
|
34
|
+
* `maxBodySize`, 408 délai de réception), avant ou pendant l'attente.
|
|
21
35
|
*/
|
|
22
36
|
protected ended(): Promise<void>;
|
|
23
37
|
parse(): Promise<this>;
|
|
@@ -36,6 +36,46 @@ export interface ISplitTarget {
|
|
|
36
36
|
* @returns les composants découpés, ou `null` (bail-out → vrai `new URL`).
|
|
37
37
|
*/
|
|
38
38
|
export declare function splitTarget(target: unknown): ISplitTarget | null;
|
|
39
|
+
/** Un request-target en forme absolue, découpé en autorité et forme origine. */
|
|
40
|
+
export interface IAbsoluteTarget {
|
|
41
|
+
/** `hôte[:port]` de la cible — l'hôte de la requête (RFC 9112 §3.2.2). */
|
|
42
|
+
authority: string;
|
|
43
|
+
/** La forme origine (`/chemin[?query]`), `/` quand la cible n'a pas de chemin. */
|
|
44
|
+
target: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Découpe un request-target en forme absolue (`http://hôte:port/chemin?q`) :
|
|
48
|
+
* la forme qu'un client envoie à un proxy, et qu'un serveur d'origine DOIT
|
|
49
|
+
* accepter (RFC 9112 §3.2.2). Il DOIT alors ignorer l'en-tête `Host` et prendre
|
|
50
|
+
* l'hôte de la cible — le recoller à `Host` fabriquait un hôte qui n'existe pas.
|
|
51
|
+
*
|
|
52
|
+
* Seuls `http` et `https` sont reconnus (RFC 6455 §4.2.1 vaut aussi pour un
|
|
53
|
+
* handshake WebSocket) ; le schéma de la cible ne remplace jamais celui de la
|
|
54
|
+
* connexion. Une autorité porteuse d'identifiants (`user@hôte`, interdite par
|
|
55
|
+
* RFC 9110 §4.2.4) n'est pas reconnue : `Host` garde la main.
|
|
56
|
+
*
|
|
57
|
+
* Chemin de chaque requête : un seul test de caractère pour la forme origine,
|
|
58
|
+
* aucune allocation.
|
|
59
|
+
*
|
|
60
|
+
* @param target - le request-target brut, tel que reçu.
|
|
61
|
+
* @returns l'autorité et la forme origine, ou `null` pour toute autre forme.
|
|
62
|
+
*/
|
|
63
|
+
export declare function splitAbsoluteForm(target: string | undefined): IAbsoluteTarget | null;
|
|
64
|
+
/**
|
|
65
|
+
* Analyse un request-target origin-form (`/chemin[?query]`) dans la forme
|
|
66
|
+
* NORMALISÉE que la porte HTTP donne au routeur (`HttpRequest`) : découpe exacte
|
|
67
|
+
* quand le parse WHATWG serait l'identité ({@link splitTarget}), vrai parse
|
|
68
|
+
* WHATWG sinon — dot-segments résolus, `\` replié, percent-encoding posé.
|
|
69
|
+
*
|
|
70
|
+
* Pour qui reçoit un chemin hors d'une requête HTTP — le pont WebSocket
|
|
71
|
+
* `api.request` — et doit router exactement comme HTTP : la même chaîne, deux
|
|
72
|
+
* transports, une seule forme.
|
|
73
|
+
*
|
|
74
|
+
* @param target - le chemin reçu ; il doit commencer par `/`.
|
|
75
|
+
* @returns pathname et search normalisés, ou `null` si `target` n'est pas un
|
|
76
|
+
* chemin origin-form analysable.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseRequestTarget(target: string): ISplitTarget | null;
|
|
39
79
|
/**
|
|
40
80
|
* L'autorité (`host[:port]` brut de l'en-tête `Host` / `:authority`) est-elle
|
|
41
81
|
* déjà sous la forme exacte que le parseur WHATWG rendrait ?
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type http from "node:http";
|
|
2
|
+
import type { ByteStreamSource } from "./ByteStream.js";
|
|
3
|
+
/** Ce qu'une `Response` Web devient pour le flux d'octets de Nodefony. */
|
|
4
|
+
export interface IWebResponseParts {
|
|
5
|
+
/** Statut de la réponse. */
|
|
6
|
+
status: number;
|
|
7
|
+
/** En-têtes à poser, filtrés (voir {@link webResponseParts}). */
|
|
8
|
+
headers: http.OutgoingHttpHeaders;
|
|
9
|
+
/** Corps, Web ou vide — jamais `null`. */
|
|
10
|
+
source: ByteStreamSource;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Traduit une `Response` Web standard en statut, en-têtes et corps pour le flux
|
|
14
|
+
* d'octets — sans la lire : le corps reste un flux.
|
|
15
|
+
*
|
|
16
|
+
* Deux origines, deux règles, que `response.type` distingue :
|
|
17
|
+
* - **construite par l'application** (`new Response(…)`, `Response.json(…)`,
|
|
18
|
+
* `Response.redirect(…)` — `type === "default"`) : ses en-têtes sont les
|
|
19
|
+
* siens, cookies compris (`getSetCookie()`), et partent tels quels ;
|
|
20
|
+
* - **relayée** (issue de `fetch` — `"basic"`, `"cors"`…) : `content-encoding`,
|
|
21
|
+
* `content-length` et `set-cookie` de l'amont sont écartés (voir
|
|
22
|
+
* `UPSTREAM_ONLY`).
|
|
23
|
+
*
|
|
24
|
+
* Les en-têtes de connexion (RFC 9110 §7.6.1 — la liste du relais,
|
|
25
|
+
* `HOP_BY_HOP`) ne partent jamais : ils décrivent un saut, et HTTP/2 les
|
|
26
|
+
* refuse.
|
|
27
|
+
*
|
|
28
|
+
* @param response - une `Response` dont le corps n'a pas été consommé.
|
|
29
|
+
* @returns statut, en-têtes filtrés et corps.
|
|
30
|
+
*/
|
|
31
|
+
export declare function webResponseParts(response: Response): IWebResponseParts;
|