@nodefony/http 10.0.0-beta.2 → 10.0.0-beta.3
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.153.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +11 -5
- package/dist/nodefony/config/config.js +44 -4
- package/dist/nodefony/config/defineModuleConfig.js +1 -1
- package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +0 -1
- package/dist/nodefony/service/audit-logger.js +2 -3
- package/dist/nodefony/service/certificates.js +140 -15
- package/dist/nodefony/service/error-renderer.js +1 -1
- package/dist/nodefony/service/http-kernel.js +221 -47
- package/dist/nodefony/service/reverse-proxy.js +336 -0
- package/dist/nodefony/service/servers/server-http.js +3 -3
- package/dist/nodefony/service/servers/server-https.js +3 -3
- package/dist/nodefony/service/servers/server-static.js +4 -7
- package/dist/nodefony/service/servers/server-websocket-secure.js +12 -4
- package/dist/nodefony/service/servers/server-websocket.js +12 -4
- package/dist/nodefony/service/servers/wsHeartbeat.js +31 -15
- package/dist/nodefony/service/sessions/sessions-service.js +30 -9
- package/dist/nodefony/service/upload/upload-service.js +3 -3
- package/dist/nodefony/src/context/Context.js +28 -1
- package/dist/nodefony/src/context/domainMatcher.js +20 -2
- package/dist/nodefony/src/context/forwarded.js +2 -2
- package/dist/nodefony/src/context/http/HttpContext.js +91 -40
- package/dist/nodefony/src/context/http/Request.js +1 -9
- package/dist/nodefony/src/context/http/Response.js +16 -1
- package/dist/nodefony/src/context/http/SseStream.js +350 -0
- package/dist/nodefony/src/context/responseEnded.js +27 -0
- package/dist/nodefony/src/context/websocket/Response.js +9 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +2 -3
- package/dist/nodefony/src/context/websocket/websocketUrl.js +21 -0
- package/dist/nodefony/src/profiler/Profiler.js +6 -10
- package/dist/nodefony/src/proxy/forward.js +476 -0
- package/dist/nodefony/src/proxy/rules.js +182 -0
- package/dist/nodefony/src/servers/upgradeDispatch.js +49 -0
- package/dist/nodefony/src/session/session.js +28 -12
- package/dist/types/index.d.ts +5 -0
- package/dist/types/nodefony/config/config.d.ts +16 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +2 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +16 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
- package/dist/types/nodefony/service/certificates.d.ts +96 -5
- package/dist/types/nodefony/service/error-renderer.d.ts +1 -1
- package/dist/types/nodefony/service/http-kernel.d.ts +111 -5
- package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -0
- 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 +22 -2
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +47 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +3 -0
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
- package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +1 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +156 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +85 -0
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +39 -0
- package/dist/types/nodefony/src/session/session.d.ts +2 -0
- package/docs/cookies.md +13 -13
- package/docs/index.md +5 -1
- package/docs/observabilite.md +15 -15
- package/docs/rate-limit.md +30 -30
- package/docs/reverse-proxy.md +522 -0
- package/docs/servers.md +94 -60
- package/docs/session.md +61 -61
- package/docs/sse.md +283 -0
- package/docs/upload.md +23 -23
- package/package.json +8 -8
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import { Service, Module } from "nodefony";
|
|
4
|
+
import type { IProxyMount, IProxyMountOptions } from "../interfaces/IReverseProxy.js";
|
|
5
|
+
import { type ProxiedRequest, type ProxiedResponse } from "../src/proxy/forward.js";
|
|
6
|
+
/** Un montage tel que le service le garde : la dernière cible analysée y est mise en cache. */
|
|
7
|
+
interface IMountRecord extends IProxyMount {
|
|
8
|
+
/** D'où vient le montage : la configuration de l'application, ou un module. */
|
|
9
|
+
readonly source: "config" | "module";
|
|
10
|
+
/** Dernière origine rendue par `target`, et son analyse. */
|
|
11
|
+
lastOrigin: string | undefined;
|
|
12
|
+
lastUrl: URL | undefined;
|
|
13
|
+
/** Pool de connexions vers l'amont, créé au premier relais (scheme de la cible). */
|
|
14
|
+
agent: http.Agent | null;
|
|
15
|
+
/** Tunnels WebSocket ouverts vers l'amont — bornés par `maxSockets`, hors pool. */
|
|
16
|
+
tunnels: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Proxy inverse de Nodefony : relaie un préfixe d'URL vers un autre serveur
|
|
20
|
+
* HTTP — requêtes ET, sur demande, upgrades WebSocket — sur la MÊME origine
|
|
21
|
+
* que la page. Le client ne voit qu'un serveur, un certificat ; l'amont peut
|
|
22
|
+
* rester sur la boucle locale.
|
|
23
|
+
*
|
|
24
|
+
* Deux sources de montages, mêmes règles (`src/proxy/rules.ts`) : la section
|
|
25
|
+
* `proxy.mounts` de la configuration, lue à la construction, et
|
|
26
|
+
* {@link ReverseProxy.mount}, appelé par un module (`@nodefony/frontend` y
|
|
27
|
+
* relaie `/_vite/<famille>/` vers ses serveurs Vite de développement). Un
|
|
28
|
+
* module se résout ce service par NOM (`container.get("reverse-proxy")`) :
|
|
29
|
+
* il n'a pas à importer `@nodefony/http`.
|
|
30
|
+
*
|
|
31
|
+
* Consulté AVANT le routage : une route attrape-tout de l'application ne peut
|
|
32
|
+
* pas avaler un préfixe relayé. Sans montage, le pipeline ne paie qu'une
|
|
33
|
+
* lecture de champ (`mounts === null`) — c'est le cas de toute application
|
|
34
|
+
* qui ne s'en sert pas.
|
|
35
|
+
*
|
|
36
|
+
* Règles communes à tout montage : en-têtes de connexion jamais transmis
|
|
37
|
+
* (RFC 9110 §7.6.1), `X-Forwarded-For/-Proto/-Host` et `Via` posés par le
|
|
38
|
+
* proxy, Host hors `trustedHosts` jamais relayé (même barrière, même 421 que
|
|
39
|
+
* toute requête), amont injoignable → 502, amont muet → 504, boucle → 508.
|
|
40
|
+
*/
|
|
41
|
+
declare class ReverseProxy extends Service {
|
|
42
|
+
#private;
|
|
43
|
+
module: Module;
|
|
44
|
+
/**
|
|
45
|
+
* Montages actifs, du préfixe le PLUS LONG au plus court : le premier qui
|
|
46
|
+
* couvre une URL est le plus spécifique. `null` tant qu'il n'y en a aucun.
|
|
47
|
+
*/
|
|
48
|
+
mounts: IMountRecord[] | null;
|
|
49
|
+
/** Délai d'inactivité appliqué aux montages qui n'en fixent pas (`proxy.timeoutMs`). */
|
|
50
|
+
readonly defaultTimeoutMs: number;
|
|
51
|
+
/** Délai d'établissement de la connexion amont (`proxy.connectTimeoutMs`). */
|
|
52
|
+
readonly connectTimeoutMs: number;
|
|
53
|
+
/** Connexions simultanées par amont (`proxy.maxSockets`) : pool ET tunnels WebSocket. */
|
|
54
|
+
readonly maxSockets: number;
|
|
55
|
+
/**
|
|
56
|
+
* Nom de CE processus dans `Via` (RFC 9110 §7.6.3) : tiré au hasard au
|
|
57
|
+
* démarrage, il ne dit rien de la machine et distingue deux instances —
|
|
58
|
+
* un message qui le porte déjà est une boucle (508).
|
|
59
|
+
*/
|
|
60
|
+
readonly pseudonym: string;
|
|
61
|
+
constructor(module: Module);
|
|
62
|
+
/**
|
|
63
|
+
* Relaie un préfixe d'URL vers un amont. Idempotent pour un même module :
|
|
64
|
+
* un même préfixe est remplacé. Un module qui recouvre un préfixe de la
|
|
65
|
+
* CONFIGURATION le remplace aussi, mais le dit — l'auteur de l'application
|
|
66
|
+
* doit pouvoir lire pourquoi son montage ne sert plus.
|
|
67
|
+
*
|
|
68
|
+
* @param prefix - préfixe d'URL (normalisé : `/` en tête et en fin)
|
|
69
|
+
* @param options - cible et réglages du montage
|
|
70
|
+
* @throws Error qui nomme chaque refus (préfixe `/` ou réservé, cible qui
|
|
71
|
+
* n'est pas une origine http(s) nue, `secure: false` hors boucle locale…)
|
|
72
|
+
*/
|
|
73
|
+
mount(prefix: string, options: IProxyMountOptions): void;
|
|
74
|
+
/**
|
|
75
|
+
* Retire un montage. Le dernier retiré rend `mounts` à `null` : le pipeline
|
|
76
|
+
* cesse alors de le consulter.
|
|
77
|
+
*
|
|
78
|
+
* @param prefix - préfixe tel que monté (normalisé de la même façon)
|
|
79
|
+
*/
|
|
80
|
+
unmount(prefix: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Ferme tous les pools (arrêt du noyau). Les montages restent déclarés ; un
|
|
83
|
+
* relais encore en vol part ensuite sur une connexion à usage unique.
|
|
84
|
+
*/
|
|
85
|
+
closeAll(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Montage le plus spécifique qui couvre une URL, s'il y en a un.
|
|
88
|
+
*
|
|
89
|
+
* @param url - cible brute de la requête (chemin + requête)
|
|
90
|
+
* @returns le montage au préfixe le plus long qui couvre l'URL
|
|
91
|
+
*/
|
|
92
|
+
match(url: string | undefined): IProxyMount | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Origine courante d'un montage, analysée — mise en cache tant que la cible
|
|
95
|
+
* rend la même chaîne.
|
|
96
|
+
*
|
|
97
|
+
* @returns l'URL de l'amont, ou `undefined` si rien n'est relayable
|
|
98
|
+
*/
|
|
99
|
+
private targetOf;
|
|
100
|
+
/**
|
|
101
|
+
* Pool de connexions d'un montage, créé au premier relais et recréé si la
|
|
102
|
+
* cible change de scheme.
|
|
103
|
+
*
|
|
104
|
+
* Un `Agent` DÉDIÉ, jamais l'agent global : sous `NODE_USE_ENV_PROXY`, cas
|
|
105
|
+
* courant d'un poste d'entreprise, l'agent global envoie tout vers
|
|
106
|
+
* `HTTP_PROXY` — un amont sur la boucle locale serait demandé au proxy de
|
|
107
|
+
* l'entreprise. Un agent construit ici n'a pas de `proxyEnv` : il va droit
|
|
108
|
+
* à la cible. Keep-alive borné (`maxSockets`), `lifo` : le socket le plus
|
|
109
|
+
* récent, le moins susceptible d'avoir été fermé par l'amont.
|
|
110
|
+
*/
|
|
111
|
+
private agentFor;
|
|
112
|
+
/** Réglages de relais d'un montage. */
|
|
113
|
+
private relayFor;
|
|
114
|
+
/** Règles du noyau HTTP (résolues par nom), `undefined` hors serveur. */
|
|
115
|
+
private rules;
|
|
116
|
+
/** Ce que le proxy annonce à l'amont sur le client. */
|
|
117
|
+
private fromOf;
|
|
118
|
+
/**
|
|
119
|
+
* Relaie une requête HTTP si un montage la couvre — méthode comprise. Cible
|
|
120
|
+
* pas encore connue → `503` + `Retry-After`, sans aller jusqu'au routage.
|
|
121
|
+
*
|
|
122
|
+
* L'appelant a déjà tranché la barrière d'hôte : c'est le pipeline HTTP, qui
|
|
123
|
+
* la calcule pour toute requête (`Context.validDomain`). Un chemin ambigu
|
|
124
|
+
* sous un préfixe monté rend 400, sans contacter l'amont.
|
|
125
|
+
*
|
|
126
|
+
* ⚠️ Un préfixe monté est servi AVANT le pare-feu, la CSRF et les en-têtes
|
|
127
|
+
* de sécurité applicatifs : c'est une surface publique pour qui atteint
|
|
128
|
+
* Nodefony. L'amont porte sa propre politique d'accès.
|
|
129
|
+
*
|
|
130
|
+
* @param req - requête du client
|
|
131
|
+
* @param res - réponse au client
|
|
132
|
+
* @param scheme - scheme par lequel le client est arrivé
|
|
133
|
+
* @returns la promesse de fin d'échange, ou `undefined` si la requête n'est
|
|
134
|
+
* pas relayée (hors préfixe ou méthode non relayée : elle suit alors son
|
|
135
|
+
* chemin normal)
|
|
136
|
+
*/
|
|
137
|
+
forward(req: ProxiedRequest, res: ProxiedResponse, scheme: string): Promise<void> | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Relaie un upgrade WebSocket si un montage `websocket` couvre son URL.
|
|
140
|
+
* Appelé par le répartiteur d'`upgrade` des serveurs WebSocket, AVANT le
|
|
141
|
+
* serveur WebSocket de Nodefony.
|
|
142
|
+
*
|
|
143
|
+
* Contrôles, dans l'ordre, chacun avec la réponse que rendrait le serveur
|
|
144
|
+
* WebSocket de Nodefony : cible pas encore connue → 503 ; `Host` hors `trustedHosts` (si `domainCheck`) →
|
|
145
|
+
* 421 ; chemin ambigu ou handshake non conforme (RFC 6455 §4.2.1 — seul
|
|
146
|
+
* `Upgrade: websocket` est relayé, jamais un tunnel `h2c`) → 400 ;
|
|
147
|
+
* `Origin` refusée par la MÊME règle que le serveur WebSocket (anti-CSWSH,
|
|
148
|
+
* RFC 6455 §10.2) → 403 ; `maxSockets` tunnels déjà ouverts vers l'amont →
|
|
149
|
+
* 503 ; quotas par IP du noyau (débit de handshakes, connexions
|
|
150
|
+
* simultanées) → 429. Le socket est alors consommé.
|
|
151
|
+
*
|
|
152
|
+
* @returns `true` si le proxy a pris le socket en charge
|
|
153
|
+
*/
|
|
154
|
+
handleUpgrade(req: http.IncomingMessage, socket: Duplex, head: Buffer): boolean;
|
|
155
|
+
}
|
|
156
|
+
export default ReverseProxy;
|
|
@@ -7,6 +7,7 @@ import { AddressInfo } from "node:net";
|
|
|
7
7
|
import type { IncomingMessage } from "node:http";
|
|
8
8
|
import httpsServers from "./server-https.js";
|
|
9
9
|
declare class WebsocketSecure extends Service {
|
|
10
|
+
#private;
|
|
10
11
|
private readonly httpKernel;
|
|
11
12
|
options: IHttpConfig["websocketSecure"] & DefaultOptionsService;
|
|
12
13
|
module: Module;
|
|
@@ -7,6 +7,7 @@ import { AddressInfo } from "node:net";
|
|
|
7
7
|
import type { IncomingMessage } from "node:http";
|
|
8
8
|
import httpServer from "./server-http.js";
|
|
9
9
|
declare class Websocket extends Service {
|
|
10
|
+
#private;
|
|
10
11
|
private readonly httpKernel;
|
|
11
12
|
options: IHttpConfig["websocket"] & DefaultOptionsService;
|
|
12
13
|
module: Module;
|
|
@@ -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";
|
|
@@ -43,7 +43,6 @@ declare class Context extends Service implements IContextInterface {
|
|
|
43
43
|
secure: boolean;
|
|
44
44
|
security?: ISecurityZone | null | undefined;
|
|
45
45
|
cleaned: boolean;
|
|
46
|
-
isControlledAccess: boolean;
|
|
47
46
|
validDomain: boolean;
|
|
48
47
|
finished: boolean;
|
|
49
48
|
pushAllowed: boolean;
|
|
@@ -68,6 +67,14 @@ declare class Context extends Service implements IContextInterface {
|
|
|
68
67
|
session: Session | null | undefined;
|
|
69
68
|
cookieSession: Cookie | null | undefined;
|
|
70
69
|
user: unknown;
|
|
70
|
+
/**
|
|
71
|
+
* Identifiant de l'appelant AUTHENTIFIÉ, posé par le pare-feu ; `null` hors
|
|
72
|
+
* zone ou pour un anonyme. Lisible au démontage, hors de la bulle ALS : c'est
|
|
73
|
+
* ce que lisent le journal d'audit et le profileur. Distinct de `user`, qui
|
|
74
|
+
* lie la session à son principal de SESSION — y écrire l'identité d'un jeton
|
|
75
|
+
* rattacherait la session d'un autre compte.
|
|
76
|
+
*/
|
|
77
|
+
userIdentifier: string | null;
|
|
71
78
|
waitAsync: boolean;
|
|
72
79
|
isJson: boolean;
|
|
73
80
|
protected _isHtml: boolean | null;
|
|
@@ -116,6 +123,7 @@ declare class Context extends Service implements IContextInterface {
|
|
|
116
123
|
private _phaseIndex;
|
|
117
124
|
private _afterResponseFns;
|
|
118
125
|
private _afterResponseFired;
|
|
126
|
+
private requestStore;
|
|
119
127
|
private _abortController;
|
|
120
128
|
metaData: Data;
|
|
121
129
|
scheme: SchemeType;
|
|
@@ -160,6 +168,18 @@ declare class Context extends Service implements IContextInterface {
|
|
|
160
168
|
get signal(): AbortSignal;
|
|
161
169
|
_abortIfPending(reason?: string): void;
|
|
162
170
|
log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
|
|
171
|
+
/**
|
|
172
|
+
* Confie au contexte le magasin `RequestContext` de son unité de travail
|
|
173
|
+
* (requête HTTP, connexion WebSocket), pour qu'il le VIDE à son nettoyage.
|
|
174
|
+
*
|
|
175
|
+
* Un minuteur ou une promesse armé pendant l'unité de travail capture ce
|
|
176
|
+
* magasin et le garde au-delà de la réponse — avec le contexte, l'identité
|
|
177
|
+
* et le jeton du pare-feu. Le vidage ({@link RequestContext.release}) n'y
|
|
178
|
+
* laisse que la corrélation et le scope refermé.
|
|
179
|
+
*
|
|
180
|
+
* @param store - le magasin passé à `RequestContext.run`
|
|
181
|
+
*/
|
|
182
|
+
holdRequestStore(store: RequestContextPayload): void;
|
|
163
183
|
clean(): void;
|
|
164
184
|
/**
|
|
165
185
|
* Log d'un event du cycle de vie — gate boot-time (V2.1) : en production le
|
|
@@ -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,11 +30,24 @@ 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;
|
|
@@ -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;
|
|
@@ -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
|
/**
|
|
@@ -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>;
|
|
@@ -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;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL d'une requête d'upgrade WebSocket, telle que le pipeline la lit.
|
|
3
|
+
*
|
|
4
|
+
* Seule construction : le contexte WebSocket en tire son chemin et sa requête,
|
|
5
|
+
* et le contrôle d'avant `101` en tire le chemin qu'il confronte aux routes.
|
|
6
|
+
* Deux constructions divergeraient — `//a/b` lu comme un chemin d'un côté, comme
|
|
7
|
+
* un hôte de l'autre — et une route acceptée par l'une serait refusée par
|
|
8
|
+
* l'autre.
|
|
9
|
+
*
|
|
10
|
+
* @param scheme - `ws` ou `wss`
|
|
11
|
+
* @param host - en-tête `Host` de la requête (`localhost` s'il manque)
|
|
12
|
+
* @param rawUrl - cible brute de la requête (`/` si elle manque)
|
|
13
|
+
* @returns l'URL analysée
|
|
14
|
+
* @throws TypeError si l'hôte ou la cible ne forment pas une URL
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseWebsocketUrl(scheme: string, host: string | undefined, rawUrl: string | undefined): URL;
|
|
@@ -116,6 +116,7 @@ export interface ProfileSummary {
|
|
|
116
116
|
/** Forme structurelle minimale lue sur un `Context` (lecture défensive). */
|
|
117
117
|
interface ProfilableContext {
|
|
118
118
|
requestId?: string | undefined;
|
|
119
|
+
userIdentifier?: string | null | undefined;
|
|
119
120
|
type?: string | undefined;
|
|
120
121
|
scheme?: string | undefined;
|
|
121
122
|
method?: string | null | undefined;
|