@nodefony/http 10.0.0-beta.5 → 10.0.0-beta.6
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.157.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +29 -26
- package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
- package/dist/nodefony/config/config.js +4 -2
- package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
- package/dist/nodefony/service/error-renderer.js +6 -4
- package/dist/nodefony/service/http-kernel.js +112 -34
- package/dist/nodefony/service/pretty-request-logger.js +1 -1
- package/dist/nodefony/service/request-logger.js +1 -1
- package/dist/nodefony/service/reverse-proxy.js +7 -2
- package/dist/nodefony/service/servers/clientError.js +28 -4
- package/dist/nodefony/service/servers/server-h2c.js +5 -5
- package/dist/nodefony/service/servers/server-http.js +5 -3
- package/dist/nodefony/service/servers/server-https.js +12 -5
- package/dist/nodefony/service/servers/server-static.js +1 -6
- package/dist/nodefony/service/servers/server-websocket-secure.js +6 -10
- package/dist/nodefony/service/servers/server-websocket.js +6 -10
- package/dist/nodefony/service/servers/serverShutdown.js +63 -4
- package/dist/nodefony/service/sessions/sessions-service.js +3 -3
- package/dist/nodefony/service/upload/upload-service.js +2 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
- package/dist/nodefony/src/context/http/ByteStream.js +359 -0
- package/dist/nodefony/src/context/http/HttpContext.js +55 -26
- package/dist/nodefony/src/context/http/Request.js +15 -6
- package/dist/nodefony/src/context/http/Response.js +354 -35
- package/dist/nodefony/src/context/http/SseStream.js +81 -146
- package/dist/nodefony/src/context/http/StreamLifecycle.js +273 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
- package/dist/nodefony/src/context/http2/Request.js +1 -1
- package/dist/nodefony/src/context/http2/Response.js +8 -4
- package/dist/nodefony/src/context/transport.js +20 -1
- package/dist/nodefony/src/context/websocket/Response.js +4 -2
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +71 -14
- 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 +54 -13
- package/dist/nodefony/src/profiler/Profiler.js +14 -1
- package/dist/nodefony/src/proxy/forward.js +109 -9
- package/dist/nodefony/src/proxy/generateProxyConfig.js +6 -2
- package/dist/nodefony/src/proxy/rules.js +29 -14
- package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
- package/dist/types/index.d.ts +6 -0
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
- package/dist/types/nodefony/service/error-renderer.d.ts +5 -4
- package/dist/types/nodefony/service/http-kernel.d.ts +15 -2
- package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +5 -3
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +45 -3
- package/dist/types/nodefony/src/context/http/Request.d.ts +8 -1
- package/dist/types/nodefony/src/context/http/Response.d.ts +134 -8
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +32 -7
- package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +53 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +5 -3
- package/dist/types/nodefony/src/context/transport.d.ts +17 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +40 -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/profiler/Profiler.d.ts +11 -1
- package/dist/types/nodefony/src/proxy/forward.d.ts +30 -2
- package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
- package/docs/byte-stream.md +371 -0
- package/docs/cookies.md +13 -3
- package/docs/index.md +8 -0
- package/docs/reverse-proxy.md +15 -3
- package/docs/servers.md +26 -15
- package/docs/session.md +3 -3
- package/docs/sse.md +27 -20
- package/package.json +16 -16
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Duplex } from "node:stream";
|
|
2
2
|
/**
|
|
3
|
-
* Gère l'event `clientError` d'un `http.Server` / `https.Server
|
|
3
|
+
* Gère l'event `clientError` d'un `http.Server` / `https.Server`, et des connexions
|
|
4
|
+
* HTTP/1.1 d'un serveur HTTP/2 à `allowHTTP1`.
|
|
4
5
|
*
|
|
5
6
|
* ⚠️ Doc Node (`http.Server` event `'clientError'`) : dès qu'un listener
|
|
6
7
|
* `clientError` est attaché, Node **cesse de fermer le socket automatiquement**
|
|
@@ -11,7 +12,7 @@ import type { Duplex } from "node:stream";
|
|
|
11
12
|
* @param error - erreur émise (codes `llhttp` : `HPE_*`, ou `ECONNRESET`…).
|
|
12
13
|
* @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
|
|
13
14
|
*/
|
|
14
|
-
export declare function handleClientError(error:
|
|
15
|
+
export declare function handleClientError(error: Error, socket: Duplex): void;
|
|
15
16
|
/**
|
|
16
17
|
* Gère l'event `sessionError` d'un serveur HTTP/2 (h2c ou TLS) : un pair PARTI
|
|
17
18
|
* se tait, toute autre erreur est rapportée.
|
|
@@ -16,9 +16,11 @@ export interface IDrainTerminator {
|
|
|
16
16
|
* Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
|
|
17
17
|
*
|
|
18
18
|
* Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
|
|
19
|
-
* - **HTTP/2** : chaque session reçoit un GOAWAY
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* - **HTTP/2** : chaque session reçoit un GOAWAY en deux temps — d'abord à
|
|
20
|
+
* `2^31-1` ({@link announceShutdown}), puis, un aller-retour plus tard, le
|
|
21
|
+
* définitif (`session.close()`). Les flux en vol se terminent, les nouveaux
|
|
22
|
+
* sont refusés, et le client sait qu'il doit rouvrir ailleurs au lieu de
|
|
23
|
+
* constater une coupure ;
|
|
22
24
|
* - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
|
|
23
25
|
* inactive est fermée tout de suite, une requête qui arrive encore sur une
|
|
24
26
|
* socket gardée ouverte reçoit aussi `Connection: close`.
|
|
@@ -67,8 +67,8 @@ export interface IPrebuiltUiOptions {
|
|
|
67
67
|
* requête. Le SPA fallback reste au controller du module (routes littérales,
|
|
68
68
|
* cf. StudioController).
|
|
69
69
|
*
|
|
70
|
-
* Perf : l'index est lu UNE fois (lazy, caché)
|
|
71
|
-
*
|
|
70
|
+
* Perf : l'index est lu UNE fois (lazy, caché) et découpé UNE fois autour des
|
|
71
|
+
* emplacements du nonce ; un rendu ne coûte qu'un `join`.
|
|
72
72
|
*/
|
|
73
73
|
export declare class PrebuiltUi {
|
|
74
74
|
readonly publicPath: string;
|
|
@@ -76,6 +76,8 @@ export declare class PrebuiltUi {
|
|
|
76
76
|
private readonly indexPath;
|
|
77
77
|
/** Template HTML caché — `null` tant que non lu (lazy). */
|
|
78
78
|
private template;
|
|
79
|
+
/** Index découpé autour des emplacements du nonce — `null` tant que non calculé. */
|
|
80
|
+
private nonceParts;
|
|
79
81
|
constructor(opts: IPrebuiltUiOptions);
|
|
80
82
|
/**
|
|
81
83
|
* Monte `distDir` sous `publicPath` auprès de `server-static` (résolu par
|
|
@@ -90,8 +92,11 @@ export declare class PrebuiltUi {
|
|
|
90
92
|
* Document HTML d'entrée de l'UI (index pré-buildé par Vite).
|
|
91
93
|
*
|
|
92
94
|
* @param nonce nonce CSP de la requête (`Context.cspNonce`) — injecté sur
|
|
93
|
-
* chaque `<script
|
|
94
|
-
*
|
|
95
|
+
* chaque `<script>`, `src` compris : sous `'strict-dynamic'`
|
|
96
|
+
* (ADR-0024 §C1), `'self'` est ignoré et seul le nonce autorise
|
|
97
|
+
* une balise ; ce qu'elle importe ensuite hérite de la confiance.
|
|
98
|
+
* Annoncé aussi par `<meta property="csp-nonce">` en tête de
|
|
99
|
+
* `<head>` (ADR-0024 §C2) : les `<style>` créés à l'exécution.
|
|
95
100
|
* @returns le HTML, ou un commentaire HTML fail-loud si l'index est absent
|
|
96
101
|
*/
|
|
97
102
|
renderIndex(nonce?: string): string;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import type HttpContext from "./HttpContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* Réponse en FLUX D'OCTETS — la sortie d'un processus, le relais d'un autre
|
|
6
|
+
* service, une archive construite à la volée : toute source d'octets qui n'est
|
|
7
|
+
* pas un fichier du disque.
|
|
8
|
+
*
|
|
9
|
+
* Le cycle de vie (prise de la réponse, session, fermeture lue sur la réponse,
|
|
10
|
+
* plafond par IP, révocation, coupure d'un client qui ne lit plus) est celui de
|
|
11
|
+
* la brique commune au SSE (`StreamLifecycle`). Ce fichier porte ce qui est
|
|
12
|
+
* propre aux octets :
|
|
13
|
+
* - les en-têtes au PREMIER morceau : une erreur de la source avant lui passe
|
|
14
|
+
* par le chemin d'erreur normal (`HttpKernel.onError`), donc un vrai 5xx ;
|
|
15
|
+
* - l'arrêt de la source par `destroy()`, jamais `unpipe()` — qui la laisse en
|
|
16
|
+
* pause, et un producteur bloqué en écriture ne meurt plus ;
|
|
17
|
+
* - un chien de garde sur une source MUETTE (`idleTimeout`) : un seul minuteur
|
|
18
|
+
* périodique par flux, qui compare le compteur d'octets — aucun minuteur ni
|
|
19
|
+
* appel d'horloge par morceau (mesuré : l'horodatage par morceau coûtait
|
|
20
|
+
* 2 à 6 % de CPU sur des morceaux de 4 Kio) ;
|
|
21
|
+
* - la coupure (jamais une fin propre) quand la source échoue après le premier
|
|
22
|
+
* octet : le client doit savoir que le corps est tronqué.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Raison de la fin d'un flux d'octets.
|
|
26
|
+
*
|
|
27
|
+
* - `end` : la source est finie, ou le serveur a fermé le flux (durée de vie,
|
|
28
|
+
* révocation de l'identité) ;
|
|
29
|
+
* - `client` : le client est parti ;
|
|
30
|
+
* - `error` : la source a échoué ;
|
|
31
|
+
* - `timeout` : la source est restée muette plus de `idleTimeout` ;
|
|
32
|
+
* - `stall` : le client ne lisait plus depuis `stallTimeout`.
|
|
33
|
+
*
|
|
34
|
+
* L'union PEUT S'AGRANDIR : un `switch` exhaustif garde une branche `default`.
|
|
35
|
+
*/
|
|
36
|
+
export type ByteStreamEndReason = "end" | "client" | "error" | "timeout" | "stall";
|
|
37
|
+
/** Ce que rend la fin d'un flux d'octets. */
|
|
38
|
+
export interface IByteStreamResult {
|
|
39
|
+
/** Pourquoi le flux s'est terminé. */
|
|
40
|
+
reason: ByteStreamEndReason;
|
|
41
|
+
/** Octets écrits sur la réponse. */
|
|
42
|
+
bytes: number;
|
|
43
|
+
}
|
|
44
|
+
/** Options d'une réponse en flux d'octets. */
|
|
45
|
+
export interface IByteStreamOptions {
|
|
46
|
+
/** Statut de la réponse, entre 200 et 599. Défaut : 200. */
|
|
47
|
+
status?: number | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* En-têtes de la réponse, écrits au premier morceau. `Content-Length` est
|
|
50
|
+
* repris s'il est fourni ; sinon HTTP/1.1 envoie par morceaux. Défauts posés
|
|
51
|
+
* seulement s'ils manquent — ni ici, ni déjà sur la réponse :
|
|
52
|
+
* `Content-Type: application/octet-stream`, et pour un flux `live`
|
|
53
|
+
* `Cache-Control: no-cache, no-transform` et `X-Accel-Buffering: no`.
|
|
54
|
+
*/
|
|
55
|
+
headers?: http.OutgoingHttpHeaders | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Flux VIVANT (défaut `true`) : la sortie d'un processus, un relais — un
|
|
58
|
+
* proxy inverse ne doit pas le mettre en tampon, ni un cache le garder, d'où
|
|
59
|
+
* `Cache-Control: no-cache, no-transform` et `X-Accel-Buffering: no` par
|
|
60
|
+
* défaut. `false` pour une source FINIE et rejouable (un fichier, une
|
|
61
|
+
* archive) : aucun de ces deux en-têtes n'est posé.
|
|
62
|
+
*/
|
|
63
|
+
live?: boolean | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Délai maximal sans un octet de la source, en ms ; `0` le désarme. Au-delà :
|
|
66
|
+
* 504 si rien n'est encore parti, coupure sinon. Le silence est constaté
|
|
67
|
+
* par un tour de garde tous les quarts de délai : la coupure tombe entre
|
|
68
|
+
* `idleTimeout` et 1,25 × `idleTimeout`. Défaut : le délai de réponse
|
|
69
|
+
* du serveur (`responseTimeout`, 30 s). À garder sous le délai de lecture du
|
|
70
|
+
* proxy inverse (`proxy_read_timeout`, 60 s chez nginx).
|
|
71
|
+
*/
|
|
72
|
+
idleTimeout?: number | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Délai maximal pendant lequel une écriture peut rester bloquée — le client
|
|
75
|
+
* ne lit plus —, en ms ; au-delà, la connexion est coupée. `false` pour
|
|
76
|
+
* l'éteindre. Défaut : 30 000, celui du SSE.
|
|
77
|
+
*/
|
|
78
|
+
stallTimeout?: number | false | undefined;
|
|
79
|
+
/** Durée de vie maximale du flux, en ms. `false` (défaut) : pas de borne. */
|
|
80
|
+
maxDuration?: number | false | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Appelé une fois à la fin du flux, avec sa raison — AVANT la fin de la
|
|
83
|
+
* requête et la résolution de la promesse. Lié au contexte asynchrone de la
|
|
84
|
+
* requête.
|
|
85
|
+
*/
|
|
86
|
+
onClose?: ((reason: ByteStreamEndReason) => void) | undefined;
|
|
87
|
+
}
|
|
88
|
+
/** Une source d'octets : flux Node, ou flux web (`fetch().body`). */
|
|
89
|
+
export type ByteStreamSource = Readable | ReadableStream<Uint8Array>;
|
|
90
|
+
/**
|
|
91
|
+
* Répond par un flux d'octets sur la réponse d'un contexte HTTP.
|
|
92
|
+
*
|
|
93
|
+
* Prend la réponse à l'appel (le noyau ne peut plus répondre par-dessus) ;
|
|
94
|
+
* les en-têtes partent au premier morceau de la source. La promesse se règle à
|
|
95
|
+
* la fin du flux, quelle qu'en soit la raison, et ne rejette JAMAIS.
|
|
96
|
+
*
|
|
97
|
+
* @param context - le contexte de la requête, pas encore répondu.
|
|
98
|
+
* @param source - flux Node, ou flux web (`fetch().body`) ; jamais en mode objet.
|
|
99
|
+
* @param options - statut, en-têtes, délais, écouteur de fin.
|
|
100
|
+
* @returns la raison de la fin et le nombre d'octets envoyés.
|
|
101
|
+
* @throws TypeError quand la source est en mode objet.
|
|
102
|
+
* @throws RangeError quand une option est hors bornes.
|
|
103
|
+
* @throws HttpError 500 quand la réponse est déjà partie ; 429 quand l'IP a
|
|
104
|
+
* déjà tous ses flux et sockets (`wsMaxConnectionsPerIp`).
|
|
105
|
+
*
|
|
106
|
+
* La source appartient au flux dès l'appel : quand l'appel lève, elle est
|
|
107
|
+
* DÉTRUITE (un flux web est annulé) avant que l'erreur ne remonte.
|
|
108
|
+
*/
|
|
109
|
+
export declare function openByteStream(context: HttpContext, source: ByteStreamSource, options?: IByteStreamOptions): Promise<IByteStreamResult>;
|
|
@@ -2,7 +2,7 @@ import { ServerType, SchemeType } from "../../../service/http-kernel.js";
|
|
|
2
2
|
import HttpError from "../../errors/httpError.js";
|
|
3
3
|
import Context, { HTTPMethod } from "../Context.js";
|
|
4
4
|
import { Container, Scope, nodefonyError } from "nodefony";
|
|
5
|
-
import type { MaybePromise } from "nodefony";
|
|
5
|
+
import type { DefaultEventsMap, EventsMap, MaybePromise } from "nodefony";
|
|
6
6
|
import HttpRequest from "./Request.js";
|
|
7
7
|
import HttpResponse from "./Response.js";
|
|
8
8
|
import Http2Request from "../http2/Request.js";
|
|
@@ -43,8 +43,30 @@ export interface ProxyType {
|
|
|
43
43
|
export declare function switchUrlScheme(from: URL, scheme: "http" | "https", port?: number): string;
|
|
44
44
|
export type HttpRequestType = Http2Request | HttpRequest;
|
|
45
45
|
export type HttpRsponseType = Http2Response | HttpResponse;
|
|
46
|
+
/**
|
|
47
|
+
* Route le délai d'inactivité d'une socket HTTP/1.1 vers la requête qu'elle
|
|
48
|
+
* porte : 408/504 si une requête est en cours, fermeture si la connexion
|
|
49
|
+
* persistante est inactive. Écouteur `timeout` du SERVEUR — un seul pour
|
|
50
|
+
* toutes ses connexions —, que Node appelle avec la socket en argument
|
|
51
|
+
* (`socketOnTimeout` → `server.emit("timeout", socket)`).
|
|
52
|
+
*
|
|
53
|
+
* Pourquoi au serveur et pas sur la socket : Node y pose déjà son propre
|
|
54
|
+
* écouteur `timeout`, et un second faisait créer à `EventEmitter` un tableau
|
|
55
|
+
* d'écouteurs par CONNEXION. Ce site d'allocation, partagé avec tout le
|
|
56
|
+
* processus et toujours survivant (une connexion vit longtemps), finissait
|
|
57
|
+
* par être basculé dans le vieux tas par V8 (pretenuring) — dans un démarrage
|
|
58
|
+
* sur trois environ, mesuré sous charge. Le graphe de chaque requête y
|
|
59
|
+
* restait alors accroché jusqu'à la collecte complète suivante : +45 % de CPU
|
|
60
|
+
* par requête, 14 à 19 collectes complètes au lieu de 3. Un écouteur par
|
|
61
|
+
* serveur supprime aussi une fermeture et une entrée de registre par connexion.
|
|
62
|
+
*
|
|
63
|
+
* @param socket - la socket dont le délai a expiré ; une session HTTP/2 (même
|
|
64
|
+
* événement sur un serveur `allowHTTP1`) est laissée à son propre délai
|
|
65
|
+
*/
|
|
66
|
+
export declare function routeSocketTimeout(socket: unknown): void;
|
|
46
67
|
import type { IHttpContext as IHttpContextInterface } from "../../../interfaces/IContext.js";
|
|
47
68
|
import { type ISseStreamOptions, type SseStream } from "./SseStream.js";
|
|
69
|
+
import { type ByteStreamSource, type IByteStreamOptions, type IByteStreamResult } from "./ByteStream.js";
|
|
48
70
|
declare class HttpContext extends Context implements IHttpContextInterface {
|
|
49
71
|
proxy: ProxyType | null;
|
|
50
72
|
/** Socket h1 inscrite dans `socketActiveContext` — retirée par `clean()`. */
|
|
@@ -157,11 +179,31 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
157
179
|
*
|
|
158
180
|
* La requête ne se termine qu'à `close()` du flux, ou au départ du client.
|
|
159
181
|
*
|
|
182
|
+
* @typeParam Events - carte des événements du flux (cf `SseStream`).
|
|
160
183
|
* @param options - battement de cœur, délai `retry:` initial.
|
|
161
184
|
* @returns le flux, ou sa promesse quand une session est à sauver d'abord.
|
|
162
185
|
* @throws HttpError 500 quand la réponse est déjà partie.
|
|
163
186
|
*/
|
|
164
|
-
openSse(options?: ISseStreamOptions): SseStream | Promise<SseStream
|
|
187
|
+
openSse<Events extends EventsMap = DefaultEventsMap>(options?: ISseStreamOptions): SseStream<Events> | Promise<SseStream<Events>>;
|
|
188
|
+
/**
|
|
189
|
+
* Répond par un FLUX D'OCTETS : un `<video src>`, un `fetch()`, un
|
|
190
|
+
* téléchargement ou un outil tiers (curl) le consomment comme n'importe quelle
|
|
191
|
+
* réponse HTTP, une réponse par consommateur.
|
|
192
|
+
*
|
|
193
|
+
* La réponse est prise à l'appel, ses en-têtes partent au premier morceau de
|
|
194
|
+
* la source. La source est DÉTRUITE à la fin, quelle qu'en soit la raison
|
|
195
|
+
* (jamais `unpipe`, qui laisse un producteur bloqué en écriture). Pour des
|
|
196
|
+
* événements texte lus par `EventSource`, voir {@link HttpContext.openSse}.
|
|
197
|
+
* Dans un middleware, la requête est répondue : ne pas appeler la suite.
|
|
198
|
+
*
|
|
199
|
+
* @param source - flux Node, ou flux web (`fetch().body`) ; jamais en mode objet.
|
|
200
|
+
* @param options - statut, en-têtes, délais, écouteur de fin.
|
|
201
|
+
* @returns la raison de la fin et le nombre d'octets ; ne rejette jamais.
|
|
202
|
+
* @throws HttpError 500 quand la réponse est déjà partie, 429 au-delà du
|
|
203
|
+
* plafond de connexions par IP ; TypeError pour une source en mode objet.
|
|
204
|
+
* Quand l'appel lève, la source est DÉTRUITE (un flux web est annulé).
|
|
205
|
+
*/
|
|
206
|
+
openStream(source: ByteStreamSource, options?: IByteStreamOptions): Promise<IByteStreamResult>;
|
|
165
207
|
flush(chunk: unknown, encoding: BufferEncoding): MaybePromise<HttpResponse>;
|
|
166
208
|
/**
|
|
167
209
|
* Termine la requête : hook `onClose`, puis fin de la réponse si l'envoi ne
|
|
@@ -194,7 +236,7 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
194
236
|
getHost(): string | undefined;
|
|
195
237
|
getUserAgent(): string | undefined;
|
|
196
238
|
getMethod(): HTTPMethod;
|
|
197
|
-
setContentType(type?: string, encoding?: BufferEncoding): void
|
|
239
|
+
setContentType(type?: string, encoding?: BufferEncoding): void;
|
|
198
240
|
protected resolveIsHtml(): boolean;
|
|
199
241
|
setDefaultContentType(): void;
|
|
200
242
|
setContextJson(encoding?: BufferEncoding): void;
|
|
@@ -220,12 +220,19 @@ declare class HttpRequest {
|
|
|
220
220
|
getDomain(): string;
|
|
221
221
|
getUserAgent(): string | undefined;
|
|
222
222
|
getHostName(host?: string): string;
|
|
223
|
+
/**
|
|
224
|
+
* L'autorité de la requête : celle de la cible en forme absolue, sinon
|
|
225
|
+
* l'en-tête `Host` (RFC 9112 §3.2.2 — le serveur DOIT alors ignorer `Host`).
|
|
226
|
+
* HTTP/2 lit `:authority` (override).
|
|
227
|
+
*/
|
|
223
228
|
getHost(): string | undefined;
|
|
224
229
|
getRemoteAddress(): string | null;
|
|
225
230
|
/**
|
|
226
231
|
* Request-target brut (origin-form) tel que reçu du transport — SOURCE
|
|
227
232
|
* UNIQUE partagée par la concaténation de {@link getFullUrl} et la découpe
|
|
228
|
-
* fast-path du ctor.
|
|
233
|
+
* fast-path du ctor. Une cible en forme absolue est ramenée à sa forme
|
|
234
|
+
* origine, son autorité passant par {@link getHost}. HTTP/2 lit le
|
|
235
|
+
* pseudo-header `:path` (override).
|
|
229
236
|
*/
|
|
230
237
|
getRawTarget(): string | undefined;
|
|
231
238
|
/**
|
|
@@ -1,9 +1,21 @@
|
|
|
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
|
+
* Vrai pour un statut dont la réponse n'a, par définition, aucun contenu —
|
|
10
|
+
* 204, 205 et 304 (RFC 9110 §15.3.5, §15.3.6, §15.4.5).
|
|
11
|
+
*
|
|
12
|
+
* Seule source de cette liste : la réponse s'en sert pour ne jamais écrire de
|
|
13
|
+
* corps, le résolveur pour terminer une action qui ne rend rien.
|
|
14
|
+
*
|
|
15
|
+
* @param status - le code de statut de la réponse.
|
|
16
|
+
* @returns `true` quand aucun corps ne doit partir.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isContentlessStatus(status: number): boolean;
|
|
7
19
|
declare class HttpResponse {
|
|
8
20
|
#private;
|
|
9
21
|
context: HttpContext;
|
|
@@ -16,6 +28,13 @@ declare class HttpResponse {
|
|
|
16
28
|
headers: http.OutgoingHttpHeaders;
|
|
17
29
|
timeout?: number | undefined;
|
|
18
30
|
cookies: Record<string, Cookie>;
|
|
31
|
+
/**
|
|
32
|
+
* Pare-feu qui posera les en-têtes de sécurité applicatifs juste avant
|
|
33
|
+
* l'émission des en-têtes, une fois le type de la réponse fixé : la politique
|
|
34
|
+
* suit ce type (ADR-0024 §C11). Armé par le noyau HTTP au routage, consommé
|
|
35
|
+
* une fois ; `null` sans module de sécurité.
|
|
36
|
+
*/
|
|
37
|
+
headerPolicy: IFirewallGate | null;
|
|
19
38
|
constructor(response: http.ServerResponse | http2.Http2ServerResponse, context: HttpContext);
|
|
20
39
|
/**
|
|
21
40
|
* Filet posé juste avant l'émission des en-têtes : si aucun `Content-Type`
|
|
@@ -36,7 +55,9 @@ declare class HttpResponse {
|
|
|
36
55
|
/**
|
|
37
56
|
* Corps tel qu'il part sur le fil : le texte s'il n'a jamais été encodé, sinon
|
|
38
57
|
* les octets — vide légal si rien n'a été posé (`write(null)` jetterait
|
|
39
|
-
* ERR_STREAM_NULL_VALUES).
|
|
58
|
+
* ERR_STREAM_NULL_VALUES). Vide aussi sous un statut sans contenu : un corps
|
|
59
|
+
* posé par l'application sous un 205 ne part pas (RFC 9110 §15.3.6) — Node
|
|
60
|
+
* ne l'écarte de lui-même que pour 204 et 304.
|
|
40
61
|
*/
|
|
41
62
|
protected get payload(): string | Buffer;
|
|
42
63
|
/** Encodage à passer avec {@link payload} (ignoré par Node pour des octets). */
|
|
@@ -48,8 +69,8 @@ declare class HttpResponse {
|
|
|
48
69
|
addCookie(cookie: Cookie): Cookie;
|
|
49
70
|
deleteCookie(cookie: Cookie): boolean;
|
|
50
71
|
deleteCookieByName(name: string): boolean;
|
|
51
|
-
setCookies(): void
|
|
52
|
-
setCookie(cookie: Cookie): void
|
|
72
|
+
setCookies(): void;
|
|
73
|
+
setCookie(cookie: Cookie): void;
|
|
53
74
|
/**
|
|
54
75
|
* Journalise en DEBUG un cookie posé, sa VALEUR MASQUÉE : nom et attributs
|
|
55
76
|
* seulement. Le journal de développement se lit par le plan d'administration
|
|
@@ -61,11 +82,91 @@ declare class HttpResponse {
|
|
|
61
82
|
* @param serialized - sa forme `Set-Cookie`, déjà calculée pour l'en-tête.
|
|
62
83
|
*/
|
|
63
84
|
private logCookie;
|
|
64
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Pose un en-tête de la réponse ; en flux chunké déjà ouvert, le range en
|
|
87
|
+
* trailer. Le nom et la valeur sont VALIDÉS ici, comme le ferait Node : une
|
|
88
|
+
* valeur invalide (saut de ligne…) lève tout de suite et n'entre jamais dans
|
|
89
|
+
* la réserve — sinon elle lèverait au `writeHead`, et la page d'erreur
|
|
90
|
+
* rejouerait le même en-tête.
|
|
91
|
+
*
|
|
92
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
93
|
+
* @param value - valeur ; une liste émet une ligne par élément (`Set-Cookie`).
|
|
94
|
+
* @throws TypeError `ERR_INVALID_HTTP_TOKEN` / `ERR_INVALID_CHAR` sur un nom
|
|
95
|
+
* ou une valeur qu'un en-tête HTTP ne peut pas porter.
|
|
96
|
+
*/
|
|
97
|
+
setHeader(name: string, value: number | string | readonly string[]): void;
|
|
98
|
+
/**
|
|
99
|
+
* Pose un en-tête dont la valeur a DÉJÀ été validée par l'appelant — une
|
|
100
|
+
* table figée et contrôlée au démarrage (en-têtes de sécurité). Node la
|
|
101
|
+
* validera une fois, au `writeHead` ; la revalider ici effaçait le gain de
|
|
102
|
+
* l'écriture groupée (`micro-write-head.mjs`, ligne « plat validé »).
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ Jamais pour une valeur venue d'une requête : elle passe par {@link setHeader}.
|
|
105
|
+
*
|
|
106
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
107
|
+
* @param value - valeur contrôlée au démarrage.
|
|
108
|
+
*/
|
|
109
|
+
setValidatedHeader(name: string, value: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Pose un en-tête validé au démarrage SAUF si la réponse en porte déjà un :
|
|
112
|
+
* la valeur du framework est un défaut, celle que l'application a écrite
|
|
113
|
+
* gagne. Même coût que {@link setValidatedHeader} — le rangement cherche
|
|
114
|
+
* l'en-tête de toute façon.
|
|
115
|
+
*
|
|
116
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
117
|
+
* @param value - valeur contrôlée au démarrage.
|
|
118
|
+
*/
|
|
119
|
+
setDefaultHeader(name: string, value: string): void;
|
|
120
|
+
/**
|
|
121
|
+
* Pose les en-têtes de la politique armée ({@link headerPolicy}) pour une
|
|
122
|
+
* réponse de type `contentType` — une fois, juste avant l'émission.
|
|
123
|
+
*
|
|
124
|
+
* Un `304` n'en reçoit aucun : ses en-têtes REMPLACENT ceux de la
|
|
125
|
+
* représentation en cache (RFC 9111 §4.3.4). Une politique courte y
|
|
126
|
+
* réduirait la page gardée, un nonce neuf la désaccorderait de ses balises.
|
|
127
|
+
*
|
|
128
|
+
* @param contentType - type de la réponse ; `null` = sans contenu ; `undefined`
|
|
129
|
+
* = inconnu (politique complète).
|
|
130
|
+
* @param servedAsFile - corps écrit par un tiers (fichier statique, relais),
|
|
131
|
+
* qui ne peut pas porter de nonce.
|
|
132
|
+
*/
|
|
133
|
+
protected applyHeaderPolicy(contentType: string | null | undefined, servedAsFile?: boolean): void;
|
|
134
|
+
/**
|
|
135
|
+
* Type qui choisit la politique : `null` pour une réponse sans contenu (204,
|
|
136
|
+
* corps vide) — même si un `Content-Type` y a été posé, rien n'y sera rendu ;
|
|
137
|
+
* sinon le `Content-Type` posé.
|
|
138
|
+
*/
|
|
139
|
+
protected contentTypeForPolicy(): string | null;
|
|
140
|
+
/**
|
|
141
|
+
* Recopie la réserve sur la réponse Node, pour un émetteur qui écrit sans
|
|
142
|
+
* passer par {@link writeHead} — fichier statique, relais vers un amont. La
|
|
143
|
+
* réserve se vide ; ce qui se pose ensuite y retourne, et Node fusionne les
|
|
144
|
+
* deux au `writeHead`.
|
|
145
|
+
*/
|
|
146
|
+
commitHeaders(): void;
|
|
147
|
+
/**
|
|
148
|
+
* Recopie la réserve sur la réponse Node au PREMIER `setHeader` qu'un tiers
|
|
149
|
+
* y fera — fichier statique servi, redirection de dossier — et seulement
|
|
150
|
+
* alors : un raté du repli statique (la 404 d'une route inconnue) ne recopie
|
|
151
|
+
* rien, sa réponse part ensuite en un seul `writeHead`. À désarmer par
|
|
152
|
+
* {@link disarmNativeWrite} quand le tiers rend la main sans avoir écrit.
|
|
153
|
+
*
|
|
154
|
+
* L'interception est une propriété PROPRE de la réponse, ajoutée puis
|
|
155
|
+
* retirée : V8 annule alors la dernière transition de forme de l'objet.
|
|
156
|
+
*/
|
|
157
|
+
commitBeforeNativeWrite(): void;
|
|
158
|
+
/** Retire l'interception posée par {@link commitBeforeNativeWrite}. */
|
|
159
|
+
disarmNativeWrite(): void;
|
|
160
|
+
/**
|
|
161
|
+
* Retire un en-tête, de la réserve comme de la réponse Node.
|
|
162
|
+
*
|
|
163
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
164
|
+
*/
|
|
165
|
+
removeHeader(name: string): void;
|
|
65
166
|
setHeaders(obj: OutgoingHttpHeaders): OutgoingHttpHeaders;
|
|
66
|
-
setContentType(type?: string, encoding?: BufferEncoding): void
|
|
67
|
-
setFileMimeType(type: string, encoding?: BufferEncoding): void
|
|
68
|
-
setContentTypeByExtension(extention: string): void
|
|
167
|
+
setContentType(type?: string, encoding?: BufferEncoding): void;
|
|
168
|
+
setFileMimeType(type: string, encoding?: BufferEncoding): void;
|
|
169
|
+
setContentTypeByExtension(extention: string): void;
|
|
69
170
|
getMimeType(filenameOrExt: string): string | false;
|
|
70
171
|
setEncoding(encoding: BufferEncoding): BufferEncoding;
|
|
71
172
|
setStatusCode(status: number | string, message?: string): {
|
|
@@ -88,7 +189,25 @@ declare class HttpResponse {
|
|
|
88
189
|
setBody(ele: unknown, encoding?: BufferEncoding): void;
|
|
89
190
|
setLength(body?: string | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
|
90
191
|
writeHead(statusCode?: number, headers?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): void;
|
|
192
|
+
/** La réserve amorcée — pour une remise sous une autre forme (HTTP/2). */
|
|
193
|
+
protected headerList(): readonly OutgoingHttpHeader[];
|
|
91
194
|
addTrailers(headers: http.OutgoingHttpHeaders): void;
|
|
195
|
+
/**
|
|
196
|
+
* Écrit un morceau d'une réponse en flux, sans la terminer — c'est `end()`
|
|
197
|
+
* qui la clôt après le dernier.
|
|
198
|
+
*
|
|
199
|
+
* Au premier morceau, les en-têtes partent SANS longueur ni corps posé (le
|
|
200
|
+
* patron de `SseStream`), et le cadrage revient à Node : `chunked` pour un
|
|
201
|
+
* client HTTP/1.1, fermeture de connexion pour un HTTP/1.0 (RFC 9112 §6.1 —
|
|
202
|
+
* pas de `Transfer-Encoding` à qui ne le connaît pas), trames DATA en HTTP/2
|
|
203
|
+
* où l'en-tête est interdit (RFC 9113 §8.2.2). Ne JAMAIS le poser à la main :
|
|
204
|
+
* une fois `flushing` levé, `setHeader` range en trailer, et un
|
|
205
|
+
* `Transfer-Encoding` en trailer est un message malformé (RFC 9110 §6.5.1).
|
|
206
|
+
*
|
|
207
|
+
* @param chunk - le morceau à écrire.
|
|
208
|
+
* @param encoding - l'encodage d'un morceau texte.
|
|
209
|
+
* @returns la promesse réglée quand le flux accepte l'écriture suivante.
|
|
210
|
+
*/
|
|
92
211
|
flush(chunk: unknown, encoding: BufferEncoding): MaybePromise<HttpResponse>;
|
|
93
212
|
/**
|
|
94
213
|
* Écrit le corps sur le flux et, pour une réponse unique, la termine.
|
|
@@ -113,8 +232,15 @@ declare class HttpResponse {
|
|
|
113
232
|
* @throws de façon SYNCHRONE quand la réponse Node n'existe plus.
|
|
114
233
|
*/
|
|
115
234
|
end(chunk?: string | Buffer, encoding?: BufferEncoding): MaybePromise<http.ServerResponse | http2.ServerHttp2Stream>;
|
|
235
|
+
/**
|
|
236
|
+
* Valeur d'un en-tête posé — dans la réserve, sinon sur la réponse Node (ce
|
|
237
|
+
* qu'un tiers y a écrit directement : `send`, quota).
|
|
238
|
+
*
|
|
239
|
+
* @param name - nom de l'en-tête (casse indifférente).
|
|
240
|
+
*/
|
|
116
241
|
getHeader(name: string): string | number | string[] | undefined;
|
|
117
242
|
hasHeader(name: string): boolean;
|
|
243
|
+
/** Tous les en-têtes posés, noms en minuscules — une copie, à chaque appel. */
|
|
118
244
|
getHeaders(): http.OutgoingHttpHeaders;
|
|
119
245
|
redirect(url: string, status?: number | string, headers?: Record<string, string | number>): this;
|
|
120
246
|
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,10 +57,14 @@ 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
|
}
|
|
@@ -69,14 +74,32 @@ export declare const SSE_HEARTBEAT_MS = 15000;
|
|
|
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,53 @@
|
|
|
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
|
+
* Qui a fermé le flux, quand ce n'est pas le client : le serveur (`close()` —
|
|
32
|
+
* fin demandée, durée de vie, révocation), ou la coupure d'un client qui ne lit
|
|
33
|
+
* plus. `null` : le client est parti, ou le flux est encore ouvert.
|
|
34
|
+
*/
|
|
35
|
+
export type StreamLifecycleCause = "server" | "stall" | null;
|
|
36
|
+
/** Ce sur quoi on écrit : la réponse HTTP/1.1, ou le flux HTTP/2. */
|
|
37
|
+
export type StreamTarget = http.ServerResponse | http2.ServerHttp2Stream;
|
|
38
|
+
/** Gardes et identité d'un flux, options résolues. */
|
|
39
|
+
export interface IStreamLifecycleSettings {
|
|
40
|
+
stallTimeout: number | false;
|
|
41
|
+
maxDuration: number | false;
|
|
42
|
+
/** Revalidateur de l'identité du flux — `null` pour un anonyme. */
|
|
43
|
+
revalidate: (() => Promise<boolean>) | null;
|
|
44
|
+
/** Identifiant de connexion de l'identité qui ouvre le flux — `null` anonyme. */
|
|
45
|
+
identifier: string | null;
|
|
46
|
+
}
|
|
47
|
+
/** Ce que l'ouverture reçoit de l'appelant. */
|
|
48
|
+
export interface IOpenStreamLifecycle {
|
|
49
|
+
/** Préfixe des messages d'erreur (`SSE`, `Flux`). */
|
|
50
|
+
label: string;
|
|
51
|
+
stallTimeout: number | false;
|
|
52
|
+
maxDuration: number | false;
|
|
53
|
+
}
|