@nodefony/http 10.0.0-beta.4 → 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 -25
- package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
- package/dist/nodefony/config/config.js +6 -3
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +17 -3
- package/dist/nodefony/service/audit-logger.js +3 -17
- package/dist/nodefony/service/error-renderer.js +6 -4
- package/dist/nodefony/service/http-kernel.js +191 -40
- package/dist/nodefony/service/pretty-request-logger.js +11 -10
- 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/Context.js +56 -13
- package/dist/nodefony/src/context/http/ByteStream.js +359 -0
- package/dist/nodefony/src/context/http/HttpContext.js +75 -25
- 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 +28 -5
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +20 -1
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +4 -2
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +81 -20
- 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/FrameProfile.js +22 -0
- package/dist/nodefony/src/profiler/Profiler.js +25 -22
- package/dist/nodefony/src/profiler/aggregate.js +26 -6
- 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 +8 -0
- package/dist/types/nodefony/config/config.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +16 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +5 -1
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/error-renderer.d.ts +5 -4
- package/dist/types/nodefony/service/http-kernel.d.ts +65 -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/Context.d.ts +25 -1
- package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +60 -4
- 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 +17 -3
- package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
- package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
- package/dist/types/nodefony/src/context/transport.d.ts +17 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +41 -1
- 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/FrameProfile.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +27 -102
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +23 -106
- 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/diagnostics-channel.md +303 -0
- package/docs/index.md +14 -1
- package/docs/observabilite.md +94 -16
- package/docs/reverse-proxy.md +33 -19
- package/docs/servers.md +26 -15
- package/docs/session.md +3 -3
- package/docs/sse.md +27 -20
- package/package.json +16 -16
|
@@ -20,106 +20,12 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { ProfiledResolver } from "./FrameProfile.js";
|
|
22
22
|
import type { ProtocolName } from "../../interfaces/IContext.js";
|
|
23
|
-
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
durationMs: number | null;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Une requête SQL/NoSQL exécutée pendant la requête HTTP (SEAM ORM — futur).
|
|
33
|
-
*
|
|
34
|
-
* Non collecté aujourd'hui : quand `@nodefony/orm-core` arrivera, les adapters
|
|
35
|
-
* pousseront leurs requêtes dans un buffer per-request dev-only (via l'ALS
|
|
36
|
-
* `RequestContext.getRequestId()`, déjà en place) que `collect()` lira ici.
|
|
37
|
-
* Le champ `queries` reste donc `undefined` tant qu'aucun ORM ne pushe → 0 coût.
|
|
38
|
-
*/
|
|
39
|
-
export interface ProfileQuery {
|
|
40
|
-
/** Requête (SQL ou commande NoSQL), tronquée si volumineuse. */
|
|
41
|
-
sql: string;
|
|
42
|
-
/** Début relatif (`performance.now()`) — même horloge que les phases. */
|
|
43
|
-
startMs?: number | undefined;
|
|
44
|
-
/** Durée d'exécution en ms. */
|
|
45
|
-
durationMs: number;
|
|
46
|
-
/** Lignes affectées/retournées, si connu. */
|
|
47
|
-
rows?: number | undefined;
|
|
48
|
-
/** Connecteur émetteur (`drizzle`, `mongoose`…). */
|
|
49
|
-
connector?: string | undefined;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Traversée du firewall par cette requête — **ce qui était possible** (la zone :
|
|
53
|
-
* son nom, si elle est protégée, quels authenticators elle accepte) croisé avec
|
|
54
|
-
* **ce qui s'est passé** (quel maillon a résolu l'identité, l'issue, le motif
|
|
55
|
-
* d'un refus).
|
|
56
|
-
*
|
|
57
|
-
* Sans cela, une requête qui PASSE est invisible côté sécurité : le chemin de
|
|
58
|
-
* succès n'émet aucun événement d'audit (choix délibéré — le volume nominal
|
|
59
|
-
* n'est pas un signal). `undefined` hors zone firewall.
|
|
60
|
-
*/
|
|
61
|
-
export interface ProfileSecurity {
|
|
62
|
-
/** Nom de la zone traversée. */
|
|
63
|
-
zone: string | null;
|
|
64
|
-
/** La zone exige-t-elle une identité (`security: true`) ? */
|
|
65
|
-
protected: boolean;
|
|
66
|
-
/** Chaîne d'authenticators : `first` = le premier qui supporte, `all` = MFA. */
|
|
67
|
-
mode: string | null;
|
|
68
|
-
/** Authenticators que la zone accepte (ce qui était POSSIBLE). */
|
|
69
|
-
candidates: string[];
|
|
70
|
-
/** Authenticator qui a RÉELLEMENT résolu l'identité. */
|
|
71
|
-
authenticator: string | null;
|
|
72
|
-
/** `granted` · `anonymous` · `denied` · `failure` · `throttled` · `bypass` · `public`. */
|
|
73
|
-
outcome: string | null;
|
|
74
|
-
/** Motif du refus (`no_credentials`, `invalid_credentials`…). */
|
|
75
|
-
reason: string | null;
|
|
76
|
-
/** Rôles du token résolu (l'axe autorisation). */
|
|
77
|
-
roles: string[] | null;
|
|
78
|
-
}
|
|
79
|
-
/** Profil complet d'une requête (HTTP ou message WS), exposé par `get`. */
|
|
80
|
-
export interface ProfileEntry {
|
|
81
|
-
requestId: string;
|
|
82
|
-
/**
|
|
83
|
-
* Numéro d'ordre de collecte, croissant sur la vie du profileur. Un profil
|
|
84
|
-
* réinséré (identifiant réutilisé) reçoit un numéro neuf.
|
|
85
|
-
*/
|
|
86
|
-
seq: number;
|
|
87
|
-
/** Horodatage de la collecte (`Date.now()`), pour le tri client. */
|
|
88
|
-
ts: number;
|
|
89
|
-
kind: "http" | "ws";
|
|
90
|
-
method: string | null;
|
|
91
|
-
url: string;
|
|
92
|
-
scheme: string;
|
|
93
|
-
status: number | null;
|
|
94
|
-
/** Durée totale serveur (ms), dérivée des phases. */
|
|
95
|
-
durationMs: number | null;
|
|
96
|
-
route: string | null;
|
|
97
|
-
controller: string | null;
|
|
98
|
-
action: string | null;
|
|
99
|
-
remoteAddress: string | null;
|
|
100
|
-
/** Identité (username) si le firewall l'a injectée, sinon `null`. */
|
|
101
|
-
user: string | null;
|
|
102
|
-
/** `traceparent` W3C Trace Context (P2.7) — corrélation distribuée RFC-propre. */
|
|
103
|
-
traceparent: string | null;
|
|
104
|
-
error: string | null;
|
|
105
|
-
phases: ProfilePhase[];
|
|
106
|
-
/** Requêtes ORM (SEAM futur — `undefined` tant qu'aucun adapter ne pushe). */
|
|
107
|
-
queries?: ProfileQuery[] | undefined;
|
|
108
|
-
/** Traversée du firewall — `undefined` si la requête n'a croisé aucune zone. */
|
|
109
|
-
security?: ProfileSecurity | undefined;
|
|
110
|
-
}
|
|
111
|
-
/** Résumé léger d'un profil pour la liste `recent` (sans les phases). */
|
|
112
|
-
export interface ProfileSummary {
|
|
113
|
-
requestId: string;
|
|
114
|
-
ts: number;
|
|
115
|
-
kind: "http" | "ws";
|
|
116
|
-
method: string | null;
|
|
117
|
-
url: string;
|
|
118
|
-
status: number | null;
|
|
119
|
-
durationMs: number | null;
|
|
120
|
-
route: string | null;
|
|
121
|
-
error: string | null;
|
|
122
|
-
}
|
|
23
|
+
import type { IProfileEntry, IProfilePhase, IProfileQuery, IProfileSecurity, IProfileSummary } from "nodefony";
|
|
24
|
+
export type ProfilePhase = IProfilePhase;
|
|
25
|
+
export type ProfileQuery = IProfileQuery;
|
|
26
|
+
export type ProfileSecurity = IProfileSecurity;
|
|
27
|
+
export type ProfileEntry = IProfileEntry;
|
|
28
|
+
export type ProfileSummary = IProfileSummary;
|
|
123
29
|
/** Forme structurelle minimale lue sur un `Context` (lecture défensive). */
|
|
124
30
|
interface ProfilableContext {
|
|
125
31
|
requestId?: string | undefined;
|
|
@@ -142,6 +48,11 @@ interface ProfilableContext {
|
|
|
142
48
|
durationMs?: number | undefined;
|
|
143
49
|
}> | undefined;
|
|
144
50
|
resolver?: ProfiledResolver | null | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Arrête l'horloge de la requête et rend sa durée (ms) — la même que le
|
|
53
|
+
* journal d'accès (`IContext.stopClock`). `null` si `timing.enabled` vaut `false`.
|
|
54
|
+
*/
|
|
55
|
+
stopClock(): number | null;
|
|
145
56
|
/**
|
|
146
57
|
* Buffer de requêtes ORM rempli pendant la requête (dev-only). Même tableau
|
|
147
58
|
* que la payload ALS — les adapters ORM y poussent via `RequestContext`.
|
|
@@ -205,6 +116,14 @@ export declare class Profiler implements IProfilerReader {
|
|
|
205
116
|
private readonly _caps;
|
|
206
117
|
/** Dernier numéro d'ordre attribué (0 = aucun profil collecté). */
|
|
207
118
|
private _seq;
|
|
119
|
+
/**
|
|
120
|
+
* Démarrage du profileur, donc du processus (ms epoch, horloge du serveur) :
|
|
121
|
+
* aucune requête servie avant n'est connue. Rendu avec un profil absent, pour
|
|
122
|
+
* que le lecteur dise « le serveur a redémarré » plutôt que « évincé ».
|
|
123
|
+
*/
|
|
124
|
+
readonly startedAt: number;
|
|
125
|
+
/** Dernier vidage des anneaux (ms epoch), `null` s'il n'y en a jamais eu. */
|
|
126
|
+
private _clearedAt;
|
|
208
127
|
/**
|
|
209
128
|
* @param cap - capacité de l'anneau de l'application.
|
|
210
129
|
* @param sideCap - capacité de chacun des anneaux de l'outillage et des
|
|
@@ -222,8 +141,12 @@ export declare class Profiler implements IProfilerReader {
|
|
|
222
141
|
get(requestId: string): ProfileEntry | undefined;
|
|
223
142
|
/**
|
|
224
143
|
* Résumés des requêtes les plus récentes (récent → ancien), capés à `limit`.
|
|
144
|
+
*
|
|
145
|
+
* @param limit - nombre maximal de résumés
|
|
146
|
+
* @param connection - le requestId d'une connexion WebSocket : ses SEULES
|
|
147
|
+
* frames du pont (`<connection>.<n>`, règle `frameIndexOf` du cœur)
|
|
225
148
|
*/
|
|
226
|
-
recent(limit?: number): ProfileSummary[];
|
|
149
|
+
recent(limit?: number, connection?: string): ProfileSummary[];
|
|
227
150
|
/** Dernier numéro d'ordre attribué : le point de départ d'une lecture suivie. */
|
|
228
151
|
get cursor(): number;
|
|
229
152
|
/**
|
|
@@ -240,7 +163,9 @@ export declare class Profiler implements IProfilerReader {
|
|
|
240
163
|
since(cursor: number): IProfileBatch;
|
|
241
164
|
/** Nombre de profils en mémoire. */
|
|
242
165
|
get size(): number;
|
|
243
|
-
/** Vide les anneaux. */
|
|
166
|
+
/** Vide les anneaux, et retient l'instant du vidage ({@link clearedAt}). */
|
|
244
167
|
clear(): void;
|
|
168
|
+
/** Dernier vidage des anneaux (ms epoch, horloge du serveur), `null` si aucun. */
|
|
169
|
+
get clearedAt(): number | null;
|
|
245
170
|
}
|
|
246
171
|
export default Profiler;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ProfileEntry } from "./Profiler.js";
|
|
2
|
+
import type { IStatusBreakdown, IPhaseTotal, IRouteStats, INPlusOne, ISqlShapeStats, IErrorSample, IProfileReport } from "nodefony";
|
|
3
|
+
export type { IStatusBreakdown, IPhaseTotal, IRouteStats, INPlusOne, ISqlShapeStats, IErrorSample, IProfileReport, };
|
|
2
4
|
/**
|
|
3
5
|
* Agrégation des profils de requêtes — règle PURE, sans horloge ni I/O.
|
|
4
6
|
*
|
|
@@ -21,7 +23,14 @@ export declare const ROUTE_SAMPLE_CAP = 256;
|
|
|
21
23
|
export declare const SQL_SHAPE_CAP = 200;
|
|
22
24
|
/** Exemples d'erreurs retenus (statut ≥ 500 ou exception). */
|
|
23
25
|
export declare const ERROR_SAMPLE_CAP = 10;
|
|
24
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Préfixe de l'espace réservé au framework.
|
|
28
|
+
*
|
|
29
|
+
* @deprecated Ne dit pas ce qui est de l'outillage : une connexion
|
|
30
|
+
* d'utilisateur (`/nodefony/security/api/auth/…`) ou une route d'application
|
|
31
|
+
* (`/nodefony/test/index`) passent sous ce préfixe. La règle est
|
|
32
|
+
* `isConsolePath` (`nodefony`), que `profileLane` appelle.
|
|
33
|
+
*/
|
|
25
34
|
export declare const TOOLING_URL_PREFIX = "/nodefony/";
|
|
26
35
|
/**
|
|
27
36
|
* Provenance d'un profil : l'application, l'outillage (`/nodefony/…` :
|
|
@@ -30,8 +39,10 @@ export declare const TOOLING_URL_PREFIX = "/nodefony/";
|
|
|
30
39
|
*/
|
|
31
40
|
export type ProfileLane = "app" | "tooling" | "asset";
|
|
32
41
|
/**
|
|
33
|
-
* Range un profil par provenance, d'après son URL.
|
|
34
|
-
*
|
|
42
|
+
* Range un profil par provenance, d'après son URL. L'outillage est reconnu par
|
|
43
|
+
* la règle du cœur, `isConsolePath` — la même que les Journaux, la barre de
|
|
44
|
+
* débogage et le Studio : une connexion d'utilisateur reste de l'application.
|
|
45
|
+
* Les anneaux du profileur et le filtre de l'agrégation la lisent tous deux.
|
|
35
46
|
*
|
|
36
47
|
* @param url - URL du profil, absolue ou chemin.
|
|
37
48
|
* @returns la provenance du profil.
|
|
@@ -44,113 +55,19 @@ export declare function profileLane(url: string): ProfileLane;
|
|
|
44
55
|
* @param url - URL du profil, absolue ou chemin.
|
|
45
56
|
*/
|
|
46
57
|
export declare function isToolingUrl(url: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Une requête de cette URL se profile-t-elle ? L'application toujours ;
|
|
60
|
+
* l'outillage et les assets relayés seulement sur demande (`timing.tooling`)
|
|
61
|
+
* — on ne mesure pas la façon dont on mesure.
|
|
62
|
+
*
|
|
63
|
+
* @param url - URL ou chemin de la requête (ou de la frame du pont)
|
|
64
|
+
* @param tooling - profiler aussi l'outillage
|
|
65
|
+
*/
|
|
66
|
+
export declare function isProfiledUrl(url: string, tooling: boolean): boolean;
|
|
47
67
|
/** Groupe des requêtes qu'aucune route n'a servies (404, statique, sondes). */
|
|
48
68
|
export declare const NO_ROUTE = "(aucune route)";
|
|
49
69
|
/** Pseudo-phase : le temps de l'action hors phases imbriquées et hors SQL. */
|
|
50
70
|
export declare const ACTION_SELF_PHASE = "action.self";
|
|
51
|
-
/** Répartition des statuts HTTP d'une route. */
|
|
52
|
-
export interface IStatusBreakdown {
|
|
53
|
-
s2xx: number;
|
|
54
|
-
s3xx: number;
|
|
55
|
-
s4xx: number;
|
|
56
|
-
s5xx: number;
|
|
57
|
-
/** Dont 401 : la route exige une authentification que la requête n'avait pas. */
|
|
58
|
-
unauthorized: number;
|
|
59
|
-
/** Dont 403 : authentifiée, mais refusée. */
|
|
60
|
-
forbidden: number;
|
|
61
|
-
}
|
|
62
|
-
/** Temps cumulé d'une phase sur les requêtes d'une route. */
|
|
63
|
-
export interface IPhaseTotal {
|
|
64
|
-
totalMs: number;
|
|
65
|
-
/** Requêtes où la phase est apparue et s'est terminée. */
|
|
66
|
-
count: number;
|
|
67
|
-
}
|
|
68
|
-
/** Statistiques d'une route sur la fenêtre agrégée. */
|
|
69
|
-
export interface IRouteStats {
|
|
70
|
-
route: string;
|
|
71
|
-
kind: "http" | "ws";
|
|
72
|
-
controller: string | null;
|
|
73
|
-
action: string | null;
|
|
74
|
-
count: number;
|
|
75
|
-
/** Requêtes sans durée mesurée (mesure des phases désactivée, erreur précoce). */
|
|
76
|
-
untimed: number;
|
|
77
|
-
/**
|
|
78
|
-
* Requêtes SERVIES et chronométrées (statut < 400, sans exception) : la
|
|
79
|
-
* population de tout ce qui se dit « par requête » ci-dessous. Refus et
|
|
80
|
-
* erreurs se lisent dans `status` et `errors`.
|
|
81
|
-
*/
|
|
82
|
-
served: number;
|
|
83
|
-
/** Percentiles et maximum, sur les requêtes servies. */
|
|
84
|
-
p50Ms: number | null;
|
|
85
|
-
p95Ms: number | null;
|
|
86
|
-
maxMs: number | null;
|
|
87
|
-
status: IStatusBreakdown;
|
|
88
|
-
/** Requêtes en erreur : statut ≥ 500 ou exception levée. */
|
|
89
|
-
errors: number;
|
|
90
|
-
/**
|
|
91
|
-
* Requêtes SQL/NoSQL des requêtes servies : total, maximum dans une
|
|
92
|
-
* requête, temps CUMULÉ (des requêtes parallèles s'y additionnent).
|
|
93
|
-
*/
|
|
94
|
-
queries: {
|
|
95
|
-
total: number;
|
|
96
|
-
perRequestMax: number;
|
|
97
|
-
totalMs: number;
|
|
98
|
-
};
|
|
99
|
-
/** Temps par phase des requêtes servies, `action.self` compris. */
|
|
100
|
-
phases: Record<string, IPhaseTotal>;
|
|
101
|
-
}
|
|
102
|
-
/** Un N+1 : la même forme SQL répétée dans une requête. */
|
|
103
|
-
export interface INPlusOne {
|
|
104
|
-
route: string;
|
|
105
|
-
controller: string | null;
|
|
106
|
-
action: string | null;
|
|
107
|
-
shape: string;
|
|
108
|
-
/** Plus grand nombre de répétitions vu dans une requête. */
|
|
109
|
-
maxRepeats: number;
|
|
110
|
-
/** Requêtes touchées. */
|
|
111
|
-
requests: number;
|
|
112
|
-
/** Temps cumulé de ces répétitions. */
|
|
113
|
-
totalMs: number;
|
|
114
|
-
/** Une requête touchée, pour aller voir son profil complet. */
|
|
115
|
-
sampleRequestId: string;
|
|
116
|
-
}
|
|
117
|
-
/** Une forme SQL et son coût sur la fenêtre. */
|
|
118
|
-
export interface ISqlShapeStats {
|
|
119
|
-
shape: string;
|
|
120
|
-
connector: string | null;
|
|
121
|
-
count: number;
|
|
122
|
-
totalMs: number;
|
|
123
|
-
maxMs: number;
|
|
124
|
-
/** Routes qui l'ont émise (au plus cinq). */
|
|
125
|
-
routes: string[];
|
|
126
|
-
}
|
|
127
|
-
/** Un exemple d'erreur, de quoi la retrouver dans le journal. */
|
|
128
|
-
export interface IErrorSample {
|
|
129
|
-
route: string;
|
|
130
|
-
status: number | null;
|
|
131
|
-
error: string | null;
|
|
132
|
-
requestId: string;
|
|
133
|
-
}
|
|
134
|
-
/** Le rapport agrégé. */
|
|
135
|
-
export interface IProfileReport {
|
|
136
|
-
/** Requêtes agrégées (hors outillage filtré). */
|
|
137
|
-
requests: number;
|
|
138
|
-
http: number;
|
|
139
|
-
ws: number;
|
|
140
|
-
/** Requêtes de l'outillage écartées (`/nodefony/…`, `/_vite/…`). */
|
|
141
|
-
filteredTooling: number;
|
|
142
|
-
/** Requêtes sans durée mesurée. */
|
|
143
|
-
untimed: number;
|
|
144
|
-
/** Triées par p95 décroissant. */
|
|
145
|
-
routes: IRouteStats[];
|
|
146
|
-
/** Triés par temps cumulé décroissant. */
|
|
147
|
-
nPlusOne: INPlusOne[];
|
|
148
|
-
/** Les dix formes SQL les plus coûteuses (temps cumulé décroissant). */
|
|
149
|
-
slowQueries: ISqlShapeStats[];
|
|
150
|
-
/** Formes SQL distinctes vues au-delà de la capacité, comptées sans détail. */
|
|
151
|
-
sqlShapesDropped: number;
|
|
152
|
-
errors: IErrorSample[];
|
|
153
|
-
}
|
|
154
71
|
/** Réglages d'une agrégation. */
|
|
155
72
|
export interface IProfileAggregatorOptions {
|
|
156
73
|
/** Répétitions à partir desquelles une forme SQL est un N+1. */
|
|
@@ -55,6 +55,19 @@ export declare const MAX_VIA_HOPS = 10;
|
|
|
55
55
|
* @param pseudonym - pseudonyme de ce processus
|
|
56
56
|
*/
|
|
57
57
|
export declare function viaLoops(via: string | string[] | undefined, pseudonym: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* `Max-Forwards` reçu, tel qu'un intermédiaire doit le lire (RFC 9110 §7.6.2) :
|
|
60
|
+
* sur `OPTIONS` et `TRACE` seulement — la même section permet de l'ignorer
|
|
61
|
+
* pour toute autre méthode, et il part alors tel quel.
|
|
62
|
+
*
|
|
63
|
+
* @param method - méthode de la requête
|
|
64
|
+
* @param value - en-tête `Max-Forwards` reçu
|
|
65
|
+
* @returns `null` s'il n'y a rien à faire (autre méthode, en-tête absent) ;
|
|
66
|
+
* `-1` si la valeur n'est pas `1*DIGIT` — elle ne se décrémente pas, l'en-tête
|
|
67
|
+
* n'est donc pas transmis ; sinon la valeur reçue (`0` : le proxy répond
|
|
68
|
+
* lui-même, sans joindre l'amont).
|
|
69
|
+
*/
|
|
70
|
+
export declare function receivedMaxForwards(method: string, value: string | string[] | undefined): number | null;
|
|
58
71
|
/**
|
|
59
72
|
* Cadrage du corps transmis à l'amont (RFC 9112 §6). `unsupported` : un
|
|
60
73
|
* codage de transfert autre que `chunked` seul — le proxy ne sait pas le
|
|
@@ -118,8 +131,9 @@ export declare function upstreamPath(url: string, mount: IProxyMount): string;
|
|
|
118
131
|
* Relaie une requête HTTP vers l'amont et renvoie sa réponse au client.
|
|
119
132
|
*
|
|
120
133
|
* Le corps est transmis avec un cadrage cohérent ({@link bodyFramingOf}). Un
|
|
121
|
-
* message qui porte déjà notre `Via` rend 508 sans contacter l'amont.
|
|
122
|
-
*
|
|
134
|
+
* message qui porte déjà notre `Via` rend 508 sans contacter l'amont. Sur
|
|
135
|
+
* `OPTIONS` et `TRACE`, `Max-Forwards` est décrémenté ; à `0`, le proxy répond
|
|
136
|
+
* lui-même en destinataire final (RFC 9110 §7.6.2). Un client parti coupe l'échange amont ; un amont injoignable rend 502 ; une
|
|
123
137
|
* connexion non établie à temps ou un amont muet rendent 504 — à condition
|
|
124
138
|
* que rien n'ait encore été envoyé, sinon la réponse est interrompue (un
|
|
125
139
|
* statut ne se rattrape pas). Un amont coupé en plein corps interrompt la
|
|
@@ -154,6 +168,20 @@ export declare function rawHead(status: string, rawHeaders: readonly string[], s
|
|
|
154
168
|
* @param headers - en-têtes du refus (`WWW-Authenticate`, `Retry-After`…)
|
|
155
169
|
*/
|
|
156
170
|
export declare function refuseUpgrade(socket: Duplex, status: number, reason: string, headers?: Readonly<Record<string, string | number>> | null): void;
|
|
171
|
+
/**
|
|
172
|
+
* Refuse un handshake WebSocket non conforme — la réponse qui convient à la
|
|
173
|
+
* raison rendue par `websocketHandshakeProblem`, seule implémentation pour le
|
|
174
|
+
* serveur WebSocket ET le proxy inverse.
|
|
175
|
+
*
|
|
176
|
+
* Une version inconnue reçoit `426` avec `Sec-WebSocket-Version: 13` : le
|
|
177
|
+
* client apprend vers quelle version se rabattre (RFC 6455 §4.2.2 point 6,
|
|
178
|
+
* §4.4), et le 426 porte l'`Upgrade` qu'exige RFC 9110 §15.5.22. Tout autre
|
|
179
|
+
* défaut de forme reçoit `400`.
|
|
180
|
+
*
|
|
181
|
+
* @param socket - socket du client, encore en attente de sa réponse
|
|
182
|
+
* @param problem - la raison rendue par `websocketHandshakeProblem`
|
|
183
|
+
*/
|
|
184
|
+
export declare function refuseMalformedHandshake(socket: Duplex, problem: string): void;
|
|
157
185
|
/**
|
|
158
186
|
* Relaie un upgrade WebSocket DÉJÀ validé (méthode, `Upgrade`, clé, version,
|
|
159
187
|
* `Origin` — cf `ReverseProxy.handleUpgrade`) : rejoue le handshake vers
|
|
@@ -73,6 +73,26 @@ export declare function proxyMountProblems(prefix: string, mount: IProxyMountSha
|
|
|
73
73
|
* @returns `true` si le chemin doit être refusé
|
|
74
74
|
*/
|
|
75
75
|
export declare function isAmbiguousPath(url: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Le corps reçu porte-t-il un codage de transfert que le serveur ne sait pas
|
|
78
|
+
* défaire ? Seul `chunked` est compris : Node le retire, mais laisse en place
|
|
79
|
+
* tout codage qui le précède (`gzip, chunked`) — le corps livré serait encore
|
|
80
|
+
* compressé. RFC 9112 §6.1 : un tel message reçoit 501. Un `chunked` qui n'est
|
|
81
|
+
* pas le DERNIER codage est déjà refusé par Node en 400 (§6.3).
|
|
82
|
+
*
|
|
83
|
+
* Seule règle, pour le serveur d'origine et pour le proxy inverse. Chemin de
|
|
84
|
+
* chaque requête : une lecture d'en-tête, rien d'autre sans `Transfer-Encoding`.
|
|
85
|
+
*
|
|
86
|
+
* @param coding - valeur de l'en-tête `Transfer-Encoding`, absente le plus souvent
|
|
87
|
+
* @returns `true` quand le corps ne peut pas être lu tel qu'annoncé
|
|
88
|
+
*/
|
|
89
|
+
export declare function isUnsupportedTransferCoding(coding: string | undefined): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Raison rendue par {@link websocketHandshakeProblem} quand le client parle une
|
|
92
|
+
* autre version du protocole : la seule qui appelle une réponse distincte —
|
|
93
|
+
* le client doit apprendre vers quelle version se rabattre (RFC 6455 §4.4).
|
|
94
|
+
*/
|
|
95
|
+
export declare const WS_UNSUPPORTED_VERSION = "Sec-WebSocket-Version \u2260 13";
|
|
76
96
|
/**
|
|
77
97
|
* Un upgrade est-il un handshake WebSocket conforme (RFC 6455 §4.2.1) ? Seul
|
|
78
98
|
* un tel handshake est relayé : un `Upgrade` quelconque (`h2c`…) raccordé
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type IncomingMessage } from "node:http";
|
|
2
2
|
import type { Duplex } from "node:stream";
|
|
3
|
-
import type
|
|
3
|
+
import { WebSocketServer, type ServerOptions, type WebSocket } from "ws";
|
|
4
4
|
/** Serveur qui émet `upgrade` : HTTP/1.1, HTTPS ou HTTP/2 sécurisé (`allowHTTP1`). */
|
|
5
5
|
interface IUpgradeEmitter {
|
|
6
6
|
on(event: "upgrade", listener: (req: IncomingMessage, socket: Duplex, head: Buffer) => void): unknown;
|
|
@@ -26,6 +26,18 @@ export interface IUpgradeAdmission {
|
|
|
26
26
|
* le handshake) — il n'y a alors rien à servir
|
|
27
27
|
*/
|
|
28
28
|
accept(): WebSocket | null | Promise<WebSocket | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Retient le sous-protocole que le serveur renverra dans le `101` (RFC 6455
|
|
31
|
+
* §4.2.2 : « the subprotocol the server is ready to use »), à appeler AVANT
|
|
32
|
+
* {@link accept}. Sans choix, la règle d'avant s'applique : l'option
|
|
33
|
+
* `handleProtocols` de la configuration `ws`, sinon le premier offert.
|
|
34
|
+
*
|
|
35
|
+
* Un choix absent de l'offre du client n'est jamais renvoyé — la norme ne
|
|
36
|
+
* permet que l'un des sous-protocoles offerts, ou aucun.
|
|
37
|
+
*
|
|
38
|
+
* @param protocol - le sous-protocole choisi, `false` pour n'en renvoyer aucun
|
|
39
|
+
*/
|
|
40
|
+
selectSubprotocol(protocol: string | false): void;
|
|
29
41
|
/**
|
|
30
42
|
* Refuse par un statut HTTP, AVANT le `101` (RFC 6455 §4.2.2).
|
|
31
43
|
*
|
|
@@ -36,6 +48,34 @@ export interface IUpgradeAdmission {
|
|
|
36
48
|
}
|
|
37
49
|
/** Ce qui décide d'un upgrade : reçoit la requête et rend son verdict, plus tard s'il le faut. */
|
|
38
50
|
export type UpgradeAdmit = (req: IncomingMessage, admission: IUpgradeAdmission) => void;
|
|
51
|
+
/** Signature de l'option `handleProtocols` de `ws` (`ServerOptions`). */
|
|
52
|
+
export type SubprotocolHandler = (protocols: Set<string>, request: IncomingMessage) => string | false;
|
|
53
|
+
/**
|
|
54
|
+
* Fabrique l'option `handleProtocols` du serveur `ws` de Nodefony : le choix
|
|
55
|
+
* retenu par l'admission ({@link IUpgradeAdmission.selectSubprotocol}) s'il y
|
|
56
|
+
* en a un, sinon `fallback` (celle de la configuration), sinon le premier
|
|
57
|
+
* offert — le défaut de `ws` 8, conservé pour tout contrôleur qui ne choisit
|
|
58
|
+
* pas.
|
|
59
|
+
*
|
|
60
|
+
* @param fallback - l'option `handleProtocols` de la configuration, s'il y en a une
|
|
61
|
+
* @returns le rappel à passer à `new WebSocketServer({ handleProtocols })`
|
|
62
|
+
*/
|
|
63
|
+
export declare function createSubprotocolHandler(fallback?: SubprotocolHandler): SubprotocolHandler;
|
|
64
|
+
/**
|
|
65
|
+
* Construit le serveur `ws` de Nodefony à partir des options de la
|
|
66
|
+
* configuration, en forçant ce que Nodefony gère — seul endroit où ces
|
|
67
|
+
* options sont posées, pour le serveur clair comme pour le sécurisé :
|
|
68
|
+
*
|
|
69
|
+
* - `noServer` : l'upgrade passe d'abord par le proxy inverse, puis par
|
|
70
|
+
* {@link attachUpgradeDispatch} ;
|
|
71
|
+
* - `clientTracking` : requis par `broadcast()` et le heartbeat ;
|
|
72
|
+
* - `handleProtocols` : le sous-protocole CHOISI par le contrôleur
|
|
73
|
+
* ({@link createSubprotocolHandler}), celui de la configuration à défaut.
|
|
74
|
+
*
|
|
75
|
+
* @param options - options `ws` issues de la configuration du serveur WebSocket
|
|
76
|
+
* @returns le serveur, sans écoute propre
|
|
77
|
+
*/
|
|
78
|
+
export declare function createNodefonyWebSocketServer(options: ServerOptions): WebSocketServer;
|
|
39
79
|
/**
|
|
40
80
|
* Pose l'UNIQUE écouteur `upgrade` d'un serveur : le proxy inverse d'abord,
|
|
41
81
|
* puis le serveur WebSocket de Nodefony.
|