@nodefony/http 10.0.0-beta.2 → 10.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +19 -11
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +45 -6
- package/dist/nodefony/config/defineModuleConfig.js +2 -3
- package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -3
- package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
- package/dist/nodefony/service/audit-logger.js +22 -16
- package/dist/nodefony/service/certificates.js +228 -19
- package/dist/nodefony/service/error-renderer.js +30 -13
- package/dist/nodefony/service/http-kernel.js +331 -82
- package/dist/nodefony/service/pretty-request-logger.js +5 -4
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +336 -0
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +7 -10
- package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
- package/dist/nodefony/service/servers/server-websocket.js +18 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
- package/dist/nodefony/service/sessions/sessions-service.js +34 -20
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +51 -32
- package/dist/nodefony/src/context/domainMatcher.js +21 -4
- package/dist/nodefony/src/context/forwarded.js +3 -4
- package/dist/nodefony/src/context/http/HttpContext.js +98 -46
- package/dist/nodefony/src/context/http/Request.js +34 -38
- package/dist/nodefony/src/context/http/Response.js +39 -7
- package/dist/nodefony/src/context/http/SseStream.js +349 -0
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +22 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/responseEnded.js +26 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/websocket/Response.js +42 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
- package/dist/nodefony/src/profiler/Profiler.js +108 -30
- package/dist/nodefony/src/profiler/aggregate.js +422 -0
- package/dist/nodefony/src/proxy/forward.js +500 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +188 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +128 -0
- package/dist/nodefony/src/session/session.js +30 -15
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +12 -3
- package/dist/types/nodefony/config/config.d.ts +16 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
- package/dist/types/nodefony/service/certificates.d.ts +147 -5
- package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
- package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +2 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
- package/dist/types/nodefony/src/context/Context.d.ts +35 -6
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
- package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +88 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +69 -0
- package/dist/types/nodefony/src/session/session.d.ts +2 -0
- package/docs/cookies.md +85 -78
- package/docs/index.md +5 -1
- package/docs/observabilite.md +81 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +522 -0
- package/docs/servers.md +378 -230
- package/docs/session.md +180 -180
- package/docs/sse.md +287 -0
- package/docs/upload.md +120 -117
- package/package.json +10 -11
- package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -13,6 +13,18 @@ export interface IWsHeartbeatOptions {
|
|
|
13
13
|
/** Délai (ms) accordé pour recevoir le `pong` avant de couper la socket. */
|
|
14
14
|
keepaliveGracePeriod?: number;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Ce que le tick lit et écrit d'une connexion — la socket `ws` en production.
|
|
18
|
+
*/
|
|
19
|
+
export interface IHeartbeatClient {
|
|
20
|
+
readonly readyState: number;
|
|
21
|
+
ping(): void;
|
|
22
|
+
terminate(): void;
|
|
23
|
+
/** Dernier signe de vie (date du dernier `pong`, ou de la connexion). */
|
|
24
|
+
_nfLastPong?: number;
|
|
25
|
+
/** Date du dernier `ping` émis ; `0` = aucun ping en attente de réponse. */
|
|
26
|
+
_nfPingedAt?: number;
|
|
27
|
+
}
|
|
16
28
|
/**
|
|
17
29
|
* Arme le suivi keep-alive d'UNE connexion : initialise l'horodatage de vie et
|
|
18
30
|
* attache l'unique listener `pong` qui le rafraîchit.
|
|
@@ -24,6 +36,20 @@ export interface IWsHeartbeatOptions {
|
|
|
24
36
|
* @param ws - la socket fraîchement connectée
|
|
25
37
|
*/
|
|
26
38
|
export declare const trackPong: (ws: Ws) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Un tick du keep-alive : pingue les connexions muettes depuis `interval`, et
|
|
41
|
+
* coupe celles dont le ping attend sa réponse depuis plus de `grace`.
|
|
42
|
+
*
|
|
43
|
+
* Fonction PURE du temps : l'horloge est passée en argument, si bien que la
|
|
44
|
+
* règle se prouve pas à pas, sans minuteur réel — un banc qui la prouverait à
|
|
45
|
+
* l'horloge murale confondrait une boucle d'événements chargée avec un zombie.
|
|
46
|
+
*
|
|
47
|
+
* @param clients - les connexions du serveur
|
|
48
|
+
* @param now - l'instant du tick (`performance.now()` en production)
|
|
49
|
+
* @param interval - délai sans signe de vie avant un ping, ms
|
|
50
|
+
* @param grace - délai accordé au pong avant de couper, ms
|
|
51
|
+
*/
|
|
52
|
+
export declare const heartbeatTick: (clients: Iterable<IHeartbeatClient>, now: number, interval: number, grace: number) => void;
|
|
27
53
|
/**
|
|
28
54
|
* Démarre le heartbeat keep-alive du serveur : détecte les connexions zombies
|
|
29
55
|
* (half-open — TCP encore ouvert mais le pair a disparu sans frame Close) et les
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Service, Module } from "nodefony";
|
|
2
2
|
import type { IPage } from "nodefony";
|
|
3
|
-
import type { ISessionStorage, ISessionSummary, ISessionRecord, ISessionListQuery } from "../../interfaces/ISession.js";
|
|
3
|
+
import type { ISessionStorage, ISerializedSession, ISessionSummary, ISessionRecord, ISessionListFilter, ISessionListQuery } from "../../interfaces/ISession.js";
|
|
4
4
|
import HttpKernel, { ContextType } from "../http-kernel.js";
|
|
5
5
|
import Session, { OptionsSessionType } from "../../src/session/session.js";
|
|
6
6
|
import type { IHttpConfig } from "../../config/config.js";
|
|
@@ -191,6 +191,24 @@ declare class SessionsService extends Service {
|
|
|
191
191
|
* est sûre. Idempotent — `false` si aucun `ref` ne correspond.
|
|
192
192
|
*/
|
|
193
193
|
destroyByRef(ref: string, actor?: string | null): Promise<boolean>;
|
|
194
|
+
/**
|
|
195
|
+
* Détruit TOUTES les sessions dont le contenu satisfait `match` — la primitive
|
|
196
|
+
* des révocations groupées (« déconnecter partout », déconnexion demandée par
|
|
197
|
+
* un fournisseur d'identité). Ni journal ni audit : l'appelant, qui connaît le
|
|
198
|
+
* motif, les émet.
|
|
199
|
+
*
|
|
200
|
+
* @remarks Quand la méthode rend la main, il ne reste AUCUNE session qui
|
|
201
|
+
* satisfait `match` (au plus `MAX_LOGOUT_PASSES` passages). Une révocation qui
|
|
202
|
+
* en laisse une seule n'est pas une imprécision, c'est une faille.
|
|
203
|
+
*
|
|
204
|
+
* @param filter - restriction poussée au store (`user` : WHERE indexable) ;
|
|
205
|
+
* `undefined` parcourt tout le parc.
|
|
206
|
+
* @param match - prédicat sur la session sérialisée — il doit RE-VÉRIFIER ce
|
|
207
|
+
* que `filter` exprime, un store pouvant l'ignorer.
|
|
208
|
+
* @returns le nombre de sessions détruites.
|
|
209
|
+
* @throws Error si le store actif ne sait pas énumérer ses sessions.
|
|
210
|
+
*/
|
|
211
|
+
destroyWhere(filter: ISessionListFilter | undefined, match: (data: ISerializedSession) => boolean): Promise<number>;
|
|
194
212
|
/**
|
|
195
213
|
* « Déconnexion partout » : détruit TOUTES les sessions d'un utilisateur (scan
|
|
196
214
|
* O(N) — pas d'index inverse, acceptable en admin). Renvoie le nombre détruit.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Container, Service, Severity, Msgid, Message, Pci, Pdu, KernelEventsType, nodefonyError, Scope } from "nodefony";
|
|
2
|
-
import type { IProfilerQuery } from "nodefony";
|
|
2
|
+
import type { IProfilerQuery, RequestContextPayload } from "nodefony";
|
|
3
3
|
import type { IRouteResolver, IRequestRouter } from "../../interfaces/IRouting.js";
|
|
4
4
|
import type { ISecurityZone } from "../../interfaces/ISecurity.js";
|
|
5
5
|
import { WebSocketServer } from "ws";
|
|
@@ -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";
|
|
@@ -43,7 +44,6 @@ declare class Context extends Service implements IContextInterface {
|
|
|
43
44
|
secure: boolean;
|
|
44
45
|
security?: ISecurityZone | null | undefined;
|
|
45
46
|
cleaned: boolean;
|
|
46
|
-
isControlledAccess: boolean;
|
|
47
47
|
validDomain: boolean;
|
|
48
48
|
finished: boolean;
|
|
49
49
|
pushAllowed: boolean;
|
|
@@ -51,7 +51,15 @@ declare class Context extends Service implements IContextInterface {
|
|
|
51
51
|
requested: boolean;
|
|
52
52
|
sessionStarting: boolean;
|
|
53
53
|
domain: string;
|
|
54
|
-
|
|
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;
|
|
55
63
|
server?: WebSocketServer | http.Server | https.Server | http2.Http2SecureServer | null | undefined;
|
|
56
64
|
httpKernel: HttpKernel | null;
|
|
57
65
|
request: contextRequest | null;
|
|
@@ -68,6 +76,14 @@ declare class Context extends Service implements IContextInterface {
|
|
|
68
76
|
session: Session | null | undefined;
|
|
69
77
|
cookieSession: Cookie | null | undefined;
|
|
70
78
|
user: unknown;
|
|
79
|
+
/**
|
|
80
|
+
* Identifiant de l'appelant AUTHENTIFIÉ, posé par le pare-feu ; `null` hors
|
|
81
|
+
* zone ou pour un anonyme. Lisible au démontage, hors de la bulle ALS : c'est
|
|
82
|
+
* ce que lisent le journal d'audit et le profileur. Distinct de `user`, qui
|
|
83
|
+
* lie la session à son principal de SESSION — y écrire l'identité d'un jeton
|
|
84
|
+
* rattacherait la session d'un autre compte.
|
|
85
|
+
*/
|
|
86
|
+
userIdentifier: string | null;
|
|
71
87
|
waitAsync: boolean;
|
|
72
88
|
isJson: boolean;
|
|
73
89
|
protected _isHtml: boolean | null;
|
|
@@ -116,11 +132,12 @@ declare class Context extends Service implements IContextInterface {
|
|
|
116
132
|
private _phaseIndex;
|
|
117
133
|
private _afterResponseFns;
|
|
118
134
|
private _afterResponseFired;
|
|
135
|
+
private requestStore;
|
|
119
136
|
private _abortController;
|
|
120
137
|
metaData: Data;
|
|
121
138
|
scheme: SchemeType;
|
|
122
139
|
webSocketState: WebSocketState;
|
|
123
|
-
constructor(container: Container | Scope,
|
|
140
|
+
constructor(container: Container | Scope, serverType: ServerType);
|
|
124
141
|
/**
|
|
125
142
|
* Assemble l'enveloppe `metaData` de la requête (`nodefony.*` + `route`
|
|
126
143
|
* snapshot per-requête + overrides appelant). Délègue au builder monomorphe
|
|
@@ -160,11 +177,23 @@ declare class Context extends Service implements IContextInterface {
|
|
|
160
177
|
get signal(): AbortSignal;
|
|
161
178
|
_abortIfPending(reason?: string): void;
|
|
162
179
|
log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
|
|
180
|
+
/**
|
|
181
|
+
* Confie au contexte le magasin `RequestContext` de son unité de travail
|
|
182
|
+
* (requête HTTP, connexion WebSocket), pour qu'il le VIDE à son nettoyage.
|
|
183
|
+
*
|
|
184
|
+
* Un minuteur ou une promesse armé pendant l'unité de travail capture ce
|
|
185
|
+
* magasin et le garde au-delà de la réponse — avec le contexte, l'identité
|
|
186
|
+
* et le jeton du pare-feu. Le vidage ({@link RequestContext.release}) n'y
|
|
187
|
+
* laisse que la corrélation et le scope refermé.
|
|
188
|
+
*
|
|
189
|
+
* @param store - le magasin passé à `RequestContext.run`
|
|
190
|
+
*/
|
|
191
|
+
holdRequestStore(store: RequestContextPayload): void;
|
|
163
192
|
clean(): void;
|
|
164
193
|
/**
|
|
165
194
|
* Log d'un event du cycle de vie — gate boot-time (V2.1) : en production le
|
|
166
|
-
* Pdu n'est PAS construit (return avant toute allocation). Hors prod,
|
|
167
|
-
*
|
|
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.
|
|
168
197
|
*/
|
|
169
198
|
private logEvent;
|
|
170
199
|
fire(event: KernelEventsType, ...args: unknown[]): boolean;
|
|
@@ -13,12 +13,22 @@
|
|
|
13
13
|
* Politique de compilation (sûre, ancrée, ReDoS-free) :
|
|
14
14
|
* - string sans `*` → match EXACT ancré (`^...$`, le `.` est littéral).
|
|
15
15
|
* - string avec `*` → wildcard UN label (`*.example.com` → `^[^.]+\.example\.com$`,
|
|
16
|
-
* RFC
|
|
16
|
+
* RFC 9525 TLS-wildcard : matche `img.example.com`, pas `a.b.example.com` ni `example.com`).
|
|
17
17
|
* - `RegExp` → reprise telle quelle (l'auteur assume l'ancrage).
|
|
18
18
|
*
|
|
19
19
|
* Compilation faite UNE fois (boot / enregistrement de route) ; le test par
|
|
20
20
|
* requête est un simple `RegExp.test` sur une liste pré-compilée (zéro alloc hot-path).
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Nom d'hôte d'une autorité brute (`host[:port]`), sans port — la valeur que
|
|
24
|
+
* rendrait `URL.hostname` pour une autorité déjà canonique, sans en payer le
|
|
25
|
+
* parse (`indexOf` + `slice`, #508). Un littéral IPv6 garde ses crochets
|
|
26
|
+
* (`[::1]:5151` → `[::1]`) : couper au premier `:` le réduirait à `[`.
|
|
27
|
+
*
|
|
28
|
+
* @param host - autorité brute (`Host`), éventuellement absente
|
|
29
|
+
* @returns le nom d'hôte, `""` si l'autorité est absente
|
|
30
|
+
*/
|
|
31
|
+
export declare function hostnameOf(host: string | undefined): string;
|
|
22
32
|
/** Un pattern de domaine : string (exact ou `*`-wildcard) ou `RegExp` (libre). */
|
|
23
33
|
export type DomainPattern = string | RegExp;
|
|
24
34
|
/**
|
|
@@ -30,17 +30,30 @@ export interface ProxyType {
|
|
|
30
30
|
proxyRealIp?: string | undefined;
|
|
31
31
|
proxyVia?: string | undefined;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Même URL (chemin, requête, hôte) sous un autre schéma et un autre port.
|
|
35
|
+
* `port` absent → port par défaut du schéma (omis de l'URL).
|
|
36
|
+
*
|
|
37
|
+
* @param from - URL de la requête.
|
|
38
|
+
* @param scheme - schéma cible.
|
|
39
|
+
* @param port - port du serveur cible, s'il est connu.
|
|
40
|
+
* @returns l'URL absolue de redirection.
|
|
41
|
+
*/
|
|
42
|
+
export declare function switchUrlScheme(from: URL, scheme: "http" | "https", port?: number): string;
|
|
33
43
|
export type HttpRequestType = Http2Request | HttpRequest;
|
|
34
44
|
export type HttpRsponseType = Http2Response | HttpResponse;
|
|
35
45
|
import type { IHttpContext as IHttpContextInterface } from "../../../interfaces/IContext.js";
|
|
46
|
+
import { type ISseStreamOptions, type SseStream } from "./SseStream.js";
|
|
36
47
|
declare class HttpContext extends Context implements IHttpContextInterface {
|
|
37
48
|
proxy: ProxyType | null;
|
|
49
|
+
/** Socket h1 inscrite dans `socketActiveContext` — retirée par `clean()`. */
|
|
50
|
+
private activeSocket;
|
|
38
51
|
isRedirect: boolean;
|
|
39
52
|
sended: boolean;
|
|
40
53
|
request: HttpRequestType;
|
|
41
54
|
response: HttpRsponseType;
|
|
42
55
|
uploadService: uploadService | null;
|
|
43
|
-
constructor(container: Container | Scope, request: http.IncomingMessage | http2.Http2ServerRequest, response: http.ServerResponse | http2.Http2ServerResponse,
|
|
56
|
+
constructor(container: Container | Scope, request: http.IncomingMessage | http2.Http2ServerRequest, response: http.ServerResponse | http2.Http2ServerResponse, serverType: ServerType);
|
|
44
57
|
/**
|
|
45
58
|
* URL d'origine effective, construite À LA DEMANDE (1ʳᵉ lecture, puis
|
|
46
59
|
* mémoïsée). `Origin` invalide ou `null` (RFC 6454) → repli sur l'URL de la
|
|
@@ -71,6 +84,16 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
71
84
|
*/
|
|
72
85
|
handle(): MaybePromise<this>;
|
|
73
86
|
setTimeout(): void;
|
|
87
|
+
/**
|
|
88
|
+
* Nettoie le contexte, et le DÉTACHE de sa socket h1 : la connexion
|
|
89
|
+
* persistante survit à la requête, et l'entrée de `socketActiveContext`
|
|
90
|
+
* retiendrait sinon tout le contexte jusqu'à sa fermeture. Seule l'entrée qui
|
|
91
|
+
* pointe encore sur CE contexte est retirée — une requête suivante sur la
|
|
92
|
+
* même socket a pu la réécrire. Le gestionnaire `timeout` de la socket ne
|
|
93
|
+
* trouve alors rien et prend le chemin « socket inactive → `destroy()` »,
|
|
94
|
+
* celui qu'il prenait déjà pour un contexte nettoyé.
|
|
95
|
+
*/
|
|
96
|
+
clean(): void;
|
|
74
97
|
/**
|
|
75
98
|
* Sérialise la donnée selon son type (JSON, HTML, texte), pose statut et
|
|
76
99
|
* en-têtes, puis l'envoie ({@link send}). Synchrone quand l'envoi l'est (#505).
|
|
@@ -115,6 +138,16 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
115
138
|
* @returns la réponse, ou sa promesse quand l'écriture attend.
|
|
116
139
|
*/
|
|
117
140
|
write(chunk: unknown, encoding?: BufferEncoding, _flush?: boolean): MaybePromise<Http2Response | HttpResponse>;
|
|
141
|
+
/**
|
|
142
|
+
* Répond par un flux d'événements serveur (`text/event-stream`).
|
|
143
|
+
*
|
|
144
|
+
* La requête ne se termine qu'à `close()` du flux, ou au départ du client.
|
|
145
|
+
*
|
|
146
|
+
* @param options - battement de cœur, délai `retry:` initial.
|
|
147
|
+
* @returns le flux, ou sa promesse quand une session est à sauver d'abord.
|
|
148
|
+
* @throws HttpError 500 quand la réponse est déjà partie.
|
|
149
|
+
*/
|
|
150
|
+
openSse(options?: ISseStreamOptions): SseStream | Promise<SseStream>;
|
|
118
151
|
flush(chunk: unknown, encoding: BufferEncoding): MaybePromise<HttpResponse>;
|
|
119
152
|
/**
|
|
120
153
|
* Termine la requête : hook `onClose`, puis fin de la réponse si l'envoi ne
|
|
@@ -126,7 +159,21 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
126
159
|
/** Fin de la réponse, sauf si l'envoi l'a déjà terminée. */
|
|
127
160
|
private endResponse;
|
|
128
161
|
redirect(Url: string, status?: number | string, headers?: Record<string, string | number>): HttpResponse;
|
|
162
|
+
/**
|
|
163
|
+
* Redirige vers la même URL en HTTPS, sur le port du serveur HTTPS lié —
|
|
164
|
+
* ou le port par défaut derrière un proxy, dont le port public est inconnu.
|
|
165
|
+
*
|
|
166
|
+
* @param status - code de redirection (défaut de `redirect`).
|
|
167
|
+
* @param headers - en-têtes ajoutés à la réponse.
|
|
168
|
+
*/
|
|
129
169
|
redirectHttps(status?: number | string, headers?: Record<string, string | number>): HttpResponse;
|
|
170
|
+
/**
|
|
171
|
+
* Redirige vers la même URL en HTTP, sur le port du serveur HTTP lié — ou
|
|
172
|
+
* le port par défaut derrière un proxy.
|
|
173
|
+
*
|
|
174
|
+
* @param status - code de redirection (défaut de `redirect`).
|
|
175
|
+
* @param headers - en-têtes ajoutés à la réponse.
|
|
176
|
+
*/
|
|
130
177
|
redirectHttp(status?: number | string, headers?: Record<string, string | number>): HttpResponse;
|
|
131
178
|
getHostName(): string;
|
|
132
179
|
getRemoteAddress(): string | null;
|
|
@@ -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é
|
|
84
|
-
* Fastify, NestJS)
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
*
|
|
91
|
+
* Lecture seule : une requête sans upload rend un vide partagé et gelé,
|
|
92
|
+
* aucune allocation.
|
|
92
93
|
*/
|
|
93
|
-
get
|
|
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 (`
|
|
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
|
|
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;
|
|
@@ -22,6 +22,9 @@ declare class HttpResponse {
|
|
|
22
22
|
* n'a été choisi (ni négociation, ni `render`, ni controller), émet le défaut
|
|
23
23
|
* `this.contentType` (application/octet-stream) — comportement identique à
|
|
24
24
|
* l'ancienne pose au constructeur, sans le ping-pong set/remove/re-set.
|
|
25
|
+
*
|
|
26
|
+
* Une réponse SANS corps n'annonce aucun type : `Content-Type` décrit le
|
|
27
|
+
* contenu (RFC 9110 §8.3), et il n'y en a pas.
|
|
25
28
|
*/
|
|
26
29
|
protected ensureContentTypeHeader(): void;
|
|
27
30
|
/**
|
|
@@ -47,6 +50,17 @@ declare class HttpResponse {
|
|
|
47
50
|
deleteCookieByName(name: string): boolean;
|
|
48
51
|
setCookies(): void | http.ServerResponse<http.IncomingMessage>;
|
|
49
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;
|
|
50
64
|
setHeader(name: string, value: number | string | readonly string[]): void | http.ServerResponse<http.IncomingMessage>;
|
|
51
65
|
setHeaders(obj: OutgoingHttpHeaders): OutgoingHttpHeaders;
|
|
52
66
|
setContentType(type?: string, encoding?: BufferEncoding): void | http.ServerResponse<http.IncomingMessage>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type HttpContext from "./HttpContext.js";
|
|
2
|
+
/**
|
|
3
|
+
* Le flux d'événements serveur (SSE) d'une réponse HTTP — `text/event-stream`.
|
|
4
|
+
*
|
|
5
|
+
* SSE n'est pas une méthode : c'est un FORMAT de réponse, choisi à l'exécution
|
|
6
|
+
* par l'action. La route reste déclarée `GET` ou `POST`, et le même `POST` peut
|
|
7
|
+
* répondre en JSON ou en flux selon l'en-tête `Accept`.
|
|
8
|
+
*
|
|
9
|
+
* Ce que la classe porte, et qu'une action écrite à la main oubliait :
|
|
10
|
+
* - les en-têtes du flux (`Cache-Control: no-cache, no-transform`,
|
|
11
|
+
* `X-Accel-Buffering: no` pour qu'un nginx ne mette pas le flux en tampon) ;
|
|
12
|
+
* - HTTP/1.1 ET HTTP/2 : en HTTP/1.1 Node découpe le corps en morceaux
|
|
13
|
+
* (RFC 9112 §7.1) ; en HTTP/2 l'écriture passe par le FLUX, et aucun
|
|
14
|
+
* `Transfer-Encoding` n'est posé (interdit, RFC 9113 §8.2.2) ;
|
|
15
|
+
* - la fermeture lue sur la RÉPONSE, jamais sur la requête : en HTTP/2,
|
|
16
|
+
* `request.on("close")` part dès la fin du corps de la requête — c'est le
|
|
17
|
+
* défaut qui avait tué l'ancien flux de la console d'administration ;
|
|
18
|
+
* - le délai d'inactivité de la requête DÉSARMÉ (un flux n'a pas de fin
|
|
19
|
+
* attendue), remplacé par un battement de cœur sur un minuteur `unref` ;
|
|
20
|
+
* - la contre-pression : un client lent freine l'émetteur qui attend `send()` ;
|
|
21
|
+
* un client qui ne lit PLUS est coupé au bout de `stallTimeout` ;
|
|
22
|
+
* - les gardes des connexions longues, les mêmes que pour une socket
|
|
23
|
+
* WebSocket : plafond de connexions par IP (`wsMaxConnectionsPerIp`, un seul
|
|
24
|
+
* budget pour les deux transports) et fermeture du flux quand l'identité qui
|
|
25
|
+
* l'a ouvert est révoquée (`RevocationWatch`, cœur).
|
|
26
|
+
*
|
|
27
|
+
* Norme : WHATWG HTML §9.2 (copie hors ligne
|
|
28
|
+
* `nodefony-framework-dev/references/rfc/specs/whatwg-sse.md`).
|
|
29
|
+
*/
|
|
30
|
+
/** Options d'ouverture d'un flux. */
|
|
31
|
+
export interface ISseStreamOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Période du battement de cœur (commentaire `:`), en ms entre 1 et 2^31-1 —
|
|
34
|
+
* `false` pour l'éteindre. Défaut : 15 000, la valeur que la norme suggère
|
|
35
|
+
* contre les proxys qui coupent une connexion muette (§9.2.7).
|
|
36
|
+
*/
|
|
37
|
+
heartbeat?: number | false | undefined;
|
|
38
|
+
/** Délai de reconnexion imposé au client (champ `retry:`), en ms. */
|
|
39
|
+
retry?: number | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Délai maximal pendant lequel une écriture peut rester bloquée — le client
|
|
42
|
+
* ne lit plus —, en ms entre 1 et 2^31-1 ; au-delà, la connexion est coupée.
|
|
43
|
+
* Remplace le délai d'inactivité de la requête, désarmé pendant un flux.
|
|
44
|
+
* `false` pour l'éteindre. Défaut : 30 000.
|
|
45
|
+
*/
|
|
46
|
+
stallTimeout?: number | false | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Taille maximale d'un événement écrit, en octets (UTF-8). Au-delà, `send()`
|
|
49
|
+
* lève une `RangeError` au lieu d'écrire. Défaut : 1 Mio.
|
|
50
|
+
*/
|
|
51
|
+
maxEventBytes?: number | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Durée de vie maximale du flux, en ms entre 1 et 2^31-1 : il est fermé
|
|
54
|
+
* ensuite, et un `EventSource` se reconnecte — en repassant par le pare-feu.
|
|
55
|
+
* `false` (défaut) : pas de borne.
|
|
56
|
+
*/
|
|
57
|
+
maxDuration?: number | false | undefined;
|
|
58
|
+
}
|
|
59
|
+
/** Ce qu'un événement porte en plus de ses données. */
|
|
60
|
+
export interface ISseSendOptions {
|
|
61
|
+
/** Type de l'événement (champ `event:`) — absent : `message`. */
|
|
62
|
+
event?: string | undefined;
|
|
63
|
+
/** Identifiant (champ `id:`) que le client renverra en `Last-Event-ID`. */
|
|
64
|
+
id?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Battement de cœur par défaut, en ms (§9.2.7 : « every 15 seconds or so »). */
|
|
67
|
+
export declare const SSE_HEARTBEAT_MS = 15000;
|
|
68
|
+
/** Délai de blocage d'écriture par défaut, en ms. */
|
|
69
|
+
export declare const SSE_STALL_TIMEOUT_MS = 30000;
|
|
70
|
+
/** Taille maximale d'un événement par défaut, en octets (1 Mio). */
|
|
71
|
+
export declare const SSE_MAX_EVENT_BYTES = 1048576;
|
|
72
|
+
/**
|
|
73
|
+
* Un flux `text/event-stream` ouvert sur une réponse.
|
|
74
|
+
*
|
|
75
|
+
* Il s'obtient par {@link openSseStream} (ou `this.renderSse()` dans un
|
|
76
|
+
* contrôleur), jamais par `new` : l'ouverture écrit les en-têtes, et un flux
|
|
77
|
+
* sans en-têtes ne se rattrape pas.
|
|
78
|
+
*/
|
|
79
|
+
export declare class SseStream {
|
|
80
|
+
#private;
|
|
81
|
+
/** Le flux est-il fermé — par le serveur (`close()`) ou par le client parti ? */
|
|
82
|
+
get closed(): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Envoie un événement.
|
|
85
|
+
*
|
|
86
|
+
* @param data - texte envoyé tel quel, ou valeur sérialisée en JSON ; un
|
|
87
|
+
* texte de plusieurs lignes part en autant de champs `data:`.
|
|
88
|
+
* @param options - type (`event`) et identifiant (`id`) de l'événement.
|
|
89
|
+
* @returns rien quand l'écriture est acceptée ; une promesse réglée au
|
|
90
|
+
* `drain` quand le client lit moins vite qu'on écrit — l'attendre borne la
|
|
91
|
+
* mémoire du serveur. Sans effet une fois le flux fermé.
|
|
92
|
+
* @throws TypeError quand `event` ou `id` contient une fin de ligne.
|
|
93
|
+
* @throws RangeError quand l'événement dépasse `maxEventBytes`.
|
|
94
|
+
*/
|
|
95
|
+
send(data: unknown, options?: ISseSendOptions): Promise<void> | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Envoie un commentaire — ignoré par le client, utile pour garder une
|
|
98
|
+
* connexion vivante ou marquer le flux à la lecture.
|
|
99
|
+
*
|
|
100
|
+
* @returns comme {@link send}.
|
|
101
|
+
*/
|
|
102
|
+
comment(text?: string): Promise<void> | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Impose au client un délai de reconnexion (champ `retry:`).
|
|
105
|
+
*
|
|
106
|
+
* @param ms - délai en millisecondes, entier positif.
|
|
107
|
+
* @returns comme {@link send}.
|
|
108
|
+
* @throws RangeError quand `ms` n'est pas un entier entre 0 et 2^31-1.
|
|
109
|
+
*/
|
|
110
|
+
retry(ms: number): Promise<void> | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* Appelle `listener` à la fermeture du flux, quelle qu'en soit la cause.
|
|
113
|
+
*
|
|
114
|
+
* L'écouteur est lié au contexte asynchrone de la requête au moment de
|
|
115
|
+
* l'enregistrement : la fermeture par le client part de la SOCKET, hors de
|
|
116
|
+
* la bulle de la requête, et un `RequestContext.get()` y rendrait `undefined`.
|
|
117
|
+
* Un flux déjà fermé appelle l'écouteur au tour suivant.
|
|
118
|
+
*/
|
|
119
|
+
onClose(listener: () => void): void;
|
|
120
|
+
/**
|
|
121
|
+
* Ferme le flux et termine la requête. Idempotent.
|
|
122
|
+
*
|
|
123
|
+
* @returns la fin de la requête (hooks `onClose` du contexte compris).
|
|
124
|
+
*/
|
|
125
|
+
close(): Promise<void> | undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Le client accepte-t-il un flux d'événements ? Lit l'en-tête `Accept`.
|
|
129
|
+
*
|
|
130
|
+
* Un même `POST` répond en JSON ou en flux selon ce que demande le client
|
|
131
|
+
* (porte MCP) : c'est cette fonction qui tranche, et elle seule. Une plage
|
|
132
|
+
* `q=0` est un REFUS explicite (RFC 9110 §12.4.2) ; `*\/*` ne suffit pas — un
|
|
133
|
+
* client qui ne nomme pas le flux ne sait pas le lire.
|
|
134
|
+
*
|
|
135
|
+
* @param accept - valeur de l'en-tête `Accept`, absente ou multiple.
|
|
136
|
+
* @returns `true` quand `text/event-stream` est nommé avec un poids non nul.
|
|
137
|
+
*/
|
|
138
|
+
export declare function acceptsEventStream(accept: string | string[] | undefined): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Ouvre un flux d'événements sur la réponse d'un contexte HTTP.
|
|
141
|
+
*
|
|
142
|
+
* Sauve la session d'abord — ses cookies partent avec les en-têtes, qu'on ne
|
|
143
|
+
* pourra plus toucher —, puis écrit les en-têtes du flux et désarme le délai
|
|
144
|
+
* d'inactivité de la requête.
|
|
145
|
+
*
|
|
146
|
+
* @param context - le contexte de la requête, pas encore répondu.
|
|
147
|
+
* @param options - battement de cœur, délai `retry:` initial.
|
|
148
|
+
* @returns le flux ouvert, ou sa promesse quand une session est à sauver.
|
|
149
|
+
* @throws HttpError 500 quand la réponse est déjà partie.
|
|
150
|
+
* @throws HttpError 429 quand l'IP du client a déjà tous ses flux et sockets
|
|
151
|
+
* (`wsMaxConnectionsPerIp`) — refusé AVANT d'écrire quoi que ce soit.
|
|
152
|
+
* @throws RangeError quand un délai n'est ni `false` ni un entier entre 1 et
|
|
153
|
+
* 2^31-1, ou que `maxEventBytes` n'est pas un entier positif.
|
|
154
|
+
*/
|
|
155
|
+
export declare function openSseStream(context: HttpContext, options?: ISseStreamOptions): SseStream | Promise<SseStream>;
|
|
@@ -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 → `
|
|
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
|
|
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:
|
|
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
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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,20 @@
|
|
|
1
|
+
import http2 from "node:http2";
|
|
2
|
+
import type http from "node:http";
|
|
3
|
+
/**
|
|
4
|
+
* La réponse a-t-elle été TERMINÉE par le serveur ? Règle UNIQUE, pour HTTP/1.1
|
|
5
|
+
* comme pour HTTP/2 — l'écouteur `close` du noyau (client parti → 499) et le
|
|
6
|
+
* timeout par flux de `HttpContext` l'appellent tous deux.
|
|
7
|
+
*
|
|
8
|
+
* Sous HTTP/2, la réponse Nodefony écrit et termine le FLUX
|
|
9
|
+
* (`stream.respond`/`stream.end`), jamais la réponse de compatibilité : son
|
|
10
|
+
* `writableEnded` reste à `false` alors que le client a tout reçu. Mais le
|
|
11
|
+
* flux seul ne suffit pas non plus : un flux ANNULÉ par le pair (RST_STREAM
|
|
12
|
+
* avant la fin) a LUI AUSSI `writableEnded === true`, Node terminant le côté
|
|
13
|
+
* écriture en le détruisant. `aborted` sépare la fin posée par le serveur de la
|
|
14
|
+
* coupure du client — même sémantique que `writableEnded` en HTTP/1.1.
|
|
15
|
+
*
|
|
16
|
+
* @param response - la réponse Node de la requête
|
|
17
|
+
* @returns `true` si le serveur a terminé la réponse (ou son flux HTTP/2) ;
|
|
18
|
+
* `false` s'il ne l'a pas terminée, client parti compris
|
|
19
|
+
*/
|
|
20
|
+
export declare function responseEnded(response: http.ServerResponse | http2.Http2ServerResponse): boolean;
|