@nodefony/http 10.0.0-alpha.1
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/LICENSE +544 -0
- package/README.md +77 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +108 -0
- package/dist/nodefony/command/assetsPublishCommand.js +102 -0
- package/dist/nodefony/command/certificatesCommand.js +47 -0
- package/dist/nodefony/command/networkCommand.js +27 -0
- package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
- package/dist/nodefony/config/config.js +335 -0
- package/dist/nodefony/config/defineModuleConfig.js +93 -0
- package/dist/nodefony/interfaces/IContext.js +1 -0
- package/dist/nodefony/interfaces/ICookie.js +1 -0
- package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
- package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
- package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
- package/dist/nodefony/interfaces/IRequest.js +1 -0
- package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
- package/dist/nodefony/interfaces/IResponse.js +1 -0
- package/dist/nodefony/interfaces/ISession.js +1 -0
- package/dist/nodefony/interfaces/IUpload.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +376 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
- package/dist/nodefony/service/audit-logger.js +159 -0
- package/dist/nodefony/service/certificates.js +545 -0
- package/dist/nodefony/service/error-renderer.js +320 -0
- package/dist/nodefony/service/http-kernel.js +948 -0
- package/dist/nodefony/service/pretty-request-logger.js +72 -0
- package/dist/nodefony/service/request-logger.js +54 -0
- package/dist/nodefony/service/servers/clientError.js +20 -0
- package/dist/nodefony/service/servers/server-http.js +135 -0
- package/dist/nodefony/service/servers/server-https.js +204 -0
- package/dist/nodefony/service/servers/server-static.js +192 -0
- package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
- package/dist/nodefony/service/servers/server-websocket.js +104 -0
- package/dist/nodefony/service/servers/serverShutdown.js +31 -0
- package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
- package/dist/nodefony/service/sessions/sessions-service.js +580 -0
- package/dist/nodefony/service/trace.js +72 -0
- package/dist/nodefony/service/upload/upload-service.js +171 -0
- package/dist/nodefony/src/assets/collectAssets.js +34 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
- package/dist/nodefony/src/context/Context.js +415 -0
- package/dist/nodefony/src/context/domainMatcher.js +88 -0
- package/dist/nodefony/src/context/forwarded.js +185 -0
- package/dist/nodefony/src/context/http/HttpContext.js +309 -0
- package/dist/nodefony/src/context/http/Request.js +543 -0
- package/dist/nodefony/src/context/http/Response.js +368 -0
- package/dist/nodefony/src/context/http/parser.js +188 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
- package/dist/nodefony/src/context/http2/Request.js +29 -0
- package/dist/nodefony/src/context/http2/Response.js +97 -0
- package/dist/nodefony/src/context/metaData.js +47 -0
- package/dist/nodefony/src/context/requestId.js +41 -0
- package/dist/nodefony/src/context/trustProxy.js +167 -0
- package/dist/nodefony/src/context/websocket/Response.js +181 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
- package/dist/nodefony/src/cookies/cookie.js +258 -0
- package/dist/nodefony/src/errors/httpError.js +69 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
- package/dist/nodefony/src/profiler/Profiler.js +139 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
- package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
- package/dist/nodefony/src/servers/portBinder.js +114 -0
- package/dist/nodefony/src/session/session.js +390 -0
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
- package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
- package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
- package/dist/types/index.d.ts +83 -0
- package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
- package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
- package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
- package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
- package/dist/types/nodefony/config/config.d.ts +197 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
- package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
- package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
- package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
- package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
- package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
- package/dist/types/nodefony/interfaces/index.d.ts +7 -0
- package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
- package/dist/types/nodefony/service/certificates.d.ts +246 -0
- package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
- package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
- package/dist/types/nodefony/service/request-logger.d.ts +18 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
- package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
- package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
- package/dist/types/nodefony/service/trace.d.ts +39 -0
- package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
- package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
- package/dist/types/nodefony/src/context/Context.d.ts +195 -0
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
- package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
- package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
- package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
- package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
- package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
- package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
- package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
- package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
- package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
- package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
- package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
- package/dist/types/nodefony/src/session/session.d.ts +171 -0
- package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
- package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
- package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
- package/docs/cookies.md +365 -0
- package/docs/index.md +163 -0
- package/docs/observabilite.md +460 -0
- package/docs/rate-limit.md +372 -0
- package/docs/servers.md +935 -0
- package/docs/session.md +768 -0
- package/docs/upload.md +460 -0
- package/package.json +101 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { severityFromStatus } from "./audit-logger.js";
|
|
2
|
+
import { logColor } from "nodefony";
|
|
3
|
+
import { performance } from "node:perf_hooks";
|
|
4
|
+
//#region nodefony/service/pretty-request-logger.ts
|
|
5
|
+
/**
|
|
6
|
+
* Pretty single-line logger for dev (P3.2) — the biggest gain for humans.
|
|
7
|
+
*
|
|
8
|
+
* Format (no padding, ANSI-colored):
|
|
9
|
+
* `GET 200 /api/test 12.3ms 127.0.0.1 [a1b2c3d4]`
|
|
10
|
+
*
|
|
11
|
+
* - method : cyan
|
|
12
|
+
* - status : green (2xx) / yellow (3xx) / yellow-bold (4xx) / red (5xx)
|
|
13
|
+
* - url : default
|
|
14
|
+
* - duration : dim
|
|
15
|
+
* - remote : dim
|
|
16
|
+
* - requestId : magenta-dim, truncated to first 8 chars
|
|
17
|
+
*
|
|
18
|
+
* Activate with `httpKernel.setRequestLogger(new PrettyRequestLogger())`.
|
|
19
|
+
*
|
|
20
|
+
* Stateless singleton. Allocates a few strings per request (terminal log
|
|
21
|
+
* path) — zero overhead when not activated.
|
|
22
|
+
*/
|
|
23
|
+
var PrettyRequestLogger = class {
|
|
24
|
+
renderHttp(context, error) {
|
|
25
|
+
const ctx = context;
|
|
26
|
+
const err = error ?? ctx.error ?? null;
|
|
27
|
+
const status = ctx.response?.statusCode ?? (err ? 500 : null);
|
|
28
|
+
const method = ctx.method ?? "?";
|
|
29
|
+
const duration = computeDurationMs(ctx.phases);
|
|
30
|
+
return {
|
|
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)}` : ""),
|
|
32
|
+
severity: severityFromStatus(status),
|
|
33
|
+
msgid: "req"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
renderWebsocket(context, error, acceptedProtocol) {
|
|
37
|
+
const ctx = context;
|
|
38
|
+
const status = ctx.response?.statusCode ?? (error ? 500 : null);
|
|
39
|
+
const duration = computeDurationMs(ctx.phases);
|
|
40
|
+
const proto = acceptedProtocol ? `[${acceptedProtocol}]` : "";
|
|
41
|
+
return {
|
|
42
|
+
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)}` : ""),
|
|
43
|
+
severity: error ? "ERROR" : "INFO",
|
|
44
|
+
msgid: "req"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function colorizeStatus(status) {
|
|
49
|
+
if (status === null) return logColor.blackBright("---");
|
|
50
|
+
const s = String(status);
|
|
51
|
+
if (status >= 500) return logColor.red(s);
|
|
52
|
+
if (status >= 400) return logColor.yellowBold(s);
|
|
53
|
+
if (status >= 300) return logColor.yellow(s);
|
|
54
|
+
return logColor.green(s);
|
|
55
|
+
}
|
|
56
|
+
function formatDuration(ms) {
|
|
57
|
+
if (ms === null) return "---";
|
|
58
|
+
if (ms < 1) return `${ms.toFixed(2)}ms`;
|
|
59
|
+
if (ms < 1e3) return `${ms.toFixed(1)}ms`;
|
|
60
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
61
|
+
}
|
|
62
|
+
function shortId(id) {
|
|
63
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
64
|
+
}
|
|
65
|
+
function computeDurationMs(phases) {
|
|
66
|
+
if (!phases.length) return null;
|
|
67
|
+
const first = phases[0];
|
|
68
|
+
if (typeof first.startMs !== "number") return null;
|
|
69
|
+
return performance.now() - first.startMs;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { PrettyRequestLogger as default };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { logColor } from "nodefony";
|
|
2
|
+
//#region nodefony/service/request-logger.ts
|
|
3
|
+
/**
|
|
4
|
+
* Default Nodefony per-request logger — preserves the legacy colored format.
|
|
5
|
+
*
|
|
6
|
+
* HTTP success: `URL : <url> FROM : <remote> ORIGIN : <host> ID : <uuid>`
|
|
7
|
+
* HTTP error: adds the error toString (in dev), single-line in prod
|
|
8
|
+
* WebSocket: adds `Accept-Protocol : <proto>` on success
|
|
9
|
+
*
|
|
10
|
+
* Stateless singleton — zero per-request allocation. Override via
|
|
11
|
+
* `httpKernel.setRequestLogger(custom)` for JSON access logs (P3.1),
|
|
12
|
+
* pretty formatter (P3.2), or NCSA combined format (P3.10).
|
|
13
|
+
*/
|
|
14
|
+
var DefaultRequestLogger = class {
|
|
15
|
+
renderHttp(context, error) {
|
|
16
|
+
const ctx = context;
|
|
17
|
+
const txt = `${logColor.cyan("URL")} : ${ctx.url} ${logColor.cyan("FROM")} : ${ctx.remoteAddress} ${logColor.cyan("ORIGIN")} : ${ctx.originUrl?.host} ${logColor.cyan("ID")} : ${ctx.requestId}`;
|
|
18
|
+
const err = error ?? ctx.error;
|
|
19
|
+
if (err) {
|
|
20
|
+
const errCode = err.code ?? ctx.response?.statusCode ?? 500;
|
|
21
|
+
const msgid = `${ctx.type} ${logColor.magenta(errCode)} ${logColor.red(ctx.method ?? "")}`;
|
|
22
|
+
return {
|
|
23
|
+
text: ctx.kernel?.environment === "production" ? `${txt} ${err}` : `${txt}\n ${err}`,
|
|
24
|
+
severity: "ERROR",
|
|
25
|
+
msgid
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
text: txt,
|
|
30
|
+
severity: "INFO",
|
|
31
|
+
msgid: `${ctx.type} ${logColor.magenta(ctx.response?.statusCode ?? "")} ${ctx.method}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
renderWebsocket(context, error, acceptedProtocol) {
|
|
35
|
+
const ctx = context;
|
|
36
|
+
if (error) {
|
|
37
|
+
const errCode = error.code ?? ctx.response?.statusCode ?? 500;
|
|
38
|
+
const msgid = `${ctx.type} ${logColor.magenta(errCode)} ${logColor.red(ctx.method ?? "")}`;
|
|
39
|
+
return {
|
|
40
|
+
text: `${logColor.cyan("URL")} : ${ctx.url} ${logColor.cyan("FROM")} : ${ctx.remoteAddress} ${logColor.cyan("ORIGIN")} : ${ctx.originUrl?.host} ${logColor.cyan("ID")} : ${ctx.requestId}\n ` + error.toString(),
|
|
41
|
+
severity: "ERROR",
|
|
42
|
+
msgid
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const msgid = `${ctx.type} ${logColor.magenta(ctx.response?.statusCode ?? "")} ${ctx.method}`;
|
|
46
|
+
return {
|
|
47
|
+
text: `${logColor.cyan("URL")} : ${ctx.url} ${logColor.cyan("Accept-Protocol")} : ${acceptedProtocol || "*"} ${logColor.cyan("FROM")} : ${ctx.remoteAddress} ${logColor.cyan("ORIGIN")} : ${ctx.originUrl?.host} ${logColor.cyan("ID")} : ${ctx.requestId}`,
|
|
48
|
+
severity: "INFO",
|
|
49
|
+
msgid
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { DefaultRequestLogger as default };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region nodefony/service/servers/clientError.ts
|
|
2
|
+
/**
|
|
3
|
+
* Gère l'event `clientError` d'un `http.Server` / `https.Server`.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ Doc Node (`http.Server` event `'clientError'`) : dès qu'un listener
|
|
6
|
+
* `clientError` est attaché, Node **cesse de fermer le socket automatiquement**
|
|
7
|
+
* (le défaut aurait répondu `400` + détruit le socket). Sans fermeture
|
|
8
|
+
* explicite, un socket malformé reste ouvert → fuite de socket/FD = DoS sur
|
|
9
|
+
* requête mal formée. On répond donc une réponse minimale et on ferme.
|
|
10
|
+
*
|
|
11
|
+
* @param error - erreur émise (codes `llhttp` : `HPE_*`, ou `ECONNRESET`…).
|
|
12
|
+
* @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
|
|
13
|
+
*/
|
|
14
|
+
function handleClientError(error, socket) {
|
|
15
|
+
if (error?.code === "ECONNRESET" || !socket.writable) return;
|
|
16
|
+
const statusLine = error?.code === "HPE_HEADER_OVERFLOW" ? "431 Request Header Fields Too Large" : "400 Bad Request";
|
|
17
|
+
socket.end(`HTTP/1.1 ${statusLine}\r\nConnection: close\r\n\r\n`);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { handleClientError };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
|
|
3
|
+
import http_kernel_default from "../http-kernel.js";
|
|
4
|
+
import { handleClientError } from "./clientError.js";
|
|
5
|
+
import { createDrainTerminator } from "./serverShutdown.js";
|
|
6
|
+
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
7
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
|
|
8
|
+
import { Module, Service, extend, inject, nodefonyError } from "nodefony";
|
|
9
|
+
import http from "node:http";
|
|
10
|
+
//#region nodefony/service/servers/server-http.ts
|
|
11
|
+
let ServerHttp = class ServerHttp extends Service {
|
|
12
|
+
httpKernel;
|
|
13
|
+
module;
|
|
14
|
+
server = null;
|
|
15
|
+
httpTerminator = null;
|
|
16
|
+
port;
|
|
17
|
+
protocol = "1.1";
|
|
18
|
+
ready = false;
|
|
19
|
+
type = "http";
|
|
20
|
+
domain;
|
|
21
|
+
scheme = "http";
|
|
22
|
+
address = null;
|
|
23
|
+
family = null;
|
|
24
|
+
active = false;
|
|
25
|
+
infos = null;
|
|
26
|
+
constructor(module, httpKernel) {
|
|
27
|
+
super("server-http", module.container, module.notificationsCenter, module.options.http);
|
|
28
|
+
this.httpKernel = httpKernel;
|
|
29
|
+
this.module = module;
|
|
30
|
+
this.active = !!module.kernel?.options.servers.http;
|
|
31
|
+
this.port = this.setPort();
|
|
32
|
+
this.domain = this.module.kernel?.domain;
|
|
33
|
+
}
|
|
34
|
+
setPort() {
|
|
35
|
+
if (this.module.kernel?.options.servers?.http) return this.module.kernel?.options.servers?.http?.port || 0;
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
createServer() {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
try {
|
|
41
|
+
if (!this.active) return reject(/* @__PURE__ */ new Error(`Server http is not allowed in config file `));
|
|
42
|
+
const opt = extend({ requestTimeout: this.options.requestTimeout });
|
|
43
|
+
this.server = http.createServer(opt);
|
|
44
|
+
this.httpTerminator = createDrainTerminator(this.server, this.options.shutdownTimeout);
|
|
45
|
+
if (this.options.maxHeadersCount) {
|
|
46
|
+
if (this.server) this.server.maxHeadersCount = this.options.maxHeadersCount;
|
|
47
|
+
}
|
|
48
|
+
if (this.options.timeout) {
|
|
49
|
+
if (this.server) {
|
|
50
|
+
this.server.setTimeout(this.options.timeout, () => {
|
|
51
|
+
this.fire("onTimeout", this);
|
|
52
|
+
});
|
|
53
|
+
this.server.timeout = this.options.timeout;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (this.options.keepAliveTimeout) {
|
|
57
|
+
if (this.server) this.server.keepAliveTimeout = this.options.keepAliveTimeout;
|
|
58
|
+
}
|
|
59
|
+
this.server.on("request", (request, response) => this.httpKernel?.onHttpRequest(request, response, this.type).catch(() => {}));
|
|
60
|
+
this.module.fire("onCreateServer", this.type, this);
|
|
61
|
+
bindWithFallback(this.server, this.domain, buildBindPlan("http", this.module.kernel?.options.servers, this.module.kernel?.environment)).then(({ address, shiftedFrom }) => {
|
|
62
|
+
this.infos = address;
|
|
63
|
+
this.port = address.port;
|
|
64
|
+
this.address = address.address;
|
|
65
|
+
this.family = address.family;
|
|
66
|
+
if (shiftedFrom !== null) this.log(`Port ${shiftedFrom} déjà occupé → HTTP écoute sur ${this.port}. Figer le port : servers.http.port ; échouer au lieu de glisser : servers.portPolicy = "strict".`, "WARNING");
|
|
67
|
+
this.ready = true;
|
|
68
|
+
this.module.fire("onServersReady", this.type, this);
|
|
69
|
+
this.attachErrorHandler();
|
|
70
|
+
resolve(this.server);
|
|
71
|
+
}).catch((error) => {
|
|
72
|
+
this.reportBindError(error);
|
|
73
|
+
reject(error);
|
|
74
|
+
});
|
|
75
|
+
this.kernel?.once("onTerminate", async () => {
|
|
76
|
+
if (!this.server) return;
|
|
77
|
+
try {
|
|
78
|
+
await this.httpTerminator?.terminate();
|
|
79
|
+
this.log(`${this.type} SHUTDOWN Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
|
|
80
|
+
} catch (e) {
|
|
81
|
+
this.log(e, "ERROR", "TERMINATE");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.server.on("clientError", (e, socket) => {
|
|
85
|
+
this.fire("onClientError", e, socket);
|
|
86
|
+
handleClientError(e, socket);
|
|
87
|
+
});
|
|
88
|
+
} catch (e) {
|
|
89
|
+
this.log(e, "CRITIC");
|
|
90
|
+
return reject(e);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Handler d'erreur DURABLE — posé une fois le serveur en écoute.
|
|
96
|
+
*
|
|
97
|
+
* Il ne traite donc plus `EADDRINUSE` au bind (le binder s'en charge, avec ou
|
|
98
|
+
* sans repli) : il couvre les erreurs de la VIE du serveur.
|
|
99
|
+
*/
|
|
100
|
+
attachErrorHandler() {
|
|
101
|
+
this.server?.on("error", (error) => {
|
|
102
|
+
this.log(new nodefonyError(error), "CRITIC");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Le bind a échoué pour de bon — soit `portPolicy: "strict"`, soit tous les
|
|
107
|
+
* ports de repli étaient pris, soit une erreur qui n'est pas un conflit de port.
|
|
108
|
+
*
|
|
109
|
+
* Reste FATAL (contrat inchangé) : un serveur qui n'écoute pas ne doit jamais
|
|
110
|
+
* laisser le process traîner en se croyant démarré.
|
|
111
|
+
*/
|
|
112
|
+
reportBindError(error) {
|
|
113
|
+
const myError = new nodefonyError(error);
|
|
114
|
+
switch (error.code) {
|
|
115
|
+
case "ENOTFOUND":
|
|
116
|
+
this.log(`CHECK DOMAIN IN /etc/hosts or config unable to connect to : ${this.domain}`, "ERROR");
|
|
117
|
+
this.log(myError, "CRITIC");
|
|
118
|
+
break;
|
|
119
|
+
case "EADDRINUSE":
|
|
120
|
+
this.log(`Port ${this.port} déjà occupé (domaine ${this.domain}) — le serveur HTTP ne peut pas écouter. Libérer le port, en choisir un autre (servers.http.port), ou autoriser le repli automatique (servers.portPolicy = "auto", défaut en développement).`, "ERROR");
|
|
121
|
+
this.log(myError, "CRITIC");
|
|
122
|
+
break;
|
|
123
|
+
default: this.log(myError, "CRITIC");
|
|
124
|
+
}
|
|
125
|
+
this.server?.close();
|
|
126
|
+
setTimeout(() => this.kernel?.terminate(1), 1e3);
|
|
127
|
+
}
|
|
128
|
+
showBanner() {
|
|
129
|
+
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
ServerHttp = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], ServerHttp);
|
|
133
|
+
var server_http_default = ServerHttp;
|
|
134
|
+
//#endregion
|
|
135
|
+
export { server_http_default as default };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
|
|
3
|
+
import http_kernel_default from "../http-kernel.js";
|
|
4
|
+
import { handleClientError } from "./clientError.js";
|
|
5
|
+
import { createDrainTerminator } from "./serverShutdown.js";
|
|
6
|
+
import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
|
|
7
|
+
import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
|
|
8
|
+
import { Module, Service, extend, inject, nodefonyError } from "nodefony";
|
|
9
|
+
import http2 from "node:http2";
|
|
10
|
+
import https from "node:https";
|
|
11
|
+
import { TLSSocket } from "node:tls";
|
|
12
|
+
//#region nodefony/service/servers/server-https.ts
|
|
13
|
+
let ServerHttps = class ServerHttps extends Service {
|
|
14
|
+
httpKernel;
|
|
15
|
+
httpTerminator = null;
|
|
16
|
+
module;
|
|
17
|
+
server = null;
|
|
18
|
+
port;
|
|
19
|
+
protocol = "1.1";
|
|
20
|
+
ready = false;
|
|
21
|
+
type = "https";
|
|
22
|
+
domain;
|
|
23
|
+
scheme = "https";
|
|
24
|
+
address = null;
|
|
25
|
+
family = null;
|
|
26
|
+
active = false;
|
|
27
|
+
infos = null;
|
|
28
|
+
constructor(module, httpKernel) {
|
|
29
|
+
super("server-https", module.container, module.notificationsCenter, module.options.https);
|
|
30
|
+
this.httpKernel = httpKernel;
|
|
31
|
+
this.module = module;
|
|
32
|
+
this.active = !!this.kernel?.options.servers.https;
|
|
33
|
+
this.port = this.setPort();
|
|
34
|
+
this.domain = this.kernel?.domain;
|
|
35
|
+
}
|
|
36
|
+
terminator() {
|
|
37
|
+
if (this.server) return createDrainTerminator(this.server, this.options.shutdownTimeout);
|
|
38
|
+
throw new Error(`Server not found`);
|
|
39
|
+
}
|
|
40
|
+
setPort() {
|
|
41
|
+
if (this.kernel?.options.servers?.https) return this.kernel?.options.servers?.https?.port || 0;
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
createServer() {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
try {
|
|
47
|
+
if (!this.active) return reject(/* @__PURE__ */ new Error(`Server https is not allowed in config file `));
|
|
48
|
+
this.protocol = this.kernel?.options.servers.https.protocol;
|
|
49
|
+
if (this.protocol === "2.0") return resolve(this.createServerH2());
|
|
50
|
+
const opt = extend({
|
|
51
|
+
requestTimeout: this.options.requestTimeout,
|
|
52
|
+
rejectUnauthorized: this.options.rejectUnauthorized,
|
|
53
|
+
key: this.httpKernel?.serviceCerticats?.key,
|
|
54
|
+
cert: this.httpKernel?.serviceCerticats?.cert,
|
|
55
|
+
ca: this.httpKernel?.serviceCerticats?.ca ? this.httpKernel?.serviceCerticats?.ca : void 0
|
|
56
|
+
});
|
|
57
|
+
this.server = https.createServer(opt);
|
|
58
|
+
this.httpTerminator = this.terminator();
|
|
59
|
+
if (this.options.timeout) {
|
|
60
|
+
if (this.server) {
|
|
61
|
+
this.server.setTimeout(this.options.timeout, () => {
|
|
62
|
+
this.fire("onTimeout", this);
|
|
63
|
+
});
|
|
64
|
+
this.server.timeout = this.options.timeout;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (this.options.maxHeadersCount) {
|
|
68
|
+
if (this.server) this.server.maxHeadersCount = this.options.maxHeadersCount;
|
|
69
|
+
}
|
|
70
|
+
if (this.options.keepAliveTimeout) {
|
|
71
|
+
if (this.server) this.server.keepAliveTimeout = this.options.keepAliveTimeout;
|
|
72
|
+
}
|
|
73
|
+
this.module.fire("onCreateServer", this.type, this);
|
|
74
|
+
this.listenWithPolicy().then(() => resolve(this.server)).catch((error) => {
|
|
75
|
+
this.reportBindError(error);
|
|
76
|
+
reject(error);
|
|
77
|
+
});
|
|
78
|
+
this.server.on("request", (request, response) => {
|
|
79
|
+
this.httpKernel?.onHttpRequest(request, response, this.type).catch(() => {});
|
|
80
|
+
});
|
|
81
|
+
this.kernel?.once("onTerminate", async () => {
|
|
82
|
+
if (!this.server) return;
|
|
83
|
+
try {
|
|
84
|
+
await this.httpTerminator?.terminate();
|
|
85
|
+
this.log(`${this.type} SHUTDOWN Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
|
|
86
|
+
} catch (e) {
|
|
87
|
+
this.log(e, "ERROR", "TERMINATE");
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
this.server.on("clientError", (e, socket) => {
|
|
91
|
+
this.fire("onClientError", e, socket);
|
|
92
|
+
handleClientError(e, socket);
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
this.log(e, "CRITIC");
|
|
96
|
+
return reject(e);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
createServerH2() {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
try {
|
|
103
|
+
const h2Cfg = (this.module?.options)?.http2 ?? {};
|
|
104
|
+
const opt = extend({
|
|
105
|
+
allowHTTP1: true,
|
|
106
|
+
rejectUnauthorized: this.options.rejectUnauthorized,
|
|
107
|
+
key: this.httpKernel?.serviceCerticats?.key,
|
|
108
|
+
cert: this.httpKernel?.serviceCerticats?.cert,
|
|
109
|
+
ca: this.httpKernel?.serviceCerticats?.ca ? this.httpKernel?.serviceCerticats?.ca : void 0
|
|
110
|
+
});
|
|
111
|
+
if (h2Cfg.maxSessionMemory) opt.maxSessionMemory = h2Cfg.maxSessionMemory;
|
|
112
|
+
if (h2Cfg.maxConcurrentStreams) opt.settings = {
|
|
113
|
+
...opt.settings,
|
|
114
|
+
maxConcurrentStreams: h2Cfg.maxConcurrentStreams
|
|
115
|
+
};
|
|
116
|
+
this.server = http2.createSecureServer(opt);
|
|
117
|
+
this.httpTerminator = this.terminator();
|
|
118
|
+
if (this.options.timeout) this.server.setTimeout(this.options.timeout, () => {
|
|
119
|
+
this.fire("onTimeout", this);
|
|
120
|
+
});
|
|
121
|
+
this.server.on("request", (request, response) => {
|
|
122
|
+
let alpnProtocol = false;
|
|
123
|
+
if (request && request.stream && request.stream.session?.socket) {
|
|
124
|
+
if (request.stream.session.socket instanceof TLSSocket) alpnProtocol = request.stream.session.socket.alpnProtocol;
|
|
125
|
+
}
|
|
126
|
+
if (request.socket) {
|
|
127
|
+
if (request.socket instanceof TLSSocket) alpnProtocol = request.socket.alpnProtocol;
|
|
128
|
+
}
|
|
129
|
+
if (alpnProtocol === "h2") return this.httpKernel?.onHttpRequest(request, response, "http2").catch(() => {});
|
|
130
|
+
else return this.httpKernel?.onHttpRequest(request, response, "https").catch(() => {});
|
|
131
|
+
});
|
|
132
|
+
this.listenWithPolicy().then(() => resolve(this.server)).catch((error) => {
|
|
133
|
+
this.reportBindError(error);
|
|
134
|
+
reject(error);
|
|
135
|
+
});
|
|
136
|
+
this.kernel?.once("onTerminate", async () => {
|
|
137
|
+
if (!this.server) return;
|
|
138
|
+
try {
|
|
139
|
+
await this.httpTerminator?.terminate();
|
|
140
|
+
this.log(`${this.type} SHUTDOWN Server is listening on DOMAIN : ${this.domain} PORT : ${this.port}`, "INFO");
|
|
141
|
+
} catch (e) {
|
|
142
|
+
this.log(e, "ERROR", "TERMINATE");
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
this.server.on("sessionError", (error) => {
|
|
146
|
+
this.log(error, "ERROR", "HTTP2 Server sessionError");
|
|
147
|
+
});
|
|
148
|
+
this.server.on("streamError", (error) => {
|
|
149
|
+
this.log(error, "ERROR", "HTTP2 Server streamError");
|
|
150
|
+
});
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return reject(e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Écoute selon `servers.portPolicy`, commune aux deux branches TLS (HTTP/1.1 et
|
|
158
|
+
* HTTP/2) — leur seule différence est le type du serveur, pas la politique.
|
|
159
|
+
*
|
|
160
|
+
* Pose le handler d'erreur durable une fois en écoute (donc APRÈS les éventuels
|
|
161
|
+
* `EADDRINUSE` de repli, qui ne sont pas des pannes).
|
|
162
|
+
*/
|
|
163
|
+
async listenWithPolicy() {
|
|
164
|
+
const { address, shiftedFrom } = await bindWithFallback(this.server, this.domain, buildBindPlan("https", this.module.kernel?.options.servers, this.module.kernel?.environment));
|
|
165
|
+
this.infos = address;
|
|
166
|
+
this.port = address.port;
|
|
167
|
+
this.address = address.address;
|
|
168
|
+
this.family = address.family;
|
|
169
|
+
if (shiftedFrom !== null) this.log(`Port ${shiftedFrom} déjà occupé → HTTPS écoute sur ${this.port}. Figer le port : servers.https.port ; échouer au lieu de glisser : servers.portPolicy = "strict".`, "WARNING");
|
|
170
|
+
this.ready = true;
|
|
171
|
+
this.module.fire("onServersReady", this.type, this);
|
|
172
|
+
this.attachErrorHandler();
|
|
173
|
+
}
|
|
174
|
+
/** Erreurs de la VIE du serveur (le bind est déjà passé — cf server-http.ts). */
|
|
175
|
+
attachErrorHandler() {
|
|
176
|
+
this.server?.on("error", (error) => {
|
|
177
|
+
this.log(new nodefonyError(error), "CRITIC");
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** Bind définitivement impossible → FATAL (contrat inchangé). */
|
|
181
|
+
reportBindError(error) {
|
|
182
|
+
const myError = new nodefonyError(error);
|
|
183
|
+
switch (error.code) {
|
|
184
|
+
case "ENOTFOUND":
|
|
185
|
+
this.log(`CHECK DOMAIN IN /etc/hosts or config unable to connect to : ${this.domain}`, "ERROR");
|
|
186
|
+
this.log(myError, "CRITIC");
|
|
187
|
+
break;
|
|
188
|
+
case "EADDRINUSE":
|
|
189
|
+
this.log(`Port ${this.port} déjà occupé (domaine ${this.domain}) — le serveur HTTPS ne peut pas écouter. Libérer le port, en choisir un autre (servers.https.port), ou autoriser le repli automatique (servers.portPolicy = "auto", défaut en développement).`, "ERROR");
|
|
190
|
+
this.log(myError, "CRITIC");
|
|
191
|
+
break;
|
|
192
|
+
default: this.log(myError, "CRITIC");
|
|
193
|
+
}
|
|
194
|
+
this.server?.close();
|
|
195
|
+
setTimeout(() => this.kernel?.terminate(1), 1e3);
|
|
196
|
+
}
|
|
197
|
+
showBanner() {
|
|
198
|
+
if (this.infos) this.log(`Server Listen on ${this.scheme}://${this.infos.address}:${this.infos.port} Family: ${this.infos.family} Protocol : ${this.protocol}`);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
ServerHttps = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], ServerHttps);
|
|
202
|
+
var server_https_default = ServerHttps;
|
|
203
|
+
//#endregion
|
|
204
|
+
export { server_https_default as default };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Service, extend } from "nodefony";
|
|
2
|
+
import { URL } from "node:url";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import mime from "mime-types";
|
|
7
|
+
import http2 from "node:http2";
|
|
8
|
+
import tls from "node:tls";
|
|
9
|
+
import serveStatic from "serve-static";
|
|
10
|
+
//#region nodefony/service/servers/server-static.ts
|
|
11
|
+
const defaultOptions = {
|
|
12
|
+
cacheControl: true,
|
|
13
|
+
maxAge: 345600
|
|
14
|
+
};
|
|
15
|
+
var Statics = class extends Service {
|
|
16
|
+
module;
|
|
17
|
+
servers;
|
|
18
|
+
/** Montages préfixés (prod frontend). Lazy : `[]` rempli à `addMount`. */
|
|
19
|
+
mounts = [];
|
|
20
|
+
/**
|
|
21
|
+
* Serveur statique config-driven actif. `false` (config `statics.enabled`) =
|
|
22
|
+
* aucun montage `web`/`assets`, 0 listener — quand un reverse-proxy/CDN sert
|
|
23
|
+
* les statiques. Ne gate PAS les `addMount()` programmatiques (frontend prod).
|
|
24
|
+
*/
|
|
25
|
+
enabled = true;
|
|
26
|
+
defaultOptions = defaultOptions;
|
|
27
|
+
constructor(module) {
|
|
28
|
+
const container = module.container || void 0;
|
|
29
|
+
const options = module.options.statics || {};
|
|
30
|
+
let event;
|
|
31
|
+
if (container) event = container.get("notificationsCenter");
|
|
32
|
+
super("server-static", container, event, options);
|
|
33
|
+
this.module = module;
|
|
34
|
+
this.servers = {};
|
|
35
|
+
this.enabled = this.options.enabled !== false;
|
|
36
|
+
if (typeof this.options.enabled !== "undefined") delete this.options.enabled;
|
|
37
|
+
this.defaultOptions = extend(defaultOptions, this.options.defaultOptions || {});
|
|
38
|
+
if (this.options.defaultOptions) delete this.options.defaultOptions;
|
|
39
|
+
if (this.enabled) this.initStaticFiles();
|
|
40
|
+
else this.log("Static file server DISABLED (statics.enabled=false) — reverse-proxy/CDN attendu", "INFO");
|
|
41
|
+
this.kernel?.once("onReady", () => {
|
|
42
|
+
this.mountModulePublics();
|
|
43
|
+
});
|
|
44
|
+
this.kernel?.on("onPostReady", () => {
|
|
45
|
+
if (this.kernel?.suppressBootBanners) return;
|
|
46
|
+
for (const ele in this.servers) this.log(`Server Listen on ${ele}`, "INFO");
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Auto-monte le `public/` de chaque module applicatif sous le préfixe natif
|
|
51
|
+
* `/<module>/` (basename du nom — `@nodefony/test` → `/test/`). Appelé une fois
|
|
52
|
+
* à `onReady`. Idempotent via {@link addMount} (un même préfixe est remplacé).
|
|
53
|
+
*
|
|
54
|
+
* Exclusions :
|
|
55
|
+
* - **app root** (`isApp`) — son `public/` est servi à la racine `/` par la
|
|
56
|
+
* racine statique `web` (favicon…), sans préfixe.
|
|
57
|
+
* - **modules frontend-managed** (présents dans `frontend.listEntries()`) —
|
|
58
|
+
* leurs assets buildés sont servis sous `/_assets/<name>/` par
|
|
59
|
+
* `@nodefony/frontend` ; on ne double-sert pas leur `public/dist`.
|
|
60
|
+
* - **modules sans `public/`** — rien à monter (http, framework, security…).
|
|
61
|
+
*
|
|
62
|
+
* Enregistré dans {@link mounts} quel que soit `enabled` → la carte
|
|
63
|
+
* préfixe→dossier reste lisible par `proxy:generate` même statiques désactivés
|
|
64
|
+
* (reverse-proxy/CDN en prod).
|
|
65
|
+
*/
|
|
66
|
+
mountModulePublics() {
|
|
67
|
+
const modules = this.kernel?.modules;
|
|
68
|
+
if (!modules) return;
|
|
69
|
+
const frontend = this.container?.get("frontend");
|
|
70
|
+
const frontManaged = frontend?.listEntries ? new Set(frontend.listEntries().map((e) => e.moduleName)) : null;
|
|
71
|
+
for (const name in modules) {
|
|
72
|
+
const mod = modules[name];
|
|
73
|
+
if (!mod || mod.isApp) continue;
|
|
74
|
+
const cfg = mod.options?.publicMount;
|
|
75
|
+
if (cfg === false) continue;
|
|
76
|
+
if (cfg == null && frontManaged?.has(mod.name)) continue;
|
|
77
|
+
const dir = join(mod.path, cfg?.dir || "public");
|
|
78
|
+
if (!existsSync(dir)) continue;
|
|
79
|
+
const prefix = cfg?.publicPath || `/${mod.name.split("/").pop()}/`;
|
|
80
|
+
this.addMount(prefix, dir);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
initStaticFiles() {
|
|
84
|
+
for (const staticRoot in this.options) {
|
|
85
|
+
let Path = this.options[staticRoot].path;
|
|
86
|
+
Path = this.kernel?.checkPath(Path);
|
|
87
|
+
let setHeaders = null;
|
|
88
|
+
const opt = this.options[staticRoot].options || {};
|
|
89
|
+
if (opt.setHeaders) {
|
|
90
|
+
if (typeof opt.setHeaders === "function") {
|
|
91
|
+
setHeaders = opt.setHeaders;
|
|
92
|
+
delete opt.setHeaders;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
opt.setHeaders = (res, path) => {
|
|
96
|
+
this.log(`Render ${path}`, "DEBUG", `SERVE STATIC ${staticRoot}`);
|
|
97
|
+
this.fire("onServeStatic", res, path, staticRoot, this);
|
|
98
|
+
};
|
|
99
|
+
if (setHeaders) this.on("onServeStatic", setHeaders);
|
|
100
|
+
this.addDirectory(Path, opt);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Monte un dossier sous un préfixe public (ex `/_assets/studio/` → outDir).
|
|
105
|
+
* Le préfixe est normalisé (leading + trailing `/`). Idempotent : un même
|
|
106
|
+
* préfixe est remplacé. Consommé par `@nodefony/frontend` en prod (assets
|
|
107
|
+
* Vite buildés) — résolu par nom via le Container, sans import croisé.
|
|
108
|
+
*
|
|
109
|
+
* @param prefix préfixe d'URL public
|
|
110
|
+
* @param dir dossier absolu à servir
|
|
111
|
+
*/
|
|
112
|
+
addMount(prefix, dir) {
|
|
113
|
+
let p = prefix.trim();
|
|
114
|
+
if (!p.startsWith("/")) p = `/${p}`;
|
|
115
|
+
if (!p.endsWith("/")) p = `${p}/`;
|
|
116
|
+
p = p.replace(/\/{2,}/g, "/");
|
|
117
|
+
const server = serveStatic(dir, extend({}, this.defaultOptions));
|
|
118
|
+
const entry = {
|
|
119
|
+
prefix: p,
|
|
120
|
+
server,
|
|
121
|
+
dir
|
|
122
|
+
};
|
|
123
|
+
const i = this.mounts.findIndex((m) => m.prefix === p);
|
|
124
|
+
if (i >= 0) this.mounts[i] = entry;
|
|
125
|
+
else this.mounts.push(entry);
|
|
126
|
+
this.log(`mount ${p} → ${dir}`, "INFO");
|
|
127
|
+
}
|
|
128
|
+
/** `true` si au moins un montage préfixé est actif (gate du pipeline). */
|
|
129
|
+
hasMounts() {
|
|
130
|
+
return this.mounts.length > 0;
|
|
131
|
+
}
|
|
132
|
+
addDirectory(Path, options) {
|
|
133
|
+
if (!Path) throw new Error("Static file path not Defined ");
|
|
134
|
+
const opt = extend({}, this.defaultOptions, options);
|
|
135
|
+
const server = serveStatic(Path, opt);
|
|
136
|
+
this.servers[Path] = server;
|
|
137
|
+
return server;
|
|
138
|
+
}
|
|
139
|
+
getStatic(server, request, response) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
server(request, response, (err) => {
|
|
142
|
+
if (err) return reject(err);
|
|
143
|
+
return resolve(response);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
getUrl(request) {
|
|
148
|
+
let scheme, host;
|
|
149
|
+
if (request instanceof http.IncomingMessage) {
|
|
150
|
+
scheme = request.connection instanceof tls.TLSSocket && request.connection.encrypted ? "https" : "http";
|
|
151
|
+
host = request.headers.host;
|
|
152
|
+
return scheme + "://" + host;
|
|
153
|
+
} else if (request instanceof http2.Http2ServerRequest) {
|
|
154
|
+
scheme = request.socket instanceof tls.TLSSocket && request.socket.encrypted ? "https" : "http";
|
|
155
|
+
host = request.headers[":authority"];
|
|
156
|
+
return scheme + "://" + host;
|
|
157
|
+
}
|
|
158
|
+
throw new Error(`Bad request type`);
|
|
159
|
+
}
|
|
160
|
+
async handle(request, response) {
|
|
161
|
+
if (!this.enabled && this.mounts.length === 0) return Promise.resolve(response);
|
|
162
|
+
const baseURL = this.getUrl(request);
|
|
163
|
+
const { pathname } = new URL(request.url, baseURL);
|
|
164
|
+
if (!pathname) throw new Error(`Bad url ${request.url}`);
|
|
165
|
+
if (this.mounts.length > 0) {
|
|
166
|
+
const raw = request.url;
|
|
167
|
+
for (const m of this.mounts) {
|
|
168
|
+
if (!raw.startsWith(m.prefix)) continue;
|
|
169
|
+
request.url = `/${raw.slice(m.prefix.length)}`;
|
|
170
|
+
try {
|
|
171
|
+
await this.getStatic(m.server, request, response);
|
|
172
|
+
request.url = raw;
|
|
173
|
+
} catch (e) {
|
|
174
|
+
request.url = raw;
|
|
175
|
+
return Promise.reject(e);
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const server in this.servers) try {
|
|
181
|
+
let ele = this.servers[server];
|
|
182
|
+
await this.getStatic(ele, request, response);
|
|
183
|
+
const type = mime.lookup(pathname);
|
|
184
|
+
response.setHeader("Content-Type", type);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return Promise.reject(e);
|
|
187
|
+
}
|
|
188
|
+
return Promise.resolve(response);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
//#endregion
|
|
192
|
+
export { Statics as default };
|