@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +13 -10
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +3 -3
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -2
- package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
- package/dist/nodefony/service/audit-logger.js +23 -30
- package/dist/nodefony/service/certificates.js +97 -13
- package/dist/nodefony/service/error-renderer.js +29 -12
- package/dist/nodefony/service/http-kernel.js +198 -50
- package/dist/nodefony/service/pretty-request-logger.js +15 -13
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +3 -3
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +3 -3
- package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
- package/dist/nodefony/service/servers/server-websocket.js +10 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
- package/dist/nodefony/service/sessions/sessions-service.js +7 -14
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +78 -43
- package/dist/nodefony/src/context/domainMatcher.js +1 -2
- package/dist/nodefony/src/context/forwarded.js +1 -2
- package/dist/nodefony/src/context/http/HttpContext.js +28 -6
- package/dist/nodefony/src/context/http/Request.js +33 -29
- package/dist/nodefony/src/context/http/Response.js +23 -6
- package/dist/nodefony/src/context/http/SseStream.js +1 -2
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +41 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/responseEnded.js +1 -2
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +33 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
- package/dist/nodefony/src/profiler/Profiler.js +104 -32
- package/dist/nodefony/src/profiler/aggregate.js +442 -0
- package/dist/nodefony/src/proxy/forward.js +29 -5
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +12 -6
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
- package/dist/nodefony/src/session/session.js +2 -3
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +9 -3
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- 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 +26 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/certificates.d.ts +55 -4
- package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/src/context/Context.d.ts +38 -5
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
- 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 +53 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
- package/docs/cookies.md +85 -78
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +6 -1
- package/docs/observabilite.md +159 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +72 -70
- package/docs/servers.md +338 -224
- package/docs/session.md +179 -179
- package/docs/sse.md +24 -20
- package/docs/upload.md +120 -117
- package/package.json +8 -9
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import HttpError from "../src/errors/httpError.js";
|
|
2
2
|
import { toWsCloseCode } from "../src/context/websocket/WebsocketContext.js";
|
|
3
3
|
import { Nodefony, nodefonyError } from "nodefony";
|
|
4
|
-
|
|
4
|
+
// nodefony/service/error-renderer.ts
|
|
5
5
|
/** Une donnée rejetée par la validation n'est pas une requête malformée : 422, pas 400. */
|
|
6
6
|
const VALIDATION_STATUS = 422;
|
|
7
7
|
/**
|
|
@@ -74,8 +74,15 @@ const INTERNAL_ERROR_KEYS = [
|
|
|
74
74
|
* deux tests — un gate qu'on ne peut plus voir mordre.
|
|
75
75
|
*/
|
|
76
76
|
const isProduction = () => Nodefony.getKernel()?.environment === "production";
|
|
77
|
+
/** Une propriété d'une anomalie de forme inconnue, lue sans rien supposer. */
|
|
78
|
+
function readIssueProperty(issue, key) {
|
|
79
|
+
return typeof issue === "object" && issue !== null ? Reflect.get(issue, key) : void 0;
|
|
80
|
+
}
|
|
81
|
+
/** Erreurs de validation reconnues : Zod, et celle des routes à schéma (cœur). */
|
|
82
|
+
const VALIDATION_ERROR_NAMES = /* @__PURE__ */ new Set(["ZodError", "SchemaValidationError"]);
|
|
77
83
|
/**
|
|
78
|
-
* Reconnaît une erreur de validation
|
|
84
|
+
* Reconnaît une erreur de validation — `ZodError`, ou `SchemaValidationError`
|
|
85
|
+
* d'une route à schéma — et en extrait les champs fautifs.
|
|
79
86
|
*
|
|
80
87
|
* Reconnaissance **structurelle** (`name` + `issues`), pas `instanceof ZodError` :
|
|
81
88
|
* une application peut embarquer sa propre copie de zod (résolutions npm multiples),
|
|
@@ -88,13 +95,21 @@ const isProduction = () => Nodefony.getKernel()?.environment === "production";
|
|
|
88
95
|
* @returns les champs fautifs, ou `null` si ce n'est pas une erreur de validation.
|
|
89
96
|
*/
|
|
90
97
|
function toValidationFields(error) {
|
|
91
|
-
const
|
|
92
|
-
if (error.name
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
message
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
const issues = Reflect.get(error, "issues");
|
|
99
|
+
if (!VALIDATION_ERROR_NAMES.has(error.name) || !Array.isArray(issues)) return null;
|
|
100
|
+
return issues.map((issue) => {
|
|
101
|
+
const path = readIssueProperty(issue, "path");
|
|
102
|
+
const message = readIssueProperty(issue, "message");
|
|
103
|
+
const code = readIssueProperty(issue, "code");
|
|
104
|
+
const part = readIssueProperty(issue, "in");
|
|
105
|
+
const field = {
|
|
106
|
+
field: Array.isArray(path) ? path.map(String).join(".") : "",
|
|
107
|
+
message: typeof message === "string" ? message : "invalide",
|
|
108
|
+
rule: typeof code === "string" ? code : void 0
|
|
109
|
+
};
|
|
110
|
+
if (typeof part === "string") field.in = part;
|
|
111
|
+
return field;
|
|
112
|
+
});
|
|
98
113
|
}
|
|
99
114
|
/**
|
|
100
115
|
* Construit l'erreur **422** exposée au client à partir des champs fautifs.
|
|
@@ -109,7 +124,10 @@ function toValidationFields(error) {
|
|
|
109
124
|
* reçoit `error.fields` et sait QUEL champ corriger — pas seulement que « ça a échoué ».
|
|
110
125
|
*/
|
|
111
126
|
function toValidationError(error, fields) {
|
|
112
|
-
const summary = fields.map((f) =>
|
|
127
|
+
const summary = fields.map((f) => {
|
|
128
|
+
const where = f.in && f.field ? `${f.in}.${f.field}` : f.in || f.field;
|
|
129
|
+
return where ? `${where}: ${f.message}` : f.message;
|
|
130
|
+
}).join(" · ");
|
|
113
131
|
const validation = new nodefonyError(`Validation failed — ${summary}`, VALIDATION_STATUS);
|
|
114
132
|
if (error.stack !== void 0) validation.stack = error.stack;
|
|
115
133
|
validation.fields = fields;
|
|
@@ -429,5 +447,4 @@ var DefaultErrorRenderer = class {
|
|
|
429
447
|
return code;
|
|
430
448
|
}
|
|
431
449
|
};
|
|
432
|
-
|
|
433
|
-
export { DefaultErrorRenderer as default, malformedIdentifierOf, schemaMismatchOf };
|
|
450
|
+
export { DefaultErrorRenderer as default, malformedIdentifierOf, schemaMismatchOf, toValidationFields };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isProfiledUrl } from "../src/profiler/aggregate.js";
|
|
1
2
|
import HttpError from "../src/errors/httpError.js";
|
|
2
3
|
import { buildTrustProxy } from "../src/context/trustProxy.js";
|
|
3
4
|
import { resolveForwarded } from "../src/context/forwarded.js";
|
|
@@ -5,22 +6,23 @@ import { MemoryRateLimitStore } from "../src/rateLimit/MemoryRateLimitStore.js";
|
|
|
5
6
|
import { WsConnectionCounter } from "../src/rateLimit/WsConnectionCounter.js";
|
|
6
7
|
import { compileDomainPatterns, compileTrustedHosts, isDomainAllowed } from "../src/context/domainMatcher.js";
|
|
7
8
|
import { responseEnded } from "../src/context/responseEnded.js";
|
|
9
|
+
import { unsentResponseWarning } from "../src/context/unsentResponse.js";
|
|
10
|
+
import { closeRequestTrace, hasRequestTraceSubscribers, openRequestTrace, runTraced } from "../src/context/requestTrace.js";
|
|
8
11
|
import { parseWebsocketUrl } from "../src/context/websocket/websocketUrl.js";
|
|
9
|
-
import WebsocketContext from "../src/context/websocket/WebsocketContext.js";
|
|
12
|
+
import WebsocketContext, { toHandshakeStatus } from "../src/context/websocket/WebsocketContext.js";
|
|
13
|
+
import { trackPong } from "./servers/wsHeartbeat.js";
|
|
10
14
|
import HttpContext from "../src/context/http/HttpContext.js";
|
|
11
15
|
import DefaultErrorRenderer from "./error-renderer.js";
|
|
12
|
-
import DefaultRequestLogger from "./request-logger.js";
|
|
13
16
|
import JsonAuditLogger from "./audit-logger.js";
|
|
17
|
+
import DefaultRequestLogger from "./request-logger.js";
|
|
14
18
|
import PrettyRequestLogger from "./pretty-request-logger.js";
|
|
15
19
|
import { resolveTraceparent } from "./trace.js";
|
|
16
20
|
import { asServersConfig, configuredServerPort } from "../src/servers/kernelServers.js";
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import { GcScheduler, IDENTITY_CHANGED_EVENT, Module, RequestContext, RevocationWatch, Service, finallyMaybe, injectable, isPromise, logColor, nodefonyError, servedUrl, thenMaybe, writeRuntimeState } from "nodefony";
|
|
21
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
|
|
22
|
+
import { GcScheduler, IDENTITY_CHANGED_EVENT, RequestContext, RevocationWatch, Service, finallyMaybe, injectable, isPromise, logColor, nodefonyError, servedUrl, thenMaybe, writeRuntimeState } from "nodefony";
|
|
20
23
|
import cluster from "node:cluster";
|
|
21
24
|
import http from "node:http";
|
|
22
|
-
|
|
23
|
-
//#region nodefony/service/http-kernel.ts
|
|
25
|
+
// nodefony/service/http-kernel.ts
|
|
24
26
|
const HEALTH_HEADERS = {
|
|
25
27
|
"content-type": "application/json",
|
|
26
28
|
"cache-control": "no-store"
|
|
@@ -111,6 +113,8 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
111
113
|
errorRenderer = new DefaultErrorRenderer();
|
|
112
114
|
requestLogger = new DefaultRequestLogger();
|
|
113
115
|
profiler = null;
|
|
116
|
+
/** `timing.tooling` : profiler aussi l'outillage (lu une fois, à onReady). */
|
|
117
|
+
profileTooling = false;
|
|
114
118
|
_trustProxyChecker = null;
|
|
115
119
|
_wsOriginPolicy = Object.create(null);
|
|
116
120
|
rateLimiter = null;
|
|
@@ -126,9 +130,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
126
130
|
this.container?.addScope("request");
|
|
127
131
|
this.responseTimeout = {
|
|
128
132
|
http: this.options.http.responseTimeout,
|
|
129
|
-
https: this.options.https.responseTimeout
|
|
130
|
-
http2: this.options.https.responseTimeout,
|
|
131
|
-
http3: this.options.https.responseTimeout
|
|
133
|
+
https: this.options.https.responseTimeout
|
|
132
134
|
};
|
|
133
135
|
this.closeTimeOutWs = {
|
|
134
136
|
ws: this.options.websocket.closeTimeout,
|
|
@@ -210,6 +212,36 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
210
212
|
get rateLimitStore() {
|
|
211
213
|
return this.rateLimiter;
|
|
212
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Cette requête — ou cette frame du pont — se profile-t-elle ? Jamais sans
|
|
217
|
+
* profileur (production). L'outillage (console, profileur, journaux, Vite :
|
|
218
|
+
* `isProfiledUrl`, sur la règle `isConsolePath` du cœur) seulement sous
|
|
219
|
+
* `timing.tooling` — on ne
|
|
220
|
+
* mesure pas la façon dont on mesure : ni tampon SQL, ni instantané, ni place
|
|
221
|
+
* dans les anneaux du profileur.
|
|
222
|
+
*
|
|
223
|
+
* @param url - URL ou chemin de la requête
|
|
224
|
+
* @returns `true` quand le profil doit être pris
|
|
225
|
+
*/
|
|
226
|
+
shouldProfile(url) {
|
|
227
|
+
return this.profiler !== null && isProfiledUrl(url, this.profileTooling);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* En développement, signale une action qui a rendu une valeur que le
|
|
231
|
+
* Resolver ne sait pas envoyer (`null`, `undefined`, instance de classe)
|
|
232
|
+
* et n'a rien envoyé elle-même : la requête attend son délai d'expiration.
|
|
233
|
+
* Le message nomme l'action, ce qu'elle a rendu et le geste qui convient
|
|
234
|
+
* (`unsentResponseWarning`).
|
|
235
|
+
*
|
|
236
|
+
* Appelé au timeout, AVANT que le 504/408 parte — après, `sended` est vrai et
|
|
237
|
+
* plus rien ne se signalerait — et au démontage, pour un client parti avant.
|
|
238
|
+
* Gratuit en production : rien n'est lu au-delà de `waitAsync`.
|
|
239
|
+
*
|
|
240
|
+
* @param context - le contexte HTTP de la requête
|
|
241
|
+
*/
|
|
242
|
+
warnUnsentResponse(context) {
|
|
243
|
+
if (context.waitAsync && !context.sended && this.kernel?.environment === "development") this.log(unsentResponseWarning(context.resolver, context.url), "WARNING");
|
|
244
|
+
}
|
|
213
245
|
configureRateLimit() {
|
|
214
246
|
this.rateLimitGc?.stop();
|
|
215
247
|
this.rateLimitGc = null;
|
|
@@ -280,6 +312,8 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
280
312
|
this.regAlias = this.compileAlias();
|
|
281
313
|
this.sessionService = this.get("sessions");
|
|
282
314
|
this.profiler = this.get("profiler");
|
|
315
|
+
const timing = this.kernel?.options.timing;
|
|
316
|
+
this.profileTooling = typeof timing === "object" && timing !== null && "tooling" in timing && timing.tooling === true;
|
|
283
317
|
this.configureRateLimit();
|
|
284
318
|
this.configureWsConnectionLimit();
|
|
285
319
|
this.configureHealth();
|
|
@@ -356,7 +390,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
356
390
|
const policy = this.getWsOriginPolicy(secure ? "websocketSecure" : "websocket");
|
|
357
391
|
if (policy.disabled) return true;
|
|
358
392
|
if (!origin) return true;
|
|
359
|
-
let originHost
|
|
393
|
+
let originHost;
|
|
360
394
|
try {
|
|
361
395
|
originHost = new URL(origin).hostname;
|
|
362
396
|
} catch {
|
|
@@ -382,7 +416,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
382
416
|
* @throws {HttpError} code WS 1008 (Policy Violation) si l'Origin est refusée.
|
|
383
417
|
*/
|
|
384
418
|
checkWebsocketOrigin(context) {
|
|
385
|
-
const secure = context.
|
|
419
|
+
const secure = context.serverType === "wss";
|
|
386
420
|
const raw = context.origin;
|
|
387
421
|
if (this.isWebsocketOriginAllowed(raw, context.domain, secure)) return;
|
|
388
422
|
const error = new HttpError(`WebSocket Origin "${raw}" not allowed (CSWSH protection)`, 1008, context);
|
|
@@ -481,7 +515,14 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
481
515
|
context.resolver = resolver;
|
|
482
516
|
return resolver;
|
|
483
517
|
}
|
|
484
|
-
if (resolver.exception)
|
|
518
|
+
if (resolver.exception) {
|
|
519
|
+
const exception = resolver.exception;
|
|
520
|
+
if (context.secure && "code" in exception && exception.code === 426) {
|
|
521
|
+
context.resolver = resolver;
|
|
522
|
+
return resolver;
|
|
523
|
+
}
|
|
524
|
+
throw resolver.exception;
|
|
525
|
+
}
|
|
485
526
|
throw new HttpError("Not Found", 404, context);
|
|
486
527
|
}
|
|
487
528
|
/**
|
|
@@ -580,18 +621,26 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
580
621
|
await context.close();
|
|
581
622
|
return context;
|
|
582
623
|
}
|
|
583
|
-
if (context instanceof WebsocketContext)
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
context.
|
|
624
|
+
if (context instanceof WebsocketContext) {
|
|
625
|
+
if (context.admission !== null) {
|
|
626
|
+
const status = toHandshakeStatus("code" in error && typeof error.code === "number" ? error.code : void 0);
|
|
627
|
+
context.refuseHandshake(status);
|
|
628
|
+
if (this.kernel?.debug) this.log(`${status} ${error.message}`, "DEBUG", "WEBSOCKET REFUSED");
|
|
587
629
|
return context;
|
|
588
630
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
631
|
+
try {
|
|
632
|
+
const wsResult = this.errorRenderer.renderWebsocket(error, context);
|
|
633
|
+
if (context.response?.connection) {
|
|
634
|
+
context.close(wsResult.code, wsResult.reason);
|
|
635
|
+
return context;
|
|
636
|
+
}
|
|
637
|
+
if (context.request && !context.rejected) {
|
|
638
|
+
context.reject(wsResult.code, wsResult.reason);
|
|
639
|
+
return context;
|
|
640
|
+
}
|
|
641
|
+
} catch {
|
|
642
|
+
throw error;
|
|
592
643
|
}
|
|
593
|
-
} catch {
|
|
594
|
-
throw error;
|
|
595
644
|
}
|
|
596
645
|
throw error;
|
|
597
646
|
}
|
|
@@ -633,7 +682,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
633
682
|
if (headerServer != null && headerServer !== "") response.setHeader("Server", headerServer);
|
|
634
683
|
if (this.secContentTypeOptions !== null) response.setHeader("X-Content-Type-Options", this.secContentTypeOptions);
|
|
635
684
|
if (this.secFrameOptions !== null) response.setHeader("X-Frame-Options", this.secFrameOptions);
|
|
636
|
-
if (this.secHsts !== null &&
|
|
685
|
+
if (this.secHsts !== null && type === "https") response.setHeader("Strict-Transport-Security", this.secHsts);
|
|
637
686
|
if (this.#healthPaths !== null) {
|
|
638
687
|
const url = request.url;
|
|
639
688
|
if (url === this.#healthPaths.liveness) {
|
|
@@ -676,6 +725,11 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
676
725
|
await serverHttps.createServer();
|
|
677
726
|
servers.push(serverHttps);
|
|
678
727
|
}
|
|
728
|
+
const serverH2c = this.get("server-h2c");
|
|
729
|
+
if (serverH2c?.active) {
|
|
730
|
+
await serverH2c.createServer();
|
|
731
|
+
servers.push(serverH2c);
|
|
732
|
+
}
|
|
679
733
|
const serverWebsocket = this.get("server-websocket");
|
|
680
734
|
if (serverWebsocket && serverHttp?.active) {
|
|
681
735
|
await serverWebsocket.createServer(serverHttp);
|
|
@@ -686,7 +740,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
686
740
|
await serverWebsocketSecure.createServer(serverHttps);
|
|
687
741
|
servers.push(serverWebsocketSecure);
|
|
688
742
|
}
|
|
689
|
-
this.publishRuntimePorts(serverHttp, serverHttps);
|
|
743
|
+
this.publishRuntimePorts(serverHttp, serverHttps, serverH2c);
|
|
690
744
|
return servers;
|
|
691
745
|
}
|
|
692
746
|
/**
|
|
@@ -720,7 +774,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
720
774
|
* {@link readRuntimeState} — `status`/`stop` redeviendraient aveugles alors que
|
|
721
775
|
* l'app écoute toujours. Le master, lui, est le propriétaire du runtime.
|
|
722
776
|
*/
|
|
723
|
-
publishRuntimePorts(serverHttp, serverHttps) {
|
|
777
|
+
publishRuntimePorts(serverHttp, serverHttps, serverH2c) {
|
|
724
778
|
const ports = [];
|
|
725
779
|
const desired = [];
|
|
726
780
|
const urls = [];
|
|
@@ -736,6 +790,11 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
736
790
|
const port = configuredServerPort(asServersConfig(this.kernel?.options.servers), "https");
|
|
737
791
|
if (port) desired.push(port);
|
|
738
792
|
}
|
|
793
|
+
if (serverH2c?.active && serverH2c.port > 0) {
|
|
794
|
+
ports.push(serverH2c.port);
|
|
795
|
+
const port = configuredServerPort(asServersConfig(this.kernel?.options.servers), "h2c");
|
|
796
|
+
if (port) desired.push(port);
|
|
797
|
+
}
|
|
739
798
|
if (ports.length === 0) return;
|
|
740
799
|
writeRuntimeState(process.cwd(), {
|
|
741
800
|
pid: cluster.isWorker ? process.ppid : process.pid,
|
|
@@ -794,10 +853,12 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
794
853
|
teardownHttp(context, scope) {
|
|
795
854
|
if (context.finished) return void 0;
|
|
796
855
|
try {
|
|
797
|
-
|
|
856
|
+
context.stopClock();
|
|
857
|
+
if (context.waitAsync) this.warnUnsentResponse(context);
|
|
798
858
|
context.logRequest();
|
|
859
|
+
if (context.requestTrace !== null) closeRequestTrace(context, context.response.statusCode, context.error);
|
|
799
860
|
context.logPhasesVerbose();
|
|
800
|
-
this.profiler?.collect(context);
|
|
861
|
+
if (this.shouldProfile(context.url)) this.profiler?.collect(context);
|
|
801
862
|
const afterResponse = context._runAfterResponse();
|
|
802
863
|
if (afterResponse !== void 0 || context.listenerCount("onFinish")) return this.finishTeardownHttp(context, scope, afterResponse);
|
|
803
864
|
this.releaseHttpContext(context, scope);
|
|
@@ -856,7 +917,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
856
917
|
response.on("close", () => {
|
|
857
918
|
if (!responseEnded(response)) {
|
|
858
919
|
context._abortIfPending("Connection closed before response finished");
|
|
859
|
-
if (
|
|
920
|
+
if (context.response.statusCode < 400) context.response.statusCode = 499;
|
|
860
921
|
}
|
|
861
922
|
this.teardownHttp(context, scope);
|
|
862
923
|
});
|
|
@@ -881,7 +942,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
881
942
|
try {
|
|
882
943
|
context = this.createHttpContext(scope, request, response, type);
|
|
883
944
|
const created = context;
|
|
884
|
-
const served =
|
|
945
|
+
const served = hasRequestTraceSubscribers() ? runTraced(openRequestTrace(created, "http"), this.dispatchHttpContext, this, created, scope, request, response) : this.dispatchHttpContext(created, scope, request, response);
|
|
885
946
|
if (!isPromise(served)) return served;
|
|
886
947
|
return served.catch((e) => this.failHttp(e, created, scope));
|
|
887
948
|
} catch (e) {
|
|
@@ -889,12 +950,26 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
889
950
|
}
|
|
890
951
|
}
|
|
891
952
|
/**
|
|
953
|
+
* Sert un contexte HTTP fraîchement créé : le crochet `onCreateContext`
|
|
954
|
+
* s'il a des écouteurs, puis le pipeline.
|
|
955
|
+
*
|
|
956
|
+
* Hot path : `fireAsync` est une fonction async → 1 Promise + 1 microtask
|
|
957
|
+
* MÊME à 0 listener. En prod sans @nodefony/security ces seams
|
|
958
|
+
* (onCreateContext/beforeResolve/afterAuth) n'ont aucun listener → garde
|
|
959
|
+
* `listenerCount` (O(1), 0 alloc) pour ne RIEN planifier.
|
|
960
|
+
*
|
|
961
|
+
* @returns le contexte servi, ou sa promesse
|
|
962
|
+
*/
|
|
963
|
+
dispatchHttpContext(context, scope, request, response) {
|
|
964
|
+
return this.listenerCount("onCreateContext") ? this.fireAsync("onCreateContext", context).then(() => this.serveHttpContext(context, scope, request, response)) : this.serveHttpContext(context, scope, request, response);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
892
967
|
* Traceparent, tampon du profileur, puis le pipeline dans la bulle ALS de la
|
|
893
968
|
* requête (`RequestContext.run`).
|
|
894
969
|
*/
|
|
895
970
|
serveHttpContext(context, scope, request, response) {
|
|
896
971
|
context.traceparent = resolveTraceparent(request.headers.traceparent);
|
|
897
|
-
const profilerQueries = this.
|
|
972
|
+
const profilerQueries = this.shouldProfile(context.url) ? [] : null;
|
|
898
973
|
context.profilerQueries = profilerQueries;
|
|
899
974
|
context.profiling = profilerQueries !== null;
|
|
900
975
|
const store = {
|
|
@@ -946,7 +1021,10 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
946
1021
|
routeHttpRequest(httpContext, request, response) {
|
|
947
1022
|
if (this.reverseProxy !== null && this.reverseProxy.mounts !== null && (httpContext.validDomain || !this.domainCheck)) {
|
|
948
1023
|
const relayed = this.reverseProxy.forward(request, response, httpContext.scheme);
|
|
949
|
-
if (relayed !== void 0)
|
|
1024
|
+
if (relayed !== void 0) {
|
|
1025
|
+
httpContext.relayAccessLog = this.reverseProxy.match(request.url)?.accessLog ?? "all";
|
|
1026
|
+
return relayed.then(() => httpContext);
|
|
1027
|
+
}
|
|
950
1028
|
}
|
|
951
1029
|
httpContext.phaseStart("resolve");
|
|
952
1030
|
httpContext.resolver = this.router ? this.router.resolve(httpContext) : null;
|
|
@@ -1019,9 +1097,15 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1019
1097
|
await this.firewall?.handleSecurity(context);
|
|
1020
1098
|
if (this.listenerCount("afterAuth")) await this.fireAsync("afterAuth", context);
|
|
1021
1099
|
} catch (authError) {
|
|
1100
|
+
if (context.resolver?.exception) {
|
|
1101
|
+
context.response.response?.removeHeader("Upgrade");
|
|
1102
|
+
context.response.response?.removeHeader("Connection");
|
|
1103
|
+
}
|
|
1022
1104
|
await this.fireAsync("onAuthFailure", context, authError).catch((e) => this.log(e, "ERROR", "onAuthFailure"));
|
|
1023
1105
|
throw authError;
|
|
1024
1106
|
}
|
|
1107
|
+
const pending = context.resolver?.exception;
|
|
1108
|
+
if (pending) throw pending;
|
|
1025
1109
|
} finally {
|
|
1026
1110
|
context.phaseEnd("firewall");
|
|
1027
1111
|
}
|
|
@@ -1057,21 +1141,48 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1057
1141
|
this.log(e, "ERROR", "WS onFinish");
|
|
1058
1142
|
}
|
|
1059
1143
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1144
|
+
/**
|
|
1145
|
+
* Point d'entrée d'un upgrade vers une route WebSocket de Nodefony, appelé
|
|
1146
|
+
* par le répartiteur d'upgrade AVANT le `101` (#596) : rien ne s'ouvre tant
|
|
1147
|
+
* que le pipeline n'a pas dit oui.
|
|
1148
|
+
*
|
|
1149
|
+
* Les bornes par IP passent en premier, avant tout scope, ALS ou pipeline
|
|
1150
|
+
* (symétrie avec le 429 HTTP et avec le proxy inverse) : le vrai vecteur DoS
|
|
1151
|
+
* est d'accumuler des sockets ouvertes. La place est rendue à la fermeture du
|
|
1152
|
+
* socket du client — même règle que pour un tunnel relayé.
|
|
1153
|
+
*
|
|
1154
|
+
* @param req - requête d'upgrade
|
|
1155
|
+
* @param admission - le verdict à rendre : accepter (`101`) ou refuser (HTTP)
|
|
1156
|
+
* @param type - serveur qui a reçu l'upgrade
|
|
1157
|
+
* @returns la promesse du traitement de la connexion
|
|
1158
|
+
*/
|
|
1159
|
+
async onWebsocketUpgrade(req, admission, type) {
|
|
1160
|
+
if (this.websocketQuotaRefusal(req, req.socket) !== null) {
|
|
1161
|
+
admission.refuse(429);
|
|
1064
1162
|
return;
|
|
1065
1163
|
}
|
|
1066
1164
|
await this.fireAsync("onServerRequest", req, null, type);
|
|
1067
1165
|
const scope = this.container?.enterScope("request");
|
|
1068
|
-
return this.handleWebsocket(scope,
|
|
1166
|
+
return this.handleWebsocket(scope, req, admission, type);
|
|
1069
1167
|
}
|
|
1070
|
-
|
|
1168
|
+
/**
|
|
1169
|
+
* Pipeline d'un handshake WebSocket, dans la bulle ALS de la connexion :
|
|
1170
|
+
* jugement (`onConnect`), pare-feu, gardes de l'action — PUIS seulement
|
|
1171
|
+
* l'acceptation, la socket et l'action de handshake. Tout refus antérieur à
|
|
1172
|
+
* l'acceptation part en statut HTTP (`onError` → `refuseHandshake`).
|
|
1173
|
+
*
|
|
1174
|
+
* @param scope - scope DI de la connexion
|
|
1175
|
+
* @param req - requête d'upgrade
|
|
1176
|
+
* @param admission - le verdict à rendre
|
|
1177
|
+
* @param type - serveur qui a reçu l'upgrade
|
|
1178
|
+
* @returns ce que rend l'action de handshake
|
|
1179
|
+
*/
|
|
1180
|
+
async handleWebsocket(scope, req, admission, type) {
|
|
1071
1181
|
let context = null;
|
|
1072
1182
|
let error = null;
|
|
1073
1183
|
try {
|
|
1074
|
-
context = this.createWebsocketContext(scope, req,
|
|
1184
|
+
context = this.createWebsocketContext(scope, req, null, type);
|
|
1185
|
+
context.admission = admission;
|
|
1075
1186
|
} catch (e) {
|
|
1076
1187
|
error = e;
|
|
1077
1188
|
}
|
|
@@ -1091,7 +1202,7 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1091
1202
|
scope
|
|
1092
1203
|
};
|
|
1093
1204
|
context?.holdRequestStore(store);
|
|
1094
|
-
|
|
1205
|
+
const pipeline = async () => {
|
|
1095
1206
|
await this.onConnect(context, error);
|
|
1096
1207
|
if (this.firewall && context?.secure) {
|
|
1097
1208
|
context.phaseStart("firewall");
|
|
@@ -1107,26 +1218,55 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1107
1218
|
context.phaseEnd("firewall");
|
|
1108
1219
|
}
|
|
1109
1220
|
}
|
|
1110
|
-
if (context)
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1221
|
+
if (!context) return;
|
|
1222
|
+
const guarded = context.resolver?.authorizeAction();
|
|
1223
|
+
if (guarded !== void 0) await guarded;
|
|
1224
|
+
const accepted = admission.accept();
|
|
1225
|
+
const ws = accepted instanceof Promise ? await accepted : accepted;
|
|
1226
|
+
if (ws === null) {
|
|
1227
|
+
if (context.requestTrace !== null) closeRequestTrace(context, null, null);
|
|
1228
|
+
this.releaseOrphanWsScope(scope, context);
|
|
1229
|
+
return;
|
|
1117
1230
|
}
|
|
1118
|
-
|
|
1231
|
+
trackPong(ws);
|
|
1232
|
+
context.acceptConnection(ws);
|
|
1233
|
+
await context.connect();
|
|
1234
|
+
context.phaseStart("action");
|
|
1235
|
+
try {
|
|
1236
|
+
return await context.handle();
|
|
1237
|
+
} finally {
|
|
1238
|
+
context.phaseEnd("action");
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
return await (context !== null && hasRequestTraceSubscribers() ? runTraced(openRequestTrace(context, "websocket"), RequestContext.run, RequestContext, store, pipeline) : RequestContext.run(store, pipeline));
|
|
1119
1242
|
} catch (e) {
|
|
1120
1243
|
try {
|
|
1121
1244
|
await this.onError(e, context);
|
|
1122
1245
|
} catch (errorHandlingError) {
|
|
1246
|
+
admission.refuse(500);
|
|
1247
|
+
this.closeFailedWebsocketTrace(context, e);
|
|
1123
1248
|
this.releaseOrphanWsScope(scope, context);
|
|
1124
1249
|
throw errorHandlingError;
|
|
1125
1250
|
}
|
|
1251
|
+
admission.refuse(500);
|
|
1252
|
+
this.closeFailedWebsocketTrace(context, e);
|
|
1126
1253
|
this.releaseOrphanWsScope(scope, context);
|
|
1127
1254
|
throw e;
|
|
1128
1255
|
}
|
|
1129
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Fin du canal `nodefony.http.request` pour un handshake interrompu par une
|
|
1259
|
+
* exception — sans effet si un refus l'a déjà publiée (`refuseHandshake`).
|
|
1260
|
+
* Le statut est un fait, pas une déduction : une socket rattachée dit que
|
|
1261
|
+
* le `101` est parti ; sinon c'est le `500` du filet qui a été rendu.
|
|
1262
|
+
*
|
|
1263
|
+
* @param context - le contexte du handshake, `null` s'il n'a pu être construit
|
|
1264
|
+
* @param error - l'exception qui a interrompu le pipeline
|
|
1265
|
+
*/
|
|
1266
|
+
closeFailedWebsocketTrace(context, error) {
|
|
1267
|
+
if (!context?.requestTrace) return;
|
|
1268
|
+
closeRequestTrace(context, context.connection !== null ? 101 : 500, error);
|
|
1269
|
+
}
|
|
1130
1270
|
releaseOrphanWsScope(scope, context) {
|
|
1131
1271
|
if (!context) {
|
|
1132
1272
|
this.container?.leaveScope(scope);
|
|
@@ -1137,6 +1277,16 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1137
1277
|
this.container?.leaveScope(scope);
|
|
1138
1278
|
context.clean();
|
|
1139
1279
|
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Juge un handshake WebSocket AVANT son acceptation : hôte, origine,
|
|
1282
|
+
* `beforeResolve`, contrôleur (protocole négocié), session. N'ouvre rien —
|
|
1283
|
+
* l'acceptation n'a lieu qu'après le pare-feu et les gardes de l'action
|
|
1284
|
+
* (cf {@link handleWebsocket}).
|
|
1285
|
+
*
|
|
1286
|
+
* @param context - contexte du handshake, encore sans socket
|
|
1287
|
+
* @param error - erreur levée à la construction du contexte, relancée telle quelle
|
|
1288
|
+
* @throws la première erreur du jugement — rendue en statut HTTP par `onError`
|
|
1289
|
+
*/
|
|
1140
1290
|
async onConnect(context, error = null) {
|
|
1141
1291
|
if (error) throw error;
|
|
1142
1292
|
if (!context) throw new nodefonyError("Bad context", 500);
|
|
@@ -1150,7 +1300,6 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1150
1300
|
throw e;
|
|
1151
1301
|
}
|
|
1152
1302
|
if (!context.sessionStarting) await this.startSession(context);
|
|
1153
|
-
return context.connect();
|
|
1154
1303
|
}
|
|
1155
1304
|
checkValidDomain(context) {
|
|
1156
1305
|
if (context.validDomain) return 200;
|
|
@@ -1301,7 +1450,6 @@ let HttpKernel = class HttpKernel extends Service {
|
|
|
1301
1450
|
return this.#streams?.revalidate() ?? Promise.resolve();
|
|
1302
1451
|
}
|
|
1303
1452
|
};
|
|
1304
|
-
HttpKernel = __decorate([injectable()
|
|
1453
|
+
HttpKernel = __decorate([injectable()], HttpKernel);
|
|
1305
1454
|
var http_kernel_default = HttpKernel;
|
|
1306
|
-
//#endregion
|
|
1307
1455
|
export { http_kernel_default as default, responseEnded };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { handshakeRefusalStatus } from "../src/context/websocket/WebsocketContext.js";
|
|
2
|
+
import { severityFromStatus, statusOf } from "./audit-logger.js";
|
|
3
|
+
import { accessStatusFamily, logColor } from "nodefony";
|
|
4
|
+
// nodefony/service/pretty-request-logger.ts
|
|
4
5
|
/**
|
|
5
6
|
* Pretty single-line logger for dev (P3.2) — the biggest gain for humans.
|
|
6
7
|
*
|
|
@@ -23,9 +24,9 @@ var PrettyRequestLogger = class {
|
|
|
23
24
|
renderHttp(context, error) {
|
|
24
25
|
const ctx = context;
|
|
25
26
|
const err = error ?? ctx.error ?? null;
|
|
26
|
-
const status = ctx
|
|
27
|
+
const status = statusOf(ctx) ?? (err ? 500 : null);
|
|
27
28
|
const method = ctx.method ?? "?";
|
|
28
|
-
const duration =
|
|
29
|
+
const duration = ctx.stopClock();
|
|
29
30
|
return {
|
|
30
31
|
text: `${logColor.cyan(method.padEnd(4))} ${colorizeStatus(status)} ${ctx.url} ${logColor.blackBright(formatDuration(duration))} ${logColor.blackBright(ctx.remoteAddress ?? "-")} ` + logColor.magenta(`[${shortId(ctx.requestId)}]`) + (err ? ` ${logColor.red(err.message)}` : ""),
|
|
31
32
|
severity: severityFromStatus(status),
|
|
@@ -34,22 +35,24 @@ var PrettyRequestLogger = class {
|
|
|
34
35
|
}
|
|
35
36
|
renderWebsocket(context, error, acceptedProtocol) {
|
|
36
37
|
const ctx = context;
|
|
37
|
-
const
|
|
38
|
-
const
|
|
38
|
+
const refused = error ? handshakeRefusalStatus(context, error) : null;
|
|
39
|
+
const status = refused ?? statusOf(ctx) ?? (error ? 500 : null);
|
|
40
|
+
const duration = ctx.stopClock();
|
|
39
41
|
const proto = acceptedProtocol ? `[${acceptedProtocol}]` : "";
|
|
40
42
|
return {
|
|
41
43
|
text: `${logColor.cyan("WS ")} ${colorizeStatus(status)} ${ctx.url} ${logColor.blackBright(proto)} ${logColor.blackBright(formatDuration(duration))} ${logColor.blackBright(ctx.remoteAddress ?? "-")} ` + logColor.magenta(`[${shortId(ctx.requestId)}]`) + (error ? ` ${logColor.red(error.message)}` : ""),
|
|
42
|
-
severity: error ? "ERROR" : "INFO",
|
|
44
|
+
severity: refused !== null ? severityFromStatus(refused) : error ? "ERROR" : "INFO",
|
|
43
45
|
msgid: "req"
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
function colorizeStatus(status) {
|
|
48
|
-
|
|
50
|
+
const family = accessStatusFamily(status);
|
|
51
|
+
if (status === null || family === null) return logColor.blackBright("---");
|
|
49
52
|
const s = String(status);
|
|
50
|
-
if (
|
|
51
|
-
if (
|
|
52
|
-
if (
|
|
53
|
+
if (family >= 5) return logColor.red(s);
|
|
54
|
+
if (family === 4) return logColor.yellowBold(s);
|
|
55
|
+
if (family === 3) return logColor.yellow(s);
|
|
53
56
|
return logColor.green(s);
|
|
54
57
|
}
|
|
55
58
|
function formatDuration(ms) {
|
|
@@ -61,5 +64,4 @@ function formatDuration(ms) {
|
|
|
61
64
|
function shortId(id) {
|
|
62
65
|
return id.length > 8 ? id.slice(0, 8) : id;
|
|
63
66
|
}
|
|
64
|
-
//#endregion
|
|
65
67
|
export { PrettyRequestLogger as default };
|