@nodefony/http 10.0.0-beta.5 → 10.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +30 -26
- package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
- package/dist/nodefony/config/config.js +29 -4
- package/dist/nodefony/grpc/GrpcCall.js +298 -0
- package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
- package/dist/nodefony/grpc/index.js +4 -0
- package/dist/nodefony/interfaces/IRpcCall.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
- package/dist/nodefony/service/audit-logger.js +52 -6
- package/dist/nodefony/service/error-renderer.js +47 -13
- package/dist/nodefony/service/http-kernel.js +306 -49
- package/dist/nodefony/service/pretty-request-logger.js +5 -3
- package/dist/nodefony/service/request-logger.js +21 -6
- package/dist/nodefony/service/reverse-proxy.js +8 -3
- package/dist/nodefony/service/servers/clientError.js +53 -4
- package/dist/nodefony/service/servers/server-h2c.js +6 -6
- package/dist/nodefony/service/servers/server-http.js +6 -4
- package/dist/nodefony/service/servers/server-https.js +14 -7
- package/dist/nodefony/service/servers/server-static.js +1 -6
- package/dist/nodefony/service/servers/server-websocket-secure.js +7 -11
- package/dist/nodefony/service/servers/server-websocket.js +7 -11
- package/dist/nodefony/service/servers/serverShutdown.js +43 -6
- package/dist/nodefony/service/sessions/sessions-service.js +7 -7
- package/dist/nodefony/service/upload/upload-service.js +2 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
- package/dist/nodefony/src/context/Context.js +12 -1
- package/dist/nodefony/src/context/http/ByteStream.js +364 -0
- package/dist/nodefony/src/context/http/HttpContext.js +139 -32
- package/dist/nodefony/src/context/http/Request.js +111 -22
- package/dist/nodefony/src/context/http/Response.js +459 -37
- package/dist/nodefony/src/context/http/SseStream.js +83 -148
- package/dist/nodefony/src/context/http/StreamLifecycle.js +582 -0
- package/dist/nodefony/src/context/http/parser.js +29 -11
- package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
- package/dist/nodefony/src/context/http/webResponse.js +56 -0
- package/dist/nodefony/src/context/http2/Request.js +1 -1
- package/dist/nodefony/src/context/http2/Response.js +55 -12
- package/dist/nodefony/src/context/requestTrace.js +6 -2
- package/dist/nodefony/src/context/rpcOutcome.js +20 -0
- package/dist/nodefony/src/context/transport.js +20 -1
- package/dist/nodefony/src/context/websocket/Response.js +15 -3
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +86 -18
- package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
- package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
- package/dist/nodefony/src/cookies/cookie.js +57 -14
- package/dist/nodefony/src/errors/httpError.js +58 -25
- package/dist/nodefony/src/grpc/cors.js +59 -0
- package/dist/nodefony/src/grpc/limits.js +16 -0
- package/dist/nodefony/src/grpc/refusal.js +271 -0
- package/dist/nodefony/src/profiler/Profiler.js +14 -1
- package/dist/nodefony/src/profiler/aggregate.js +2 -2
- package/dist/nodefony/src/proxy/forward.js +109 -9
- package/dist/nodefony/src/proxy/generateProxyConfig.js +97 -17
- package/dist/nodefony/src/proxy/rules.js +29 -14
- package/dist/nodefony/src/servers/http2Goaway.js +76 -0
- package/dist/nodefony/src/servers/http2Limits.js +41 -15
- package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
- package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
- package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
- package/dist/types/index.d.ts +11 -2
- package/dist/types/nodefony/config/config.d.ts +14 -0
- package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
- package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
- package/dist/types/nodefony/grpc/index.d.ts +15 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
- package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
- package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
- package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
- package/dist/types/nodefony/service/error-renderer.d.ts +27 -5
- package/dist/types/nodefony/service/http-kernel.d.ts +36 -3
- package/dist/types/nodefony/service/request-logger.d.ts +10 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +7 -5
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
- package/dist/types/nodefony/src/context/Context.d.ts +9 -1
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +99 -3
- package/dist/types/nodefony/src/context/http/Request.d.ts +46 -9
- package/dist/types/nodefony/src/context/http/Response.d.ts +188 -8
- package/dist/types/nodefony/src/context/http/SseStream.d.ts +33 -8
- package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +106 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
- package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +40 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
- package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
- package/dist/types/nodefony/src/context/transport.d.ts +17 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +48 -0
- package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
- package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
- package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
- package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
- package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
- package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
- package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +11 -1
- package/dist/types/nodefony/src/proxy/forward.d.ts +37 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
- package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
- package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
- package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
- package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
- package/docs/byte-stream.md +406 -0
- package/docs/cookies.md +13 -3
- package/docs/diagnostics-channel.md +45 -1
- package/docs/index.md +8 -0
- package/docs/reverse-proxy.md +15 -3
- package/docs/servers.md +67 -43
- package/docs/session.md +3 -3
- package/docs/sse.md +27 -20
- package/docs/upload.md +43 -17
- package/package.json +38 -18
|
@@ -9,8 +9,9 @@ interface IHeaderSource {
|
|
|
9
9
|
getHeader(name: string): http.OutgoingHttpHeader | undefined;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Compose les en-têtes d'une réponse HTTP/2 : ceux
|
|
13
|
-
* ceux
|
|
12
|
+
* Compose les en-têtes d'une réponse HTTP/2 : ceux qu'un tiers a écrits sur la
|
|
13
|
+
* réponse Node, puis ceux de la réserve de la réponse, puis ceux passés à
|
|
14
|
+
* `writeHead` — chacun l'emporte sur le précédent.
|
|
14
15
|
*
|
|
15
16
|
* Les clés sont ramenées en MINUSCULES : `getHeaders()` rend `content-type`,
|
|
16
17
|
* un contrôleur passe `Content-Type` — fusionnées telles quelles, les deux
|
|
@@ -26,9 +27,10 @@ interface IHeaderSource {
|
|
|
26
27
|
* @param statusMessage - message de statut, recopié en `x-status-message`
|
|
27
28
|
* @param current - la réponse Node qui porte les en-têtes déjà posés
|
|
28
29
|
* @param extra - en-têtes passés à `writeHead` (objet, ou liste plate clé/valeur)
|
|
30
|
+
* @param stored - la réserve de la réponse, liste plate aux noms en minuscules
|
|
29
31
|
* @returns les en-têtes à transmettre à `respond()`
|
|
30
32
|
*/
|
|
31
|
-
export declare function mergeResponseHeaders(statusMessage: string, current: IHeaderSource, extra?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): http.OutgoingHttpHeaders;
|
|
33
|
+
export declare function mergeResponseHeaders(statusMessage: string, current: IHeaderSource, extra?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[], stored?: readonly http.OutgoingHttpHeader[]): http.OutgoingHttpHeaders;
|
|
32
34
|
declare class Http2Response extends HttpResponse {
|
|
33
35
|
statusCode: number;
|
|
34
36
|
stream: http2.ServerHttp2Stream | null;
|
|
@@ -52,8 +54,9 @@ declare class Http2Response extends HttpResponse {
|
|
|
52
54
|
*/
|
|
53
55
|
end(chunk?: string | Buffer, encoding?: BufferEncoding): MaybePromise<http.ServerResponse | http2.ServerHttp2Stream>;
|
|
54
56
|
/**
|
|
55
|
-
* Termine la réponse de compatibilité d'un HEAD,
|
|
56
|
-
*
|
|
57
|
+
* Termine la réponse de compatibilité d'un HEAD, ou d'une réponse gRPC
|
|
58
|
+
* _Trailers-Only_ (statut gRPC dans les en-têtes), dont le flux s'est clos
|
|
59
|
+
* au `respond()`.
|
|
57
60
|
*
|
|
58
61
|
* Sans ce geste, `Http2ServerResponse` n'émet ni `finish` ni `close` : le
|
|
59
62
|
* démontage de la requête, branché sur ce `close` (ligne d'accès, profil,
|
|
@@ -68,5 +71,37 @@ declare class Http2Response extends HttpResponse {
|
|
|
68
71
|
code: number;
|
|
69
72
|
message: string;
|
|
70
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Règle le sort d'un corps non lu quand la réponse part avant qu'il soit
|
|
76
|
+
* consommé (408 délai, 413 trop gros, refus précoce) : les octets en tampon
|
|
77
|
+
* sont jetés, puis — une fois la réponse écrite — le flux est fermé en
|
|
78
|
+
* `NO_ERROR` si le client ÉMET ENCORE.
|
|
79
|
+
*
|
|
80
|
+
* RFC 9113 §8.1 : après une réponse complète, le serveur PEUT demander au
|
|
81
|
+
* client d'arrêter d'émettre par un `RST_STREAM` `NO_ERROR`. `node:http2` ne
|
|
82
|
+
* le fait pas : le flux resterait ouvert tant que le client émet, et un
|
|
83
|
+
* client lent garderait sa place de flux sur la session. Attendre `finish`
|
|
84
|
+
* (fin d'écriture) garantit que la réponse elle-même n'est pas tronquée.
|
|
85
|
+
*
|
|
86
|
+
* « Émet encore » se décide après une GRÂCE d'un aller-retour
|
|
87
|
+
* ({@link closeStreamAfterGrace} : un `PING` par session pour tous les
|
|
88
|
+
* flux qui attendent, puis l'état du flux). Fini → le flux se ferme seul,
|
|
89
|
+
* sans `RST_STREAM` ; sinon — client qui émet encore, ou muet — fermé.
|
|
90
|
+
* Jamais de coupe avant la grâce : un `RST_STREAM` soumis pendant que la
|
|
91
|
+
* réponse attend dans la file de nghttp2 la fait jeter, et le client reçoit
|
|
92
|
+
* une fin de flux sans statut. grpc-go, Go `net/http2` et nginx coupent AUSSITÔT : Node, lui,
|
|
93
|
+
* répond avant de lire les trames DATA déjà en route, et la coupe
|
|
94
|
+
* immédiate frappait des clients qui finissaient d'émettre — le client
|
|
95
|
+
* HTTP/2 de Go la lit en erreur (ADR-0026 D13 point 1, constaté par la
|
|
96
|
+
* suite de conformité de Connect). `request.complete` ne dit rien ici : il
|
|
97
|
+
* ne passe à `true` qu'une fois le corps CONSOMMÉ.
|
|
98
|
+
*
|
|
99
|
+
* Chemin d'erreur seulement : un `PING` par aller-retour et par session,
|
|
100
|
+
* quel que soit le nombre de refus en attente.
|
|
101
|
+
*
|
|
102
|
+
* Sans effet si le corps est entièrement consommé, ou le flux déjà fermé.
|
|
103
|
+
* Peut s'appeler avant comme après l'écriture de la réponse.
|
|
104
|
+
*/
|
|
105
|
+
closeUnfinishedRequest(): void;
|
|
71
106
|
}
|
|
72
107
|
export default Http2Response;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IRequestTrace, RequestTraceProtocol } from "../../interfaces/IRequestTrace.js";
|
|
2
|
+
import type { IRpcOutcome } from "../../interfaces/IRpcCall.js";
|
|
2
3
|
import type Context from "./Context.js";
|
|
3
4
|
/**
|
|
4
5
|
* Nom du `TracingChannel` qui encadre chaque requête HTTP et chaque handshake
|
|
@@ -60,5 +61,6 @@ export declare function runTraced<ThisArg, Args extends unknown[], Result>(trace
|
|
|
60
61
|
* @param context - le contexte de la requête
|
|
61
62
|
* @param statusCode - le statut rendu au client, `null` s'il n'y en a pas eu
|
|
62
63
|
* @param error - l'exception attrapée par le pipeline, `null` ou `undefined` sinon
|
|
64
|
+
* @param rpc - l'appel RPC de la requête, `null` hors RPC : copié, jamais publié tel quel
|
|
63
65
|
*/
|
|
64
|
-
export declare function closeRequestTrace(context: Context, statusCode: number | null, error: unknown): void;
|
|
66
|
+
export declare function closeRequestTrace(context: Context, statusCode: number | null, error: unknown, rpc?: IRpcOutcome | null): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IRpcOutcome } from "../../interfaces/IRpcCall.js";
|
|
2
|
+
/**
|
|
3
|
+
* Copie le résultat d'un appel RPC — les quatre champs, jamais la fiche
|
|
4
|
+
* d'appel entière, qui porte le contrôleur et le contexte (ADR-0026 D10). La
|
|
5
|
+
* forme est fixe : un objet par appel, alloué seulement par qui publie (audit
|
|
6
|
+
* JSON, canal de traçage avec abonné).
|
|
7
|
+
*
|
|
8
|
+
* @param call - l'appel RPC de la requête, `null` hors RPC.
|
|
9
|
+
* @returns sa copie, `null` hors RPC.
|
|
10
|
+
*/
|
|
11
|
+
export declare function rpcOutcomeOf(call: IRpcOutcome | null): IRpcOutcome | null;
|
|
@@ -51,3 +51,20 @@ export declare function requestAuthority(headers: {
|
|
|
51
51
|
host?: string | undefined;
|
|
52
52
|
":authority"?: string | undefined;
|
|
53
53
|
}): string | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Une requête HTTP/2 nomme-t-elle deux hôtes — `:authority` ET un `Host`
|
|
56
|
+
* différent ? RFC 9113 §8.3.1 : « SHOULD treat a request as malformed ». Le
|
|
57
|
+
* serveur lit `:authority` ({@link requestAuthority}), mais un code qui lit
|
|
58
|
+
* `headers.host` verrait un autre hôte que celui que le routage et le
|
|
59
|
+
* pare-feu ont jugé.
|
|
60
|
+
*
|
|
61
|
+
* Chemin de chaque requête : une lecture d'en-tête ; la comparaison n'a lieu
|
|
62
|
+
* que si les deux sont présents (un navigateur n'envoie pas `Host` en HTTP/2).
|
|
63
|
+
*
|
|
64
|
+
* @param headers - en-têtes de la requête Node.
|
|
65
|
+
* @returns `true` quand les deux autorités diffèrent (casse ignorée).
|
|
66
|
+
*/
|
|
67
|
+
export declare function hasConflictingAuthority(headers: {
|
|
68
|
+
host?: string | undefined;
|
|
69
|
+
":authority"?: string | undefined;
|
|
70
|
+
}): boolean;
|
|
@@ -54,7 +54,12 @@ declare class WebsocketResponse {
|
|
|
54
54
|
handshakeHeaders: Record<string, string | number> | null;
|
|
55
55
|
/**
|
|
56
56
|
* Retient un en-tête du handshake (cf {@link handshakeHeaders}) ; sans effet
|
|
57
|
-
* une fois la socket ouverte, où il n'y a plus de réponse HTTP
|
|
57
|
+
* une fois la socket ouverte, où il n'y a plus de réponse HTTP — sauf
|
|
58
|
+
* `Retry-After` posé par une action invoquée par le pont `api.request` : il
|
|
59
|
+
* va au puits de CETTE invocation, et le pont le sert avec le refus. Le même
|
|
60
|
+
* code de contrôleur dit donc « réessaie dans N s » sur les deux transports.
|
|
61
|
+
* Seul cet en-tête passe : un `Set-Cookie` ou un `WWW-Authenticate` n'a
|
|
62
|
+
* aucun sens sur une trame, et rien d'autre ne doit fuir par là.
|
|
58
63
|
*
|
|
59
64
|
* @param name - nom de l'en-tête
|
|
60
65
|
* @param value - valeur
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Context from "../Context.js";
|
|
2
2
|
import { ServerType, SchemeType } from "../../../service/http-kernel.js";
|
|
3
3
|
import { Severity, Msgid, Message, nodefonyError, Scope } from "nodefony";
|
|
4
|
+
import type { IFrameCodec } from "nodefony";
|
|
4
5
|
import Ws from "ws";
|
|
5
6
|
import type { IncomingMessage } from "node:http";
|
|
6
7
|
import WebsocketResponse from "./Response.js";
|
|
@@ -11,6 +12,7 @@ import HttpError from "../../errors/httpError.js";
|
|
|
11
12
|
import { FrameProfile } from "../../profiler/FrameProfile.js";
|
|
12
13
|
import type { PhaseName, IWebsocketContext } from "../../../interfaces/IContext.js";
|
|
13
14
|
import { ProxyType } from "../http/HttpContext.js";
|
|
15
|
+
import { type IFrameRateWindow } from "./wsFrameRate.js";
|
|
14
16
|
import { type ResolvedProxy } from "../forwarded.js";
|
|
15
17
|
export interface IWsRequestExtension {
|
|
16
18
|
url: URL;
|
|
@@ -70,10 +72,22 @@ declare class WebsocketContext extends Context implements IWebsocketContextInter
|
|
|
70
72
|
request: WsIncomingMessage | null;
|
|
71
73
|
response: WebsocketResponse | null;
|
|
72
74
|
acceptedProtocol?: string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Codec des trames BINAIRES de cette connexion, posé par la couche qui l'a négocié
|
|
77
|
+
* (le temps réel, ADR-0022 §2). Hors production, le traceur s'en sert pour
|
|
78
|
+
* afficher une trame binaire décodée plutôt que sa seule taille ; jamais lu en
|
|
79
|
+
* production. `null` = octets bruts, résumés à leur taille.
|
|
80
|
+
*/
|
|
81
|
+
frameCodec: IFrameCodec | null;
|
|
73
82
|
port: number | string;
|
|
74
83
|
rejected: boolean;
|
|
75
84
|
teardownWired: boolean;
|
|
76
85
|
connection: Ws | null;
|
|
86
|
+
/**
|
|
87
|
+
* Fenêtre de la borne de débit entrante — `null` tant que la borne est
|
|
88
|
+
* désactivée ou qu'aucune trame n'est arrivée (créée au premier usage).
|
|
89
|
+
*/
|
|
90
|
+
frameRate: IFrameRateWindow | null;
|
|
77
91
|
/**
|
|
78
92
|
* Le verdict du handshake encore à rendre : posé par le noyau tant que le
|
|
79
93
|
* pipeline décide (pare-feu, garde de l'action), `null` dès que la socket
|
|
@@ -109,7 +123,31 @@ declare class WebsocketContext extends Context implements IWebsocketContextInter
|
|
|
109
123
|
handle(data?: unknown[]): Promise<this>;
|
|
110
124
|
render(chunk: unknown, encoding?: BufferEncoding): Promise<WebsocketResponse>;
|
|
111
125
|
send(data?: string | Buffer | null, encoding?: BufferEncoding): Promise<WebsocketResponse>;
|
|
126
|
+
/**
|
|
127
|
+
* Rend le corps rendu (`setBody`) en le RETIRANT de la réponse : une trame
|
|
128
|
+
* envoyée n'a plus à vivre aussi longtemps que la connexion.
|
|
129
|
+
*
|
|
130
|
+
* @param response - la réponse de ce contexte
|
|
131
|
+
* @returns le corps rendu, ou `null` s'il n'y en a pas
|
|
132
|
+
*/
|
|
133
|
+
private static takeRenderedBody;
|
|
112
134
|
broadcast(data?: string | Buffer | null, encoding?: BufferEncoding): null | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* Le contenu des messages est-il journalisé ? Hors production seulement —
|
|
137
|
+
* décidé une fois pour le processus (P8 : runtime ∈ {development, production},
|
|
138
|
+
* `resolveRuntimeEnv`).
|
|
139
|
+
*/
|
|
140
|
+
private tracesMessageContent;
|
|
141
|
+
/**
|
|
142
|
+
* Traceur des trames SORTANTES écrites hors de {@link send} — le temps réel
|
|
143
|
+
* écrit droit sur la socket. À demander UNE fois par connexion : `null` en
|
|
144
|
+
* production, donc l'appelant choisit son chemin d'écriture à l'ouverture et
|
|
145
|
+
* ne paie rien par trame.
|
|
146
|
+
*
|
|
147
|
+
* @returns une fonction qui journalise une trame comme `send` (`WS SEND`),
|
|
148
|
+
* ou `null` quand le contenu des messages n'est pas journalisé.
|
|
149
|
+
*/
|
|
150
|
+
outboundTracer(): ((data: unknown) => void) | null;
|
|
113
151
|
/**
|
|
114
152
|
* Logge le CONTENU d'un message WS (corrélé `requestId` via l'override `log`),
|
|
115
153
|
* gaté hors prod et borné — le Suivi de requête (Studio) le surface alors par
|
|
@@ -150,6 +188,16 @@ declare class WebsocketContext extends Context implements IWebsocketContextInter
|
|
|
150
188
|
*/
|
|
151
189
|
phaseStart(name: PhaseName): void;
|
|
152
190
|
phaseEnd(name: PhaseName): void;
|
|
191
|
+
/**
|
|
192
|
+
* Applique la borne de débit entrante à une trame : `true` = l'admettre.
|
|
193
|
+
*
|
|
194
|
+
* Écartée → comptée (`frameRateRejectedTotal`) ; solde de refus atteint →
|
|
195
|
+
* connexion fermée en `1008` (RFC 6455 §7.4.1, violation de politique).
|
|
196
|
+
*
|
|
197
|
+
* @param bound - trames admises par seconde (`> 0`)
|
|
198
|
+
* @returns `true` si la trame poursuit son chemin
|
|
199
|
+
*/
|
|
200
|
+
private admitInboundFrame;
|
|
153
201
|
handleMessage(data: Buffer | string, isBinary: boolean): Promise<unknown>;
|
|
154
202
|
onClose(code: number, reason: Buffer): void;
|
|
155
203
|
/**
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* un hôte de l'autre — et une route acceptée par l'une serait refusée par
|
|
8
8
|
* l'autre.
|
|
9
9
|
*
|
|
10
|
+
* Une cible en forme absolue (`http://hôte/chemin`, RFC 6455 §4.2.1) donne son
|
|
11
|
+
* hôte, et l'en-tête `Host` est ignoré (RFC 9112 §3.2.2) — la même règle que
|
|
12
|
+
* `HttpRequest.getHost`.
|
|
13
|
+
*
|
|
10
14
|
* @param scheme - `ws` ou `wss`
|
|
11
15
|
* @param host - en-tête `Host` de la requête (`localhost` s'il manque)
|
|
12
16
|
* @param rawUrl - cible brute de la requête (`/` si elle manque)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Ws, { WebSocketServer } from "ws";
|
|
2
|
+
import { type BackpressurePolicy, type BackpressureVerdict } from "nodefony";
|
|
2
3
|
/** Politique appliquée quand le buffer d'envoi d'un client dépasse le seuil. */
|
|
3
|
-
export type WsBackpressurePolicy =
|
|
4
|
+
export type WsBackpressurePolicy = BackpressurePolicy;
|
|
4
5
|
/**
|
|
5
6
|
* Options de backpressure SORTANTE (serveur → client).
|
|
6
7
|
*
|
|
@@ -24,6 +25,10 @@ export interface IWsBackpressureOptions {
|
|
|
24
25
|
export interface IBackpressureTarget {
|
|
25
26
|
readonly bufferedAmount?: number;
|
|
26
27
|
close(code?: number, reason?: string): void;
|
|
28
|
+
/** Cumul des frames refusées pour contre-pression (lazy — lu par la sonde socket). */
|
|
29
|
+
_nfDrops?: number;
|
|
30
|
+
/** Solde de refus courant (cf {@link nextDropBalance}) ; absent = 0. */
|
|
31
|
+
_nfDropStreak?: number;
|
|
27
32
|
}
|
|
28
33
|
/** Socket augmentée d'un compteur de drops (number lazy — lu par la sonde socket). */
|
|
29
34
|
export interface IBackpressureSocket extends Ws {
|
|
@@ -37,7 +42,7 @@ export interface IBackpressureSocket extends Ws {
|
|
|
37
42
|
_nfDropStreak?: number;
|
|
38
43
|
}
|
|
39
44
|
/** Décision pour une frame : l'émettre, la sauter, ou (socket fermée) ne rien faire. */
|
|
40
|
-
export type WsSendDecision =
|
|
45
|
+
export type WsSendDecision = BackpressureVerdict;
|
|
41
46
|
/**
|
|
42
47
|
* Relit le seuil + la politique depuis les options du `WebSocketServer`.
|
|
43
48
|
*
|
|
@@ -65,9 +70,14 @@ export declare const readBackpressureOptions: (wss: WebSocketServer | null | und
|
|
|
65
70
|
* « Try Again Later » → le client peut back-off + reconnecter). NE FAIT PAS le `send()`
|
|
66
71
|
* — le caller émet uniquement si le retour vaut `"send"`.
|
|
67
72
|
*
|
|
73
|
+
* La DÉCISION est celle du cœur (`decideBackpressure` / `nextDropBalance`, partagée
|
|
74
|
+
* avec `NodefonySocket`) ; cette fonction n'en porte que l'état (compteurs sur la
|
|
75
|
+
* socket) et le geste serveur (fermeture `1013`).
|
|
76
|
+
*
|
|
68
77
|
* @param ws - la socket destinataire
|
|
69
78
|
* @param max - seuil d'octets (`<= 0` → désactivé)
|
|
70
79
|
* @param policy - action au dépassement
|
|
80
|
+
* @param closeAfterDrops - solde de refus qui ferme (`0` = jamais)
|
|
71
81
|
* @returns `"send"` (émettre) · `"drop"` (sauter cette frame) · `"close"` (socket fermée)
|
|
72
82
|
*/
|
|
73
83
|
export declare const decideSend: (ws: IBackpressureTarget, max: number, policy: WsBackpressurePolicy, closeAfterDrops?: number) => WsSendDecision;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type BackpressureVerdict } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* Options de la borne de débit ENTRANTE (client → serveur), lues dans `wss.options`.
|
|
4
|
+
*
|
|
5
|
+
* Knobs Nodefony (pas des options `ws`), conservés par le spread du constructeur
|
|
6
|
+
* de `ws` — même voie que `IWsBackpressureOptions`.
|
|
7
|
+
*/
|
|
8
|
+
export interface IWsFrameRateOptions {
|
|
9
|
+
/** Trames reçues admises par seconde et par connexion ; `0`/absent = désactivée. */
|
|
10
|
+
maxFramesPerSecond?: number;
|
|
11
|
+
/** Solde de trames écartées qui ferme la connexion (`1008`) ; `0` = jamais. */
|
|
12
|
+
frameRateCloseAfterRejects?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fenêtre de comptage d'une connexion, créée à la PREMIÈRE trame reçue borne activée.
|
|
16
|
+
*
|
|
17
|
+
* Mutée en place : aucune allocation par trame.
|
|
18
|
+
*/
|
|
19
|
+
export interface IFrameRateWindow {
|
|
20
|
+
/** Début de la fenêtre courante (ms, `Date.now()`). */
|
|
21
|
+
start: number;
|
|
22
|
+
/** Trames reçues dans la fenêtre courante. */
|
|
23
|
+
count: number;
|
|
24
|
+
/** Solde de refus (+1 par trame écartée, −1 par trame admise). */
|
|
25
|
+
balance: number;
|
|
26
|
+
}
|
|
27
|
+
/** Durée d'une fenêtre de comptage (ms) : la borne s'exprime en trames PAR SECONDE. */
|
|
28
|
+
export declare const FRAME_RATE_WINDOW_MS = 1000;
|
|
29
|
+
/**
|
|
30
|
+
* Nombre cumulé de trames entrantes écartées par la borne de débit dans ce processus (unité : une trame reçue).
|
|
31
|
+
*
|
|
32
|
+
* Monotone, `0` tant que la borne est désactivée. Relayé par la sonde du hub
|
|
33
|
+
* temps réel sous le nom figé `frameRateRejectedTotal` (ADR-0021 §S9).
|
|
34
|
+
*
|
|
35
|
+
* @returns le cumul depuis le démarrage du processus
|
|
36
|
+
*/
|
|
37
|
+
export declare const frameRateRejectedTotal: () => number;
|
|
38
|
+
/**
|
|
39
|
+
* Borne de débit entrante du serveur WebSocket (`maxFramesPerSecond`) ; `0` = désactivée.
|
|
40
|
+
*
|
|
41
|
+
* Appelée à chaque trame : une lecture de propriété, aucune allocation.
|
|
42
|
+
*
|
|
43
|
+
* @param server - le serveur du contexte (`context.server`)
|
|
44
|
+
* @returns trames admises par seconde, `0` si la borne est désactivée
|
|
45
|
+
*/
|
|
46
|
+
export declare const readFrameRateBound: (server: unknown) => number;
|
|
47
|
+
/**
|
|
48
|
+
* Solde de refus qui ferme la connexion (`frameRateCloseAfterRejects`) ; `0` = jamais.
|
|
49
|
+
*
|
|
50
|
+
* @param server - le serveur du contexte (`context.server`)
|
|
51
|
+
* @returns le solde de fermeture
|
|
52
|
+
*/
|
|
53
|
+
export declare const readFrameRateCloseAfter: (server: unknown) => number;
|
|
54
|
+
/**
|
|
55
|
+
* Décide du sort d'une trame reçue, AVANT tout décodage : l'admettre, l'écarter, ou fermer la connexion.
|
|
56
|
+
*
|
|
57
|
+
* Compte des TRAMES, pas des octets (`maxPayload` borne déjà la taille). La
|
|
58
|
+
* décision est la règle unique du cœur (`decideBackpressure`), appliquée au
|
|
59
|
+
* nombre de trames de la fenêtre : écarter au-delà de la borne, fermer quand le
|
|
60
|
+
* solde de refus atteint `closeAfter` — jamais au premier dépassement. Une
|
|
61
|
+
* trame écartée est comptée dans {@link frameRateRejectedTotal}.
|
|
62
|
+
*
|
|
63
|
+
* @param win - la fenêtre de la connexion (mutée)
|
|
64
|
+
* @param now - l'instant de réception (ms)
|
|
65
|
+
* @param bound - trames admises par seconde (`> 0`)
|
|
66
|
+
* @param closeAfter - solde de refus qui ferme (`0` = jamais)
|
|
67
|
+
* @returns `"send"` (admettre), `"drop"` (écarter) ou `"close"` (fermer en `1008`)
|
|
68
|
+
*/
|
|
69
|
+
export declare const admitFrame: (win: IFrameRateWindow, now: number, bound: number, closeAfter: number) => BackpressureVerdict;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Formatage **pur** (0 état
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Formatage **pur** (0 état) du CONTENU d'un message WebSocket pour le log de trace
|
|
3
|
+
* (Suivi de requête Studio). Séparé de `WebsocketContext` pour être testable aux
|
|
4
|
+
* limites sans serveur.
|
|
5
5
|
*
|
|
6
6
|
* 🔒 Robustesse binaire (cf doc `ws` — `socket.send(data)` accepte
|
|
7
7
|
* `String | Number | Object | Buffer | ArrayBuffer | TypedArray | DataView |
|
|
8
8
|
* Buffer[] | Blob`, et l'event `message` livre `Buffer | ArrayBuffer | Buffer[]`).
|
|
9
|
-
* **Toute** charge binaire est résumée `[binary N B]` — JAMAIS
|
|
10
|
-
* (`JSON.stringify(new Uint8Array(...))` produirait `{"0":..,"1":..}`, énorme
|
|
11
|
-
* faux).
|
|
9
|
+
* **Toute** charge binaire est résumée `[binary N B]` — JAMAIS vidée octet par
|
|
10
|
+
* octet (`JSON.stringify(new Uint8Array(...))` produirait `{"0":..,"1":..}`, énorme
|
|
11
|
+
* et faux). Quand la connexion a négocié un codec de frame (CBOR, ADR-0022 §2), la
|
|
12
|
+
* trame est en plus DÉCODÉE pour être lisible : le débogage se fait à l'observation,
|
|
13
|
+
* jamais en changeant le fil.
|
|
12
14
|
*/
|
|
15
|
+
import { type IFrameCodec } from "nodefony";
|
|
13
16
|
/** Cap de troncature du contenu loggé (octets/chars). Borne ring + JSONL. */
|
|
14
17
|
export declare const WS_LOG_CONTENT_CAP = 4096;
|
|
15
18
|
/**
|
|
@@ -26,12 +29,17 @@ export declare function binaryByteLength(data: unknown): number;
|
|
|
26
29
|
/**
|
|
27
30
|
* Formate une charge utile WS en chaîne **bornée** et **sûre** pour le log :
|
|
28
31
|
* - `string` → tronquée à `cap` (+ ellipse) ;
|
|
29
|
-
* - binaire (cf {@link binaryByteLength}) → `[binary N B]` (jamais de
|
|
32
|
+
* - binaire (cf {@link binaryByteLength}) → `[binary N B]` (jamais de vidage) ;
|
|
33
|
+
* avec un `codec`, une trame d'un seul tenant est aussi décodée :
|
|
34
|
+
* `[binary N B] <protocole> <frame en JSON>`, octets imbriqués étiquetés ;
|
|
35
|
+
* illisible → `[binary N B] <protocole> illisible` ;
|
|
30
36
|
* - `null`/`undefined` → `""` ;
|
|
31
37
|
* - objet « JSON » → `JSON.stringify` compact tronqué (cycle, `bigint`, valeur
|
|
32
38
|
* non sérialisable → repli `String(...)`).
|
|
33
39
|
*
|
|
34
40
|
* @param data - charge utile (RECEIVE/SEND/BROADCAST).
|
|
35
41
|
* @param cap - longueur max avant troncature (défaut {@link WS_LOG_CONTENT_CAP}).
|
|
42
|
+
* @param codec - codec des trames binaires de la connexion, `null` = octets bruts.
|
|
43
|
+
* @returns la ligne à journaliser, jamais plus longue que `cap` + 1.
|
|
36
44
|
*/
|
|
37
|
-
export declare function formatWsLogContent(data: unknown, cap?: number): string;
|
|
45
|
+
export declare function formatWsLogContent(data: unknown, cap?: number, codec?: IFrameCodec | null): string;
|
|
@@ -19,7 +19,6 @@ declare module "http2" {
|
|
|
19
19
|
* externe nomme le type par `ICookieOptions`, exporté au barrel.
|
|
20
20
|
*/
|
|
21
21
|
export type CookieOptionsType = ICookieOptions;
|
|
22
|
-
declare function parser(strToParse: string): import("cookie").Cookies;
|
|
23
22
|
declare function cookiesParser(context: ContextType): void;
|
|
24
23
|
declare class Cookie implements ICookieInterface {
|
|
25
24
|
options: CookieOptionsType;
|
|
@@ -57,7 +56,26 @@ declare class Cookie implements ICookieInterface {
|
|
|
57
56
|
setExpires(date: Date | string | number | undefined): Date | undefined;
|
|
58
57
|
setOriginalMaxAge(ms: number | StringValue): number;
|
|
59
58
|
setPriority(val: PriorityType): PriorityType;
|
|
59
|
+
/**
|
|
60
|
+
* Durée de vie restante en SECONDES ENTIÈRES, `undefined` sous une seconde.
|
|
61
|
+
*
|
|
62
|
+
* RFC 6265 §4.1.1 : `max-age-av = "Max-Age=" non-zero-digit *DIGIT`. Un
|
|
63
|
+
* `Max-Age=3599.874` est ignoré par le navigateur, et le cookie devient un
|
|
64
|
+
* cookie de session. Arrondi INFÉRIEUR : le cookie ne survit jamais à son
|
|
65
|
+
* `Expires`.
|
|
66
|
+
*/
|
|
60
67
|
getMaxAge(): number | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Vérifie la grammaire RFC 6265 §4.1.1 du nom, du `Path` et du `Domain`,
|
|
70
|
+
* au moment où le cookie est POSÉ sur la réponse.
|
|
71
|
+
*
|
|
72
|
+
* Sans elle, `path: "/; Domain=evil.example"` injectait un attribut, et un
|
|
73
|
+
* nom porteur de `;` partait tel quel. La grammaire est celle du paquet
|
|
74
|
+
* `cookie` : une seule implémentation.
|
|
75
|
+
*
|
|
76
|
+
* @throws {TypeError} nommant l'attribut hors grammaire.
|
|
77
|
+
*/
|
|
78
|
+
assertGrammar(): void;
|
|
61
79
|
toString(): string;
|
|
62
80
|
/**
|
|
63
81
|
* Signe une valeur de cookie : `HMAC-SHA256(value)` clé = `secret`, encodé en
|
|
@@ -85,4 +103,4 @@ declare class Cookie implements ICookieInterface {
|
|
|
85
103
|
serializeWebSocket(): IWsCookie;
|
|
86
104
|
}
|
|
87
105
|
export default Cookie;
|
|
88
|
-
export { cookiesParser
|
|
106
|
+
export { cookiesParser };
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
import { nodefonyError as NodefonyError } from "nodefony";
|
|
2
2
|
import { ContextType } from "../../service/http-kernel.js";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Sérialise les propriétés propres d'une erreur HTTP, sans jamais LIRE celles
|
|
5
|
+
* qu'on écarte.
|
|
6
|
+
*
|
|
7
|
+
* Ne pas lire compte : `stack` est un accesseur paresseux de V8, et sa première
|
|
8
|
+
* lecture FORMATE la pile (`prepareStackTrace`, plusieurs µs). Lire puis
|
|
9
|
+
* supprimer, c'est payer ce formatage pour le jeter — ce que faisait le rendu
|
|
10
|
+
* d'erreur en production, sur chaque 404.
|
|
11
|
+
*
|
|
12
|
+
* @param error - l'erreur à sérialiser (en pratique un `HttpError`).
|
|
13
|
+
* @param omit - clés à écarter EN PLUS des références vivantes, ou `null`.
|
|
14
|
+
* @returns un objet plat, prêt pour `JSON.stringify`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function serializeHttpError(error: Error, omit?: ReadonlySet<string> | null): Record<string, unknown>;
|
|
4
17
|
declare class HttpError extends NodefonyError {
|
|
5
|
-
context?: ContextType | undefined;
|
|
6
|
-
response?: HttpRsponseType | undefined;
|
|
7
|
-
request?: HttpRequestType | undefined;
|
|
8
18
|
url?: string | undefined;
|
|
9
19
|
controller?: string | undefined;
|
|
10
20
|
action?: string | undefined;
|
|
11
21
|
jsonResponse?: string | undefined;
|
|
12
22
|
constructor(message?: unknown, code?: number, context?: ContextType);
|
|
23
|
+
/**
|
|
24
|
+
* Construit un VERDICT de protocole (404, 405, 426…) sans capturer de pile.
|
|
25
|
+
*
|
|
26
|
+
* La pile d'un verdict pointe toujours au même endroit du noyau ou du routeur
|
|
27
|
+
* et ne dit rien de la requête ; la capturer coûte ~3,6 µs à profondeur
|
|
28
|
+
* réelle (`micro-error-path.mjs`), payés par chaque requête d'un scanner.
|
|
29
|
+
* Réservé à ce que le framework DÉCIDE — une panne garde sa pile. `stack` ne
|
|
30
|
+
* porte que l'en-tête (`HttpError: Not Found`).
|
|
31
|
+
*
|
|
32
|
+
* @param message - message du verdict.
|
|
33
|
+
* @param code - statut HTTP (ou code de fermeture WebSocket).
|
|
34
|
+
* @param context - contexte de la requête, comme pour le constructeur.
|
|
35
|
+
* @returns l'erreur, sans pile.
|
|
36
|
+
*/
|
|
37
|
+
static withoutStack(message: string, code: number, context?: ContextType): HttpError;
|
|
13
38
|
toString(): string;
|
|
14
39
|
}
|
|
15
40
|
export default HttpError;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ce que les protocoles RPC servis (gRPC, gRPC-Web, Connect) demandent à la
|
|
3
|
+
* politique CORS de l'application (ADR-0026 D9).
|
|
4
|
+
*
|
|
5
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
6
|
+
*/
|
|
7
|
+
export interface IRpcCorsLists {
|
|
8
|
+
/** Méthodes que le préflight doit autoriser. */
|
|
9
|
+
readonly allowedMethods: readonly string[];
|
|
10
|
+
/** En-têtes de requête que le préflight doit autoriser. */
|
|
11
|
+
readonly allowedHeaders: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* En-têtes qu'une réponse RPC doit exposer au script de la page — sans eux,
|
|
14
|
+
* un client gRPC-Web ne lit ni le statut ni l'encodage de la réponse.
|
|
15
|
+
*/
|
|
16
|
+
readonly exposedHeaders: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `Access-Control-Expose-Headers` d'une réponse RPC, composé une fois au
|
|
20
|
+
* chargement.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RPC_EXPOSED_HEADERS: string;
|
|
23
|
+
/**
|
|
24
|
+
* Les listes CORS que demandent les protocoles RPC — dès qu'un service gRPC
|
|
25
|
+
* est déclaré dans le processus, `null` sinon. Le pare-feu les ajoute à la
|
|
26
|
+
* politique de l'application au démarrage (`onReady`) : le préflight ne
|
|
27
|
+
* connaît pas la route, l'union est la seule voie (ADR-0026 D9 point 2).
|
|
28
|
+
*
|
|
29
|
+
* @returns les listes, ou `null` sans service gRPC déclaré.
|
|
30
|
+
* @experimental Surface gRPC, ADR-0026 D11.
|
|
31
|
+
*/
|
|
32
|
+
export declare function rpcCorsLists(): IRpcCorsLists | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Borne par défaut d'un message gRPC, reçu ou émis : 4 Mio (ADR-0026 D4,
|
|
3
|
+
* points 1 et 2). C'est la borne de réception des implémentations de
|
|
4
|
+
* référence (`grpc-js`, `grpc-go`) ; l'adaptateur Connect, lui, n'en pose
|
|
5
|
+
* aucune (`limit-io.js` : `0xffffffff`). Seule source de cette valeur : le
|
|
6
|
+
* schéma de la section `grpc` et le point d'entrée gRPC la lisent ici.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GRPC_DEFAULT_MAX_MESSAGE_BYTES: number;
|
|
9
|
+
/**
|
|
10
|
+
* Plus grande borne qu'accepte l'adaptateur Connect pour `readMaxBytes` et
|
|
11
|
+
* `writeMaxBytes` (`limit-io.js#validateReadWriteMaxBytes`) : la longueur
|
|
12
|
+
* d'une enveloppe tient sur 32 bits.
|
|
13
|
+
*/
|
|
14
|
+
export declare const GRPC_MAX_MESSAGE_BYTES = 4294967295;
|