@morojs/moro 1.8.12 → 1.8.14
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 +4 -2
- package/dist/core/cluster/thread-cluster.d.ts +25 -0
- package/dist/core/cluster/thread-cluster.js +95 -3
- package/dist/core/cluster/thread-cluster.js.map +1 -1
- package/dist/core/config/config-validator.js +20 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/index.js +19 -2
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/framework.d.ts +26 -0
- package/dist/core/framework.js +195 -6
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +19 -0
- package/dist/core/http/http-server.js +75 -1
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +15 -0
- package/dist/core/http/http2-server.js +36 -3
- package/dist/core/http/http2-server.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +28 -0
- package/dist/core/http/moro-engine-server.js +138 -12
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/body-type.d.ts +12 -0
- package/dist/core/http/utils/body-type.js +70 -0
- package/dist/core/http/utils/body-type.js.map +1 -0
- package/dist/core/http/utils/compression.d.ts +21 -0
- package/dist/core/http/utils/compression.js +53 -7
- package/dist/core/http/utils/compression.js.map +1 -1
- package/dist/core/http/utils/ssl-config.d.ts +11 -0
- package/dist/core/http/utils/ssl-config.js +3 -0
- package/dist/core/http/utils/ssl-config.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.js +5 -4
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +3 -0
- package/dist/core/http/uws-http-server.js +47 -2
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
- package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
- package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +7 -0
- package/dist/core/middleware/built-in/compression/core.js +32 -1
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/session/core.d.ts +1 -0
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +18 -0
- package/dist/core/middleware/built-in/static/core.js +128 -7
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/engine-adapter.js +40 -1
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/socketio-adapter.js +10 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/uws-adapter.js +54 -3
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
- package/dist/core/networking/adapters/ws-adapter.js +97 -12
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/websocket-adapter.d.ts +19 -1
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/routing/index.d.ts +12 -8
- package/dist/core/routing/index.js +7 -2
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +46 -8
- package/dist/core/routing/unified-router.js +108 -32
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +8 -1
- package/dist/core/utilities/package-utils.js +2 -0
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/workers/worker-manager.js +2 -2
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +12 -1
- package/dist/moro.js +63 -7
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +2 -0
- package/dist/types/cache.js.map +1 -1
- package/dist/types/config.d.ts +16 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/http.d.ts +6 -0
- package/dist/types/http.js.map +1 -1
- package/dist/types/session.d.ts +1 -0
- package/dist/types/session.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a request body with this Content-Type decodes to a string,
|
|
3
|
+
* false when it must stay a Buffer.
|
|
4
|
+
*
|
|
5
|
+
* Rule: a missing Content-Type keeps the historical string behaviour;
|
|
6
|
+
* `text/*` and anything declaring a `charset` are text; `application/*` is
|
|
7
|
+
* text for the JSON/XML/JS/YAML family (including `+json`-style suffixes);
|
|
8
|
+
* every other type (`application/octet-stream`, `image/*`, `audio/*`,
|
|
9
|
+
* `video/*`, `font/*`, `application/pdf`, `application/zip`, protobuf,
|
|
10
|
+
* msgpack, ...) is binary.
|
|
11
|
+
*/
|
|
12
|
+
export declare function bodyIsText(contentType: string | undefined): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Request-body text/binary classification, shared by the Node, uWS and native
|
|
2
|
+
// engine servers so `req.body` has the same shape on every backend.
|
|
3
|
+
//
|
|
4
|
+
// JSON, urlencoded and multipart bodies are parsed into objects upstream of
|
|
5
|
+
// this. For everything else the question is whether decoding the bytes as
|
|
6
|
+
// UTF-8 is lossless: a text body arriving as a Buffer is one toString() away
|
|
7
|
+
// from what the handler wanted, but a binary body decoded to a string is
|
|
8
|
+
// corrupted for good (every invalid sequence becomes U+FFFD), and there is no
|
|
9
|
+
// other way to reach the bytes. So the default for an unknown type is Buffer.
|
|
10
|
+
/** application/* subtypes that are text on the wire */
|
|
11
|
+
const TEXT_APPLICATION_SUBTYPES = new Set([
|
|
12
|
+
'json',
|
|
13
|
+
'xml',
|
|
14
|
+
'javascript',
|
|
15
|
+
'ecmascript',
|
|
16
|
+
'x-javascript',
|
|
17
|
+
'x-www-form-urlencoded',
|
|
18
|
+
'graphql',
|
|
19
|
+
'sql',
|
|
20
|
+
'yaml',
|
|
21
|
+
'x-yaml',
|
|
22
|
+
'toml',
|
|
23
|
+
'csv',
|
|
24
|
+
'ndjson',
|
|
25
|
+
'x-ndjson',
|
|
26
|
+
'jsonl',
|
|
27
|
+
'x-sh',
|
|
28
|
+
'xhtml',
|
|
29
|
+
'ld+json',
|
|
30
|
+
'problem+json',
|
|
31
|
+
'soap+xml',
|
|
32
|
+
]);
|
|
33
|
+
/** Structured-syntax suffixes (RFC 6838) that are always text */
|
|
34
|
+
const TEXT_SUFFIXES = ['+json', '+xml', '+yaml', '+toml'];
|
|
35
|
+
/**
|
|
36
|
+
* True when a request body with this Content-Type decodes to a string,
|
|
37
|
+
* false when it must stay a Buffer.
|
|
38
|
+
*
|
|
39
|
+
* Rule: a missing Content-Type keeps the historical string behaviour;
|
|
40
|
+
* `text/*` and anything declaring a `charset` are text; `application/*` is
|
|
41
|
+
* text for the JSON/XML/JS/YAML family (including `+json`-style suffixes);
|
|
42
|
+
* every other type (`application/octet-stream`, `image/*`, `audio/*`,
|
|
43
|
+
* `video/*`, `font/*`, `application/pdf`, `application/zip`, protobuf,
|
|
44
|
+
* msgpack, ...) is binary.
|
|
45
|
+
*/
|
|
46
|
+
export function bodyIsText(contentType) {
|
|
47
|
+
if (!contentType)
|
|
48
|
+
return true;
|
|
49
|
+
const semi = contentType.indexOf(';');
|
|
50
|
+
const mime = (semi === -1 ? contentType : contentType.slice(0, semi)).trim().toLowerCase();
|
|
51
|
+
if (mime === '')
|
|
52
|
+
return true;
|
|
53
|
+
if (mime.startsWith('text/'))
|
|
54
|
+
return true;
|
|
55
|
+
if (semi !== -1 && /;\s*charset=/i.test(contentType))
|
|
56
|
+
return true;
|
|
57
|
+
if (mime.startsWith('application/')) {
|
|
58
|
+
const subtype = mime.slice('application/'.length);
|
|
59
|
+
if (TEXT_APPLICATION_SUBTYPES.has(subtype))
|
|
60
|
+
return true;
|
|
61
|
+
for (const suffix of TEXT_SUFFIXES) {
|
|
62
|
+
if (subtype.endsWith(suffix))
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// message/*, model/*, image/*, audio/*, video/*, font/*, ...
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=body-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-type.js","sourceRoot":"","sources":["../../../../src/core/http/utils/body-type.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,6EAA6E;AAC7E,yEAAyE;AACzE,8EAA8E;AAC9E,8EAA8E;AAE9E,uDAAuD;AACvD,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACxC,MAAM;IACN,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,uBAAuB;IACvB,SAAS;IACT,KAAK;IACL,MAAM;IACN,QAAQ;IACR,MAAM;IACN,KAAK;IACL,QAAQ;IACR,UAAU;IACV,OAAO;IACP,MAAM;IACN,OAAO;IACP,SAAS;IACT,cAAc;IACd,UAAU;CACX,CAAC,CAAC;AAEH,iEAAiE;AACjE,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,WAA+B;IACxD,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3F,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,yBAAyB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACxD,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;YACnC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC5C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,6DAA6D;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["// Request-body text/binary classification, shared by the Node, uWS and native\n// engine servers so `req.body` has the same shape on every backend.\n//\n// JSON, urlencoded and multipart bodies are parsed into objects upstream of\n// this. For everything else the question is whether decoding the bytes as\n// UTF-8 is lossless: a text body arriving as a Buffer is one toString() away\n// from what the handler wanted, but a binary body decoded to a string is\n// corrupted for good (every invalid sequence becomes U+FFFD), and there is no\n// other way to reach the bytes. So the default for an unknown type is Buffer.\n\n/** application/* subtypes that are text on the wire */\nconst TEXT_APPLICATION_SUBTYPES = new Set([\n 'json',\n 'xml',\n 'javascript',\n 'ecmascript',\n 'x-javascript',\n 'x-www-form-urlencoded',\n 'graphql',\n 'sql',\n 'yaml',\n 'x-yaml',\n 'toml',\n 'csv',\n 'ndjson',\n 'x-ndjson',\n 'jsonl',\n 'x-sh',\n 'xhtml',\n 'ld+json',\n 'problem+json',\n 'soap+xml',\n]);\n\n/** Structured-syntax suffixes (RFC 6838) that are always text */\nconst TEXT_SUFFIXES = ['+json', '+xml', '+yaml', '+toml'];\n\n/**\n * True when a request body with this Content-Type decodes to a string,\n * false when it must stay a Buffer.\n *\n * Rule: a missing Content-Type keeps the historical string behaviour;\n * `text/*` and anything declaring a `charset` are text; `application/*` is\n * text for the JSON/XML/JS/YAML family (including `+json`-style suffixes);\n * every other type (`application/octet-stream`, `image/*`, `audio/*`,\n * `video/*`, `font/*`, `application/pdf`, `application/zip`, protobuf,\n * msgpack, ...) is binary.\n */\nexport function bodyIsText(contentType: string | undefined): boolean {\n if (!contentType) return true;\n const semi = contentType.indexOf(';');\n const mime = (semi === -1 ? contentType : contentType.slice(0, semi)).trim().toLowerCase();\n if (mime === '') return true;\n if (mime.startsWith('text/')) return true;\n if (semi !== -1 && /;\\s*charset=/i.test(contentType)) return true;\n if (mime.startsWith('application/')) {\n const subtype = mime.slice('application/'.length);\n if (TEXT_APPLICATION_SUBTYPES.has(subtype)) return true;\n for (const suffix of TEXT_SUFFIXES) {\n if (subtype.endsWith(suffix)) return true;\n }\n return false;\n }\n // message/*, model/*, image/*, audio/*, video/*, font/*, ...\n return false;\n}\n"]}
|
|
@@ -4,8 +4,17 @@ export interface CompressionSettings {
|
|
|
4
4
|
threshold: number;
|
|
5
5
|
level: number;
|
|
6
6
|
encodings: Encoding[];
|
|
7
|
+
/** Bodies at or below this size compress on the calling thread (see shouldInline). */
|
|
8
|
+
maxInlineBytes: number;
|
|
7
9
|
}
|
|
8
10
|
export declare const DEFAULT_ENCODINGS: Encoding[];
|
|
11
|
+
/**
|
|
12
|
+
* 16 KB: gzip at the default level takes well under a millisecond on a body
|
|
13
|
+
* this size, less than the two thread handoffs the pool round trip costs,
|
|
14
|
+
* while a body many times larger would hold the event loop for longer than
|
|
15
|
+
* the requests queued behind it should wait.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_MAX_INLINE_BYTES: number;
|
|
9
18
|
/**
|
|
10
19
|
* Build CompressionSettings from the framework's performance config (the
|
|
11
20
|
* shape passed to every server's configurePerformance). Shared so all four
|
|
@@ -17,9 +26,19 @@ export declare function resolveCompressionSettings(config?: {
|
|
|
17
26
|
threshold?: number;
|
|
18
27
|
level?: number;
|
|
19
28
|
encodings?: Encoding[];
|
|
29
|
+
maxInlineBytes?: number;
|
|
20
30
|
};
|
|
21
31
|
minimal?: boolean;
|
|
22
32
|
}): CompressionSettings;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a body of `bytes` is compressed on the calling thread rather than
|
|
35
|
+
* on the threadpool. Inline is cheaper for small bodies (no handoff, no
|
|
36
|
+
* promise, no wake-up) and lets the response carry a Content-Length instead
|
|
37
|
+
* of streaming chunked. `maxInlineBytes: 0` sends everything through the pool.
|
|
38
|
+
*/
|
|
39
|
+
export declare function shouldInline(bytes: number, settings: {
|
|
40
|
+
maxInlineBytes: number;
|
|
41
|
+
}): boolean;
|
|
23
42
|
/**
|
|
24
43
|
* Content types worth compressing. Text and structured formats yes; already
|
|
25
44
|
* compressed binary (images, video, zip, octet-stream) no.
|
|
@@ -33,6 +52,8 @@ export declare function isCompressible(contentType: string | undefined): boolean
|
|
|
33
52
|
export declare function negotiateEncoding(acceptEncoding: string | undefined, allowed?: Encoding[]): Encoding | null;
|
|
34
53
|
/** Compress a buffer/string with the chosen encoding. */
|
|
35
54
|
export declare function compressBuffer(data: Buffer | string, encoding: Encoding, level?: number): Promise<Buffer>;
|
|
55
|
+
/** Compress a buffer/string on the calling thread (see shouldInline). */
|
|
56
|
+
export declare function compressBufferSync(data: Buffer | string, encoding: Encoding, level?: number): Buffer;
|
|
36
57
|
export interface MaybeCompressResult {
|
|
37
58
|
body: Buffer | string;
|
|
38
59
|
encoding: Encoding | null;
|
|
@@ -20,6 +20,13 @@ const gzipAsync = (buf, opts) => (gzipFn ??= promisify(z().gzip))(buf, opts);
|
|
|
20
20
|
const deflateAsync = (buf, opts) => (deflateFn ??= promisify(z().deflate))(buf, opts);
|
|
21
21
|
const brotliAsync = (buf, opts) => (brotliFn ??= promisify(z().brotliCompress))(buf, opts);
|
|
22
22
|
export const DEFAULT_ENCODINGS = ['br', 'gzip', 'deflate'];
|
|
23
|
+
/**
|
|
24
|
+
* 16 KB: gzip at the default level takes well under a millisecond on a body
|
|
25
|
+
* this size, less than the two thread handoffs the pool round trip costs,
|
|
26
|
+
* while a body many times larger would hold the event loop for longer than
|
|
27
|
+
* the requests queued behind it should wait.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_MAX_INLINE_BYTES = 16 * 1024;
|
|
23
30
|
/**
|
|
24
31
|
* Build CompressionSettings from the framework's performance config (the
|
|
25
32
|
* shape passed to every server's configurePerformance). Shared so all four
|
|
@@ -34,6 +41,35 @@ export function resolveCompressionSettings(config) {
|
|
|
34
41
|
threshold: c?.threshold ?? 1024,
|
|
35
42
|
level: c?.level ?? 6,
|
|
36
43
|
encodings: c?.encodings ?? DEFAULT_ENCODINGS,
|
|
44
|
+
maxInlineBytes: c?.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether a body of `bytes` is compressed on the calling thread rather than
|
|
49
|
+
* on the threadpool. Inline is cheaper for small bodies (no handoff, no
|
|
50
|
+
* promise, no wake-up) and lets the response carry a Content-Length instead
|
|
51
|
+
* of streaming chunked. `maxInlineBytes: 0` sends everything through the pool.
|
|
52
|
+
*/
|
|
53
|
+
export function shouldInline(bytes, settings) {
|
|
54
|
+
return settings.maxInlineBytes > 0 && bytes <= settings.maxInlineBytes;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Brotli options for one body: quality mapped from the 1-9 level, the size
|
|
58
|
+
* hint, and a window no larger than the input needs. The default window
|
|
59
|
+
* (2^22 bytes) has the encoder allocate and clear several megabytes per call,
|
|
60
|
+
* which for a response of a few kilobytes is most of its cost; a window that
|
|
61
|
+
* covers the whole input compresses it identically, since no match can
|
|
62
|
+
* reach back further than the input is long.
|
|
63
|
+
*/
|
|
64
|
+
function brotliOptions(length, level) {
|
|
65
|
+
const c = z().constants;
|
|
66
|
+
const lgwin = Math.max(c.BROTLI_MIN_WINDOW_BITS, Math.min(c.BROTLI_DEFAULT_WINDOW, Math.ceil(Math.log2(Math.max(length, 1))) + 1));
|
|
67
|
+
return {
|
|
68
|
+
params: {
|
|
69
|
+
[c.BROTLI_PARAM_QUALITY]: Math.max(0, Math.min(11, Math.round((level * 11) / 9))),
|
|
70
|
+
[c.BROTLI_PARAM_SIZE_HINT]: length,
|
|
71
|
+
[c.BROTLI_PARAM_LGWIN]: lgwin,
|
|
72
|
+
},
|
|
37
73
|
};
|
|
38
74
|
}
|
|
39
75
|
/**
|
|
@@ -97,18 +133,26 @@ export async function compressBuffer(data, encoding, level = 6) {
|
|
|
97
133
|
const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
98
134
|
switch (encoding) {
|
|
99
135
|
case 'br':
|
|
100
|
-
return brotliAsync(buf,
|
|
101
|
-
params: {
|
|
102
|
-
[z().constants.BROTLI_PARAM_QUALITY]: Math.max(0, Math.min(11, Math.round((level * 11) / 9))),
|
|
103
|
-
[z().constants.BROTLI_PARAM_SIZE_HINT]: buf.length,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
136
|
+
return brotliAsync(buf, brotliOptions(buf.length, level));
|
|
106
137
|
case 'gzip':
|
|
107
138
|
return gzipAsync(buf, { level });
|
|
108
139
|
case 'deflate':
|
|
109
140
|
return deflateAsync(buf, { level });
|
|
110
141
|
}
|
|
111
142
|
}
|
|
143
|
+
/** Compress a buffer/string on the calling thread (see shouldInline). */
|
|
144
|
+
export function compressBufferSync(data, encoding, level = 6) {
|
|
145
|
+
const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
146
|
+
const zlib = z();
|
|
147
|
+
switch (encoding) {
|
|
148
|
+
case 'br':
|
|
149
|
+
return zlib.brotliCompressSync(buf, brotliOptions(buf.length, level));
|
|
150
|
+
case 'gzip':
|
|
151
|
+
return zlib.gzipSync(buf, { level });
|
|
152
|
+
case 'deflate':
|
|
153
|
+
return zlib.deflateSync(buf, { level });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
112
156
|
/**
|
|
113
157
|
* The one-call helper the servers use: decide whether to compress `body`
|
|
114
158
|
* given the response content type, the client's Accept-Encoding, and the
|
|
@@ -128,7 +172,9 @@ export async function maybeCompress(body, contentType, acceptEncoding, settings)
|
|
|
128
172
|
if (!encoding)
|
|
129
173
|
return { body, encoding: null };
|
|
130
174
|
try {
|
|
131
|
-
const compressed =
|
|
175
|
+
const compressed = shouldInline(byteLen, settings)
|
|
176
|
+
? compressBufferSync(body, encoding, settings.level)
|
|
177
|
+
: await compressBuffer(body, encoding, settings.level);
|
|
132
178
|
return { body: compressed, encoding };
|
|
133
179
|
}
|
|
134
180
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../../../../src/core/http/utils/compression.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared response compression, used by every runtime (Node, uWS, HTTP/2, and\n * the native engine) so switching engines never silently drops compression.\n *\n * Scope: BUFFERED responses only (json/send/end with a full body). The\n * streaming path (writeHead + write + write + end) stays uncompressed on all\n * runtimes — per-chunk flush semantics would break SSE latency and the\n * engine's Content-Length short-write detection. That is a deliberate,\n * documented non-goal.\n */\nimport { promisify } from 'util';\nimport { requireBuiltin } from '../../utilities/builtin.js';\n\n// zlib is loaded on the first compressed response, not at startup\n// (core/utilities/builtin.ts): compression is off by default.\nconst z = () => requireBuiltin<typeof import('zlib')>('zlib');\ntype Compressor = (buf: Buffer, opts: any) => Promise<Buffer>;\nlet gzipFn: Compressor | undefined;\nlet deflateFn: Compressor | undefined;\nlet brotliFn: Compressor | undefined;\nconst gzipAsync: Compressor = (buf, opts) =>\n (gzipFn ??= promisify(z().gzip) as unknown as Compressor)(buf, opts);\nconst deflateAsync: Compressor = (buf, opts) =>\n (deflateFn ??= promisify(z().deflate) as unknown as Compressor)(buf, opts);\nconst brotliAsync: Compressor = (buf, opts) =>\n (brotliFn ??= promisify(z().brotliCompress) as unknown as Compressor)(buf, opts);\n\nexport type Encoding = 'br' | 'gzip' | 'deflate';\n\nexport interface CompressionSettings {\n enabled: boolean;\n threshold: number;\n level: number; // 1-9 (mapped to brotli quality)\n encodings: Encoding[]; // server preference order\n}\n\nexport const DEFAULT_ENCODINGS: Encoding[] = ['br', 'gzip', 'deflate'];\n\n/**\n * Build CompressionSettings from the framework's performance config (the\n * shape passed to every server's configurePerformance). Shared so all four\n * runtimes interpret `performance.compression` identically.\n */\nexport function resolveCompressionSettings(config?: {\n compression?: { enabled?: boolean; threshold?: number; level?: number; encodings?: Encoding[] };\n minimal?: boolean;\n}): CompressionSettings {\n const c = config?.compression;\n // Minimal mode disables all response overhead for pure throughput.\n const enabled = config?.minimal ? false : c?.enabled !== false && c !== undefined;\n return {\n enabled,\n threshold: c?.threshold ?? 1024,\n level: c?.level ?? 6,\n encodings: c?.encodings ?? DEFAULT_ENCODINGS,\n };\n}\n\n/**\n * Content types worth compressing. Text and structured formats yes; already\n * compressed binary (images, video, zip, octet-stream) no.\n */\nexport function isCompressible(contentType: string | undefined): boolean {\n if (!contentType) return false;\n const ct = (contentType.split(';')[0] ?? '').trim().toLowerCase();\n if (ct.startsWith('text/')) return true;\n if (ct.endsWith('+json') || ct.endsWith('+xml')) return true;\n return (\n ct === 'application/json' ||\n ct === 'application/javascript' ||\n ct === 'application/xml' ||\n ct === 'application/ld+json' ||\n ct === 'application/manifest+json' ||\n ct === 'image/svg+xml'\n );\n}\n\n/**\n * Pick the best encoding the client accepts from the server's preference\n * list, honoring q-values (q=0 excludes). Returns null when the client\n * accepts none / only identity.\n */\nexport function negotiateEncoding(\n acceptEncoding: string | undefined,\n allowed: Encoding[] = DEFAULT_ENCODINGS\n): Encoding | null {\n if (!acceptEncoding) return null;\n\n // Parse \"gzip;q=0.8, br, deflate;q=0\" into a q-value map.\n const q = new Map<string, number>();\n for (const part of acceptEncoding.split(',')) {\n const [tokenRaw, ...params] = part.trim().split(';');\n const token = (tokenRaw ?? '').trim().toLowerCase();\n if (!token) continue;\n let weight = 1;\n for (const p of params) {\n const m = /^\\s*q=([0-9.]+)\\s*$/i.exec(p);\n if (m && m[1] !== undefined) weight = parseFloat(m[1]);\n }\n q.set(token, weight);\n }\n\n const wildcard = q.get('*');\n const accepts = (enc: Encoding): boolean => {\n if (q.has(enc)) return (q.get(enc) as number) > 0;\n if (wildcard !== undefined) return wildcard > 0;\n return false;\n };\n\n for (const enc of allowed) {\n if (accepts(enc)) return enc;\n }\n return null;\n}\n\n/** Compress a buffer/string with the chosen encoding. */\nexport async function compressBuffer(\n data: Buffer | string,\n encoding: Encoding,\n level = 6\n): Promise<Buffer> {\n const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;\n switch (encoding) {\n case 'br':\n return brotliAsync(buf, {\n params: {\n [z().constants.BROTLI_PARAM_QUALITY]: Math.max(\n 0,\n Math.min(11, Math.round((level * 11) / 9))\n ),\n [z().constants.BROTLI_PARAM_SIZE_HINT]: buf.length,\n },\n });\n case 'gzip':\n return gzipAsync(buf, { level });\n case 'deflate':\n return deflateAsync(buf, { level });\n }\n}\n\nexport interface MaybeCompressResult {\n body: Buffer | string;\n encoding: Encoding | null;\n}\n\n/**\n * The one-call helper the servers use: decide whether to compress `body`\n * given the response content type, the client's Accept-Encoding, and the\n * server's compression settings. Returns the (possibly compressed) body plus\n * the chosen encoding (null = sent as-is). Never throws — a compression error\n * degrades to the original body.\n */\nexport async function maybeCompress(\n body: Buffer | string,\n contentType: string | undefined,\n acceptEncoding: string | undefined,\n settings: CompressionSettings\n): Promise<MaybeCompressResult> {\n if (!settings.enabled) return { body, encoding: null };\n const byteLen = typeof body === 'string' ? Buffer.byteLength(body) : body.length;\n if (byteLen < settings.threshold) return { body, encoding: null };\n if (!isCompressible(contentType)) return { body, encoding: null };\n\n const encoding = negotiateEncoding(acceptEncoding, settings.encodings);\n if (!encoding) return { body, encoding: null };\n\n try {\n const compressed = await compressBuffer(body, encoding, settings.level);\n return { body: compressed, encoding };\n } catch {\n return { body, encoding: null };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"compression.js","sourceRoot":"","sources":["../../../../src/core/http/utils/compression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,kEAAkE;AAClE,8DAA8D;AAC9D,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC,cAAc,CAAwB,MAAM,CAAC,CAAC;AAE9D,IAAI,MAA8B,CAAC;AACnC,IAAI,SAAiC,CAAC;AACtC,IAAI,QAAgC,CAAC;AACrC,MAAM,SAAS,GAAe,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAC1C,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,IAAI,CAA0B,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACvE,MAAM,YAAY,GAAe,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAC7C,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,OAAO,CAA0B,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC7E,MAAM,WAAW,GAAe,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAC5C,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,cAAc,CAA0B,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAanF,MAAM,CAAC,MAAM,iBAAiB,GAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAS1C;IACC,MAAM,CAAC,GAAG,MAAM,EAAE,WAAW,CAAC;IAC9B,mEAAmE;IACnE,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,KAAK,SAAS,CAAC;IAClF,OAAO;QACL,OAAO;QACP,SAAS,EAAE,CAAC,EAAE,SAAS,IAAI,IAAI;QAC/B,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;QACpB,SAAS,EAAE,CAAC,EAAE,SAAS,IAAI,iBAAiB;QAC5C,cAAc,EAAE,CAAC,EAAE,cAAc,IAAI,wBAAwB;KAC9D,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAAoC;IAC9E,OAAO,QAAQ,CAAC,cAAc,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ,CAAC,cAAc,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,MAAc,EAAE,KAAa;IAClD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,CAAC,sBAAsB,EACxB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,qBAAqB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CACjF,CAAC;IACF,OAAO;QACL,MAAM,EAAE;YACN,CAAC,CAAC,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjF,CAAC,CAAC,CAAC,sBAAsB,CAAC,EAAE,MAAM;YAClC,CAAC,CAAC,CAAC,kBAAkB,CAAC,EAAE,KAAK;SAC9B;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,WAA+B;IAC5D,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,EAAE,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClE,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7D,OAAO,CACL,EAAE,KAAK,kBAAkB;QACzB,EAAE,KAAK,wBAAwB;QAC/B,EAAE,KAAK,iBAAiB;QACxB,EAAE,KAAK,qBAAqB;QAC5B,EAAE,KAAK,2BAA2B;QAClC,EAAE,KAAK,eAAe,CACvB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,cAAkC,EAClC,UAAsB,iBAAiB;IAEvC,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEjC,0DAA0D;IAC1D,MAAM,CAAC,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpD,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;gBAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,CAAC,GAAa,EAAW,EAAE;QACzC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAY,GAAG,CAAC,CAAC;QAClD,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,GAAG,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAqB,EACrB,QAAkB,EAClB,KAAK,GAAG,CAAC;IAET,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,WAAW,CAAC,GAAG,EAAE,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QAC5D,KAAK,MAAM;YACT,OAAO,SAAS,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACnC,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACxC,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,IAAqB,EAAE,QAAkB,EAAE,KAAK,GAAG,CAAC;IACrF,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,MAAM,IAAI,GAAG,CAAC,EAAE,CAAC;IACjB,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QACxE,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACvC,KAAK,SAAS;YACZ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAOD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAqB,EACrB,WAA+B,EAC/B,cAAkC,EAClC,QAA6B;IAE7B,IAAI,CAAC,QAAQ,CAAC,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvD,MAAM,OAAO,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IACjF,IAAI,OAAO,GAAG,QAAQ,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAClE,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAElE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,cAAc,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IACvE,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAE/C,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;YAChD,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC;YACpD,CAAC,CAAC,MAAM,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QACzD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAClC,CAAC;AACH,CAAC","sourcesContent":["/**\n * Shared response compression, used by every runtime (Node, uWS, HTTP/2, and\n * the native engine) so switching engines never silently drops compression.\n *\n * Scope: BUFFERED responses only (json/send/end with a full body). The\n * streaming path (writeHead + write + write + end) stays uncompressed on all\n * runtimes — per-chunk flush semantics would break SSE latency and the\n * engine's Content-Length short-write detection. That is a deliberate,\n * documented non-goal.\n */\nimport { promisify } from 'util';\nimport { requireBuiltin } from '../../utilities/builtin.js';\n\n// zlib is loaded on the first compressed response, not at startup\n// (core/utilities/builtin.ts): compression is off by default.\nconst z = () => requireBuiltin<typeof import('zlib')>('zlib');\ntype Compressor = (buf: Buffer, opts: any) => Promise<Buffer>;\nlet gzipFn: Compressor | undefined;\nlet deflateFn: Compressor | undefined;\nlet brotliFn: Compressor | undefined;\nconst gzipAsync: Compressor = (buf, opts) =>\n (gzipFn ??= promisify(z().gzip) as unknown as Compressor)(buf, opts);\nconst deflateAsync: Compressor = (buf, opts) =>\n (deflateFn ??= promisify(z().deflate) as unknown as Compressor)(buf, opts);\nconst brotliAsync: Compressor = (buf, opts) =>\n (brotliFn ??= promisify(z().brotliCompress) as unknown as Compressor)(buf, opts);\n\nexport type Encoding = 'br' | 'gzip' | 'deflate';\n\nexport interface CompressionSettings {\n enabled: boolean;\n threshold: number;\n level: number; // 1-9 (mapped to brotli quality)\n encodings: Encoding[]; // server preference order\n /** Bodies at or below this size compress on the calling thread (see shouldInline). */\n maxInlineBytes: number;\n}\n\nexport const DEFAULT_ENCODINGS: Encoding[] = ['br', 'gzip', 'deflate'];\n\n/**\n * 16 KB: gzip at the default level takes well under a millisecond on a body\n * this size, less than the two thread handoffs the pool round trip costs,\n * while a body many times larger would hold the event loop for longer than\n * the requests queued behind it should wait.\n */\nexport const DEFAULT_MAX_INLINE_BYTES = 16 * 1024;\n\n/**\n * Build CompressionSettings from the framework's performance config (the\n * shape passed to every server's configurePerformance). Shared so all four\n * runtimes interpret `performance.compression` identically.\n */\nexport function resolveCompressionSettings(config?: {\n compression?: {\n enabled?: boolean;\n threshold?: number;\n level?: number;\n encodings?: Encoding[];\n maxInlineBytes?: number;\n };\n minimal?: boolean;\n}): CompressionSettings {\n const c = config?.compression;\n // Minimal mode disables all response overhead for pure throughput.\n const enabled = config?.minimal ? false : c?.enabled !== false && c !== undefined;\n return {\n enabled,\n threshold: c?.threshold ?? 1024,\n level: c?.level ?? 6,\n encodings: c?.encodings ?? DEFAULT_ENCODINGS,\n maxInlineBytes: c?.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES,\n };\n}\n\n/**\n * Whether a body of `bytes` is compressed on the calling thread rather than\n * on the threadpool. Inline is cheaper for small bodies (no handoff, no\n * promise, no wake-up) and lets the response carry a Content-Length instead\n * of streaming chunked. `maxInlineBytes: 0` sends everything through the pool.\n */\nexport function shouldInline(bytes: number, settings: { maxInlineBytes: number }): boolean {\n return settings.maxInlineBytes > 0 && bytes <= settings.maxInlineBytes;\n}\n\n/**\n * Brotli options for one body: quality mapped from the 1-9 level, the size\n * hint, and a window no larger than the input needs. The default window\n * (2^22 bytes) has the encoder allocate and clear several megabytes per call,\n * which for a response of a few kilobytes is most of its cost; a window that\n * covers the whole input compresses it identically, since no match can\n * reach back further than the input is long.\n */\nfunction brotliOptions(length: number, level: number) {\n const c = z().constants;\n const lgwin = Math.max(\n c.BROTLI_MIN_WINDOW_BITS,\n Math.min(c.BROTLI_DEFAULT_WINDOW, Math.ceil(Math.log2(Math.max(length, 1))) + 1)\n );\n return {\n params: {\n [c.BROTLI_PARAM_QUALITY]: Math.max(0, Math.min(11, Math.round((level * 11) / 9))),\n [c.BROTLI_PARAM_SIZE_HINT]: length,\n [c.BROTLI_PARAM_LGWIN]: lgwin,\n },\n };\n}\n\n/**\n * Content types worth compressing. Text and structured formats yes; already\n * compressed binary (images, video, zip, octet-stream) no.\n */\nexport function isCompressible(contentType: string | undefined): boolean {\n if (!contentType) return false;\n const ct = (contentType.split(';')[0] ?? '').trim().toLowerCase();\n if (ct.startsWith('text/')) return true;\n if (ct.endsWith('+json') || ct.endsWith('+xml')) return true;\n return (\n ct === 'application/json' ||\n ct === 'application/javascript' ||\n ct === 'application/xml' ||\n ct === 'application/ld+json' ||\n ct === 'application/manifest+json' ||\n ct === 'image/svg+xml'\n );\n}\n\n/**\n * Pick the best encoding the client accepts from the server's preference\n * list, honoring q-values (q=0 excludes). Returns null when the client\n * accepts none / only identity.\n */\nexport function negotiateEncoding(\n acceptEncoding: string | undefined,\n allowed: Encoding[] = DEFAULT_ENCODINGS\n): Encoding | null {\n if (!acceptEncoding) return null;\n\n // Parse \"gzip;q=0.8, br, deflate;q=0\" into a q-value map.\n const q = new Map<string, number>();\n for (const part of acceptEncoding.split(',')) {\n const [tokenRaw, ...params] = part.trim().split(';');\n const token = (tokenRaw ?? '').trim().toLowerCase();\n if (!token) continue;\n let weight = 1;\n for (const p of params) {\n const m = /^\\s*q=([0-9.]+)\\s*$/i.exec(p);\n if (m && m[1] !== undefined) weight = parseFloat(m[1]);\n }\n q.set(token, weight);\n }\n\n const wildcard = q.get('*');\n const accepts = (enc: Encoding): boolean => {\n if (q.has(enc)) return (q.get(enc) as number) > 0;\n if (wildcard !== undefined) return wildcard > 0;\n return false;\n };\n\n for (const enc of allowed) {\n if (accepts(enc)) return enc;\n }\n return null;\n}\n\n/** Compress a buffer/string with the chosen encoding. */\nexport async function compressBuffer(\n data: Buffer | string,\n encoding: Encoding,\n level = 6\n): Promise<Buffer> {\n const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;\n switch (encoding) {\n case 'br':\n return brotliAsync(buf, brotliOptions(buf.length, level));\n case 'gzip':\n return gzipAsync(buf, { level });\n case 'deflate':\n return deflateAsync(buf, { level });\n }\n}\n\n/** Compress a buffer/string on the calling thread (see shouldInline). */\nexport function compressBufferSync(data: Buffer | string, encoding: Encoding, level = 6): Buffer {\n const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;\n const zlib = z();\n switch (encoding) {\n case 'br':\n return zlib.brotliCompressSync(buf, brotliOptions(buf.length, level));\n case 'gzip':\n return zlib.gzipSync(buf, { level });\n case 'deflate':\n return zlib.deflateSync(buf, { level });\n }\n}\n\nexport interface MaybeCompressResult {\n body: Buffer | string;\n encoding: Encoding | null;\n}\n\n/**\n * The one-call helper the servers use: decide whether to compress `body`\n * given the response content type, the client's Accept-Encoding, and the\n * server's compression settings. Returns the (possibly compressed) body plus\n * the chosen encoding (null = sent as-is). Never throws — a compression error\n * degrades to the original body.\n */\nexport async function maybeCompress(\n body: Buffer | string,\n contentType: string | undefined,\n acceptEncoding: string | undefined,\n settings: CompressionSettings\n): Promise<MaybeCompressResult> {\n if (!settings.enabled) return { body, encoding: null };\n const byteLen = typeof body === 'string' ? Buffer.byteLength(body) : body.length;\n if (byteLen < settings.threshold) return { body, encoding: null };\n if (!isCompressible(contentType)) return { body, encoding: null };\n\n const encoding = negotiateEncoding(acceptEncoding, settings.encodings);\n if (!encoding) return { body, encoding: null };\n\n try {\n const compressed = shouldInline(byteLen, settings)\n ? compressBufferSync(body, encoding, settings.level)\n : await compressBuffer(body, encoding, settings.level);\n return { body: compressed, encoding };\n } catch {\n return { body, encoding: null };\n }\n}\n"]}
|
|
@@ -14,6 +14,14 @@ export interface SSLConfigInput {
|
|
|
14
14
|
minVersion?: 'TLSv1.2' | 'TLSv1.3';
|
|
15
15
|
requestCert?: boolean;
|
|
16
16
|
rejectUnauthorized?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Watch the key/cert (and CA) files and reload the certificate when they
|
|
19
|
+
* change (`app.reloadTLS()` under the hood), the way an ACME client rotates
|
|
20
|
+
* them. File-path shape only. `true` = 1000 ms debounce.
|
|
21
|
+
*/
|
|
22
|
+
watch?: boolean | {
|
|
23
|
+
debounceMs?: number;
|
|
24
|
+
};
|
|
17
25
|
}
|
|
18
26
|
/** Canonical internal SSL config after normalization. */
|
|
19
27
|
export interface NormalizedSSLConfig {
|
|
@@ -65,5 +73,8 @@ export declare function sslForUws(ssl: NormalizedSSLConfig): {
|
|
|
65
73
|
* Project for the native @morojs/engine serve() options.ssl, which accepts
|
|
66
74
|
* BOTH shapes (file paths and inline PEM) and prefers inline. Pass everything
|
|
67
75
|
* through; the engine decides.
|
|
76
|
+
*
|
|
77
|
+
* The engine takes ONE CA per slot (`ca_file_name` / `ca`), so only the first
|
|
78
|
+
* CA of a list reaches it; the Node projection keeps the full list.
|
|
68
79
|
*/
|
|
69
80
|
export declare function sslForEngine(ssl: NormalizedSSLConfig): Record<string, unknown>;
|
|
@@ -88,6 +88,9 @@ export function sslForUws(ssl) {
|
|
|
88
88
|
* Project for the native @morojs/engine serve() options.ssl, which accepts
|
|
89
89
|
* BOTH shapes (file paths and inline PEM) and prefers inline. Pass everything
|
|
90
90
|
* through; the engine decides.
|
|
91
|
+
*
|
|
92
|
+
* The engine takes ONE CA per slot (`ca_file_name` / `ca`), so only the first
|
|
93
|
+
* CA of a list reaches it; the Node projection keeps the full list.
|
|
91
94
|
*/
|
|
92
95
|
export function sslForEngine(ssl) {
|
|
93
96
|
const out = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssl-config.js","sourceRoot":"","sources":["../../../../src/core/http/utils/ssl-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAoClC,MAAM,OAAO,GAAG,CAAI,CAAsB,EAAmB,EAAE,CAC7D,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAiC,EACjC,YAAoC,EACpC,MAAmB;IAEnB,IAAI,SAAS,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,EAAE,IAAI,CAAC,wEAAwE,EAAE,KAAK,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,IAAI,YAAY,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,GAAG,GAAwB;QAC/B,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,aAAa;QACzC,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,cAAc;QAC5C,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,YAAY,CAAC;QAC/C,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;KAC3C,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,CAAC,4BAA4B;IAClE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,aAAa,CAAC,GAAwB;IACpD,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAwB;IASjD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACjF,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACrF,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,OAAO;QACL,GAAG;QACH,IAAI;QACJ,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACjB,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;QACtE,GAAG,CAAC,GAAG,CAAC,kBAAkB,KAAK,SAAS,IAAI;YAC1C,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;SAC3C,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAwB;IAMhD,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO;QACL,aAAa,EAAE,GAAG,CAAC,OAAO;QAC1B,cAAc,EAAE,GAAG,CAAC,QAAQ;QAC5B,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAwB;IACnD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,IAAI,GAAG,CAAC,OAAO;QAAE,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC;IACjD,IAAI,GAAG,CAAC,QAAQ;QAAE,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC,QAAQ,CAAC;IACpD,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,GAAG,CAAC,GAAG;QAAE,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IAC/B,IAAI,GAAG,CAAC,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IAClC,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAAE,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,GAAG,CAAC,UAAU;QAAE,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;IACpD,IAAI,GAAG,CAAC,UAAU;QAAE,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;IACpD,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;IACrE,IAAI,GAAG,CAAC,kBAAkB,KAAK,SAAS;QAAE,GAAG,CAAC,kBAAkB,GAAG,GAAG,CAAC,kBAAkB,CAAC;IAC1F,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Unified SSL/TLS configuration.\n *\n * MoroJS historically carried TWO incompatible SSL shapes:\n * - `config.server.ssl` = uWS-style file paths { key_file_name, cert_file_name, passphrase }\n * - `options.https` = node-style buffers { key, cert, ca }\n * and each reached only one runtime (uWS / http2 respectively). This module\n * normalizes both into ONE internal shape and projects it back out for each\n * of the four servers (Node https, uWS SSLApp, HTTP/2, the native engine), so\n * a single `server.ssl` config flows everywhere.\n */\nimport { readFileSync } from 'fs';\nimport type { MoroLogger } from '../../logger/index.js';\n\n/** Raw, un-normalized SSL config as a user may supply it (either shape). */\nexport interface SSLConfigInput {\n // File-path shape (uWS-style; also accepted node-style keyFile/certFile)\n key_file_name?: string;\n cert_file_name?: string;\n ca_file_name?: string | string[];\n keyFile?: string;\n certFile?: string;\n caFile?: string | string[];\n // Inline shape (node-style)\n key?: string | Buffer;\n cert?: string | Buffer;\n ca?: string | Buffer | Array<string | Buffer>;\n passphrase?: string;\n minVersion?: 'TLSv1.2' | 'TLSv1.3';\n requestCert?: boolean;\n rejectUnauthorized?: boolean;\n}\n\n/** Canonical internal SSL config after normalization. */\nexport interface NormalizedSSLConfig {\n keyFile?: string | undefined;\n certFile?: string | undefined;\n caFile?: string[] | undefined;\n key?: string | Buffer | undefined;\n cert?: string | Buffer | undefined;\n ca?: Array<string | Buffer> | undefined;\n passphrase?: string | undefined;\n minVersion?: 'TLSv1.2' | 'TLSv1.3' | undefined;\n requestCert?: boolean | undefined;\n rejectUnauthorized?: boolean | undefined;\n}\n\nconst toArray = <T>(v: T | T[] | undefined): T[] | undefined =>\n v === undefined ? undefined : Array.isArray(v) ? v : [v];\n\n/**\n * Normalize the two historical SSL shapes into one. `serverSsl`\n * (config.server.ssl) wins over `optionsHttps` (options.https) when both are\n * present, matching the pre-existing precedence in framework.ts. Returns null\n * when neither carries any key/cert material (TLS simply off).\n */\nexport function normalizeSSLConfig(\n serverSsl?: SSLConfigInput | null,\n optionsHttps?: SSLConfigInput | null,\n logger?: MoroLogger\n): NormalizedSSLConfig | null {\n if (serverSsl && optionsHttps) {\n logger?.warn('Both server.ssl and options.https are set; server.ssl takes precedence', 'SSL');\n }\n const src = serverSsl ?? optionsHttps;\n if (!src) return null;\n\n const out: NormalizedSSLConfig = {\n keyFile: src.keyFile ?? src.key_file_name,\n certFile: src.certFile ?? src.cert_file_name,\n caFile: toArray(src.caFile ?? src.ca_file_name),\n key: src.key,\n cert: src.cert,\n ca: toArray(src.ca),\n passphrase: src.passphrase,\n minVersion: src.minVersion,\n requestCert: src.requestCert,\n rejectUnauthorized: src.rejectUnauthorized,\n };\n\n const hasKey = Boolean(out.key || out.keyFile);\n const hasCert = Boolean(out.cert || out.certFile);\n if (!hasKey && !hasCert) return null; // nothing usable -> TLS off\n return out;\n}\n\n/** True when the config actually carries a key+cert (complete TLS material). */\nexport function sslIsComplete(ssl: NormalizedSSLConfig): boolean {\n return Boolean((ssl.key || ssl.keyFile) && (ssl.cert || ssl.certFile));\n}\n\n/**\n * Project for the Node http/https and HTTP/2 servers: node's tls API wants\n * key/cert/ca as strings or Buffers, so any file paths are read here.\n */\nexport function sslForNode(ssl: NormalizedSSLConfig): {\n key: string | Buffer;\n cert: string | Buffer;\n ca?: Array<string | Buffer>;\n passphrase?: string;\n minVersion?: string;\n requestCert?: boolean;\n rejectUnauthorized?: boolean;\n} {\n const key = ssl.key ?? (ssl.keyFile ? readFileSync(ssl.keyFile) : undefined);\n const cert = ssl.cert ?? (ssl.certFile ? readFileSync(ssl.certFile) : undefined);\n const ca = ssl.ca ?? (ssl.caFile ? ssl.caFile.map(p => readFileSync(p)) : undefined);\n if (!key || !cert) {\n throw new Error('sslForNode requires both a key and a certificate');\n }\n return {\n key,\n cert,\n ...(ca && { ca }),\n ...(ssl.passphrase && { passphrase: ssl.passphrase }),\n ...(ssl.minVersion && { minVersion: ssl.minVersion }),\n ...(ssl.requestCert !== undefined && { requestCert: ssl.requestCert }),\n ...(ssl.rejectUnauthorized !== undefined && {\n rejectUnauthorized: ssl.rejectUnauthorized,\n }),\n };\n}\n\n/**\n * Project for uWebSockets.js SSLApp, which requires FILE PATHS (it has no\n * inline-PEM option). Returns null when only inline PEM is available so the\n * caller can log a clear error instead of silently booting plain HTTP.\n */\nexport function sslForUws(ssl: NormalizedSSLConfig): {\n key_file_name: string;\n cert_file_name: string;\n ca_file_name?: string;\n passphrase?: string;\n} | null {\n if (!ssl.keyFile || !ssl.certFile) return null;\n return {\n key_file_name: ssl.keyFile,\n cert_file_name: ssl.certFile,\n ...(ssl.caFile && ssl.caFile[0] && { ca_file_name: ssl.caFile[0] }),\n ...(ssl.passphrase && { passphrase: ssl.passphrase }),\n };\n}\n\n/**\n * Project for the native @morojs/engine serve() options.ssl, which accepts\n * BOTH shapes (file paths and inline PEM) and prefers inline. Pass everything\n * through; the engine decides.\n */\nexport function sslForEngine(ssl: NormalizedSSLConfig): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (ssl.keyFile) out.key_file_name = ssl.keyFile;\n if (ssl.certFile) out.cert_file_name = ssl.certFile;\n if (ssl.caFile && ssl.caFile[0]) out.ca_file_name = ssl.caFile[0];\n if (ssl.key) out.key = ssl.key;\n if (ssl.cert) out.cert = ssl.cert;\n if (ssl.ca && ssl.ca[0]) out.ca = ssl.ca[0];\n if (ssl.passphrase) out.passphrase = ssl.passphrase;\n if (ssl.minVersion) out.minVersion = ssl.minVersion;\n if (ssl.requestCert !== undefined) out.requestCert = ssl.requestCert;\n if (ssl.rejectUnauthorized !== undefined) out.rejectUnauthorized = ssl.rejectUnauthorized;\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ssl-config.js","sourceRoot":"","sources":["../../../../src/core/http/utils/ssl-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unified SSL/TLS configuration.\n *\n * MoroJS historically carried TWO incompatible SSL shapes:\n * - `config.server.ssl` = uWS-style file paths { key_file_name, cert_file_name, passphrase }\n * - `options.https` = node-style buffers { key, cert, ca }\n * and each reached only one runtime (uWS / http2 respectively). This module\n * normalizes both into ONE internal shape and projects it back out for each\n * of the four servers (Node https, uWS SSLApp, HTTP/2, the native engine), so\n * a single `server.ssl` config flows everywhere.\n */\nimport { readFileSync } from 'fs';\nimport type { MoroLogger } from '../../logger/index.js';\n\n/** Raw, un-normalized SSL config as a user may supply it (either shape). */\nexport interface SSLConfigInput {\n // File-path shape (uWS-style; also accepted node-style keyFile/certFile)\n key_file_name?: string;\n cert_file_name?: string;\n ca_file_name?: string | string[];\n keyFile?: string;\n certFile?: string;\n caFile?: string | string[];\n // Inline shape (node-style)\n key?: string | Buffer;\n cert?: string | Buffer;\n ca?: string | Buffer | Array<string | Buffer>;\n passphrase?: string;\n minVersion?: 'TLSv1.2' | 'TLSv1.3';\n requestCert?: boolean;\n rejectUnauthorized?: boolean;\n /**\n * Watch the key/cert (and CA) files and reload the certificate when they\n * change (`app.reloadTLS()` under the hood), the way an ACME client rotates\n * them. File-path shape only. `true` = 1000 ms debounce.\n */\n watch?: boolean | { debounceMs?: number };\n}\n\n/** Canonical internal SSL config after normalization. */\nexport interface NormalizedSSLConfig {\n keyFile?: string | undefined;\n certFile?: string | undefined;\n caFile?: string[] | undefined;\n key?: string | Buffer | undefined;\n cert?: string | Buffer | undefined;\n ca?: Array<string | Buffer> | undefined;\n passphrase?: string | undefined;\n minVersion?: 'TLSv1.2' | 'TLSv1.3' | undefined;\n requestCert?: boolean | undefined;\n rejectUnauthorized?: boolean | undefined;\n}\n\nconst toArray = <T>(v: T | T[] | undefined): T[] | undefined =>\n v === undefined ? undefined : Array.isArray(v) ? v : [v];\n\n/**\n * Normalize the two historical SSL shapes into one. `serverSsl`\n * (config.server.ssl) wins over `optionsHttps` (options.https) when both are\n * present, matching the pre-existing precedence in framework.ts. Returns null\n * when neither carries any key/cert material (TLS simply off).\n */\nexport function normalizeSSLConfig(\n serverSsl?: SSLConfigInput | null,\n optionsHttps?: SSLConfigInput | null,\n logger?: MoroLogger\n): NormalizedSSLConfig | null {\n if (serverSsl && optionsHttps) {\n logger?.warn('Both server.ssl and options.https are set; server.ssl takes precedence', 'SSL');\n }\n const src = serverSsl ?? optionsHttps;\n if (!src) return null;\n\n const out: NormalizedSSLConfig = {\n keyFile: src.keyFile ?? src.key_file_name,\n certFile: src.certFile ?? src.cert_file_name,\n caFile: toArray(src.caFile ?? src.ca_file_name),\n key: src.key,\n cert: src.cert,\n ca: toArray(src.ca),\n passphrase: src.passphrase,\n minVersion: src.minVersion,\n requestCert: src.requestCert,\n rejectUnauthorized: src.rejectUnauthorized,\n };\n\n const hasKey = Boolean(out.key || out.keyFile);\n const hasCert = Boolean(out.cert || out.certFile);\n if (!hasKey && !hasCert) return null; // nothing usable -> TLS off\n return out;\n}\n\n/** True when the config actually carries a key+cert (complete TLS material). */\nexport function sslIsComplete(ssl: NormalizedSSLConfig): boolean {\n return Boolean((ssl.key || ssl.keyFile) && (ssl.cert || ssl.certFile));\n}\n\n/**\n * Project for the Node http/https and HTTP/2 servers: node's tls API wants\n * key/cert/ca as strings or Buffers, so any file paths are read here.\n */\nexport function sslForNode(ssl: NormalizedSSLConfig): {\n key: string | Buffer;\n cert: string | Buffer;\n ca?: Array<string | Buffer>;\n passphrase?: string;\n minVersion?: string;\n requestCert?: boolean;\n rejectUnauthorized?: boolean;\n} {\n const key = ssl.key ?? (ssl.keyFile ? readFileSync(ssl.keyFile) : undefined);\n const cert = ssl.cert ?? (ssl.certFile ? readFileSync(ssl.certFile) : undefined);\n const ca = ssl.ca ?? (ssl.caFile ? ssl.caFile.map(p => readFileSync(p)) : undefined);\n if (!key || !cert) {\n throw new Error('sslForNode requires both a key and a certificate');\n }\n return {\n key,\n cert,\n ...(ca && { ca }),\n ...(ssl.passphrase && { passphrase: ssl.passphrase }),\n ...(ssl.minVersion && { minVersion: ssl.minVersion }),\n ...(ssl.requestCert !== undefined && { requestCert: ssl.requestCert }),\n ...(ssl.rejectUnauthorized !== undefined && {\n rejectUnauthorized: ssl.rejectUnauthorized,\n }),\n };\n}\n\n/**\n * Project for uWebSockets.js SSLApp, which requires FILE PATHS (it has no\n * inline-PEM option). Returns null when only inline PEM is available so the\n * caller can log a clear error instead of silently booting plain HTTP.\n */\nexport function sslForUws(ssl: NormalizedSSLConfig): {\n key_file_name: string;\n cert_file_name: string;\n ca_file_name?: string;\n passphrase?: string;\n} | null {\n if (!ssl.keyFile || !ssl.certFile) return null;\n return {\n key_file_name: ssl.keyFile,\n cert_file_name: ssl.certFile,\n ...(ssl.caFile && ssl.caFile[0] && { ca_file_name: ssl.caFile[0] }),\n ...(ssl.passphrase && { passphrase: ssl.passphrase }),\n };\n}\n\n/**\n * Project for the native @morojs/engine serve() options.ssl, which accepts\n * BOTH shapes (file paths and inline PEM) and prefers inline. Pass everything\n * through; the engine decides.\n *\n * The engine takes ONE CA per slot (`ca_file_name` / `ca`), so only the first\n * CA of a list reaches it; the Node projection keeps the full list.\n */\nexport function sslForEngine(ssl: NormalizedSSLConfig): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (ssl.keyFile) out.key_file_name = ssl.keyFile;\n if (ssl.certFile) out.cert_file_name = ssl.certFile;\n if (ssl.caFile && ssl.caFile[0]) out.ca_file_name = ssl.caFile[0];\n if (ssl.key) out.key = ssl.key;\n if (ssl.cert) out.cert = ssl.cert;\n if (ssl.ca && ssl.ca[0]) out.ca = ssl.ca[0];\n if (ssl.passphrase) out.passphrase = ssl.passphrase;\n if (ssl.minVersion) out.minVersion = ssl.minVersion;\n if (ssl.requestCert !== undefined) out.requestCert = ssl.requestCert;\n if (ssl.rejectUnauthorized !== undefined) out.rejectUnauthorized = ssl.rejectUnauthorized;\n return out;\n}\n"]}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { requireBuiltin } from '../../utilities/builtin.js';
|
|
11
11
|
// worker_threads is loaded on first use (core/utilities/builtin.ts).
|
|
12
12
|
const wt = () => requireBuiltin('worker_threads');
|
|
13
|
-
import
|
|
13
|
+
import { usableCpuCount, usableMemoryBytes } from '../../cluster/thread-cluster.js';
|
|
14
14
|
import { createFrameworkLogger } from '../../logger/index.js';
|
|
15
15
|
import { loadNativeEngine } from '../../utilities/package-utils.js';
|
|
16
16
|
const logger = createFrameworkLogger('UWSClustering');
|
|
@@ -27,10 +27,11 @@ export class UWSWorkerClusterManager {
|
|
|
27
27
|
this.workerCount = this.calculateWorkerCount();
|
|
28
28
|
}
|
|
29
29
|
calculateWorkerCount() {
|
|
30
|
-
|
|
30
|
+
// Container-aware counts (cgroup quota / affinity mask), not the host's
|
|
31
|
+
let workerCount = this.config.workers || usableCpuCount();
|
|
31
32
|
if (workerCount === 'auto') {
|
|
32
|
-
const cpuCount =
|
|
33
|
-
const totalMemoryGB =
|
|
33
|
+
const cpuCount = usableCpuCount();
|
|
34
|
+
const totalMemoryGB = usableMemoryBytes() / (1024 * 1024 * 1024);
|
|
34
35
|
let memoryPerWorkerGB = this.config.memoryPerWorkerGB;
|
|
35
36
|
if (!memoryPerWorkerGB) {
|
|
36
37
|
const headroomGB = 4;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uws-worker-clustering.js","sourceRoot":"","sources":["../../../../src/core/http/utils/uws-worker-clustering.ts"],"names":[],"mappings":"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","sourcesContent":["// uWebSockets.js Worker Thread Clustering Implementation\n// Uses worker threads with acceptor pattern for maximum performance\n//\n// NOTE: When terminating, you may see \"uv_loop_close() while having open handles\" warnings\n// from Node.js. This is a known limitation when using uWebSockets.js with worker threads.\n// The uWS C++ addon maintains internal libuv poll handles that cannot be cleanly closed\n// through JavaScript APIs. These warnings are harmless - the process will still exit cleanly.\n// To suppress these warnings, you can run Node.js with: --no-warnings or\n// NODE_NO_WARNINGS=1 environment variable.\n\nimport type { Worker } from 'worker_threads';\nimport { requireBuiltin } from '../../utilities/builtin.js';\n\n// worker_threads is loaded on first use (core/utilities/builtin.ts).\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nimport os from 'os';\nimport { createFrameworkLogger } from '../../logger/index.js';\nimport { loadNativeEngine } from '../../utilities/package-utils.js';\n\nconst logger = createFrameworkLogger('UWSClustering');\n\nexport interface UWSClusterConfig {\n workers?: number | 'auto';\n memoryPerWorkerGB?: number;\n port: number;\n host?: string;\n ssl?: {\n key_file_name?: string;\n cert_file_name?: string;\n passphrase?: string;\n };\n}\n\nexport class UWSWorkerClusterManager {\n private workers: Worker[] = [];\n private acceptorApp: any = null;\n private acceptorListenSocket: any = null;\n private uws: any = null;\n private config: UWSClusterConfig;\n private workerCount: number;\n private isShuttingDown = false;\n\n constructor(config: UWSClusterConfig) {\n this.config = config;\n this.workerCount = this.calculateWorkerCount();\n }\n\n private calculateWorkerCount(): number {\n let workerCount = this.config.workers || os.cpus().length;\n\n if (workerCount === 'auto') {\n const cpuCount = os.cpus().length;\n const totalMemoryGB = os.totalmem() / (1024 * 1024 * 1024);\n\n let memoryPerWorkerGB = this.config.memoryPerWorkerGB;\n\n if (!memoryPerWorkerGB) {\n const headroomGB = 4;\n memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));\n }\n\n workerCount = Math.min(cpuCount, Math.floor(totalMemoryGB / memoryPerWorkerGB));\n\n logger.info(\n `Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,\n 'WorkerCalculation'\n );\n } else if (typeof workerCount === 'number') {\n logger.info(`Using specified worker count: ${workerCount}`, 'WorkerCalculation');\n }\n\n return workerCount;\n }\n\n async startAcceptorAndWorkers(_appFactory: () => any): Promise<void> {\n if (!wt().isMainThread) {\n throw new Error('startAcceptorAndWorkers can only be called from main thread');\n }\n\n try {\n // Worker clustering uses the uWS-shaped App/SSLApp + SO_REUSEPORT API,\n // so it needs uWebSockets.js specifically (not @morojs/engine).\n const engine = loadNativeEngine({ candidates: ['uWebSockets.js'] });\n if (!engine) {\n throw new Error('uWebSockets.js is not available for uWS worker clustering');\n }\n const uwsModule = engine.module;\n this.uws = uwsModule.default || uwsModule;\n\n // Create acceptor app - must match SSL config of worker apps\n const ssl = this.config.ssl;\n if (ssl && ssl.key_file_name && ssl.cert_file_name) {\n this.acceptorApp = this.uws.SSLApp({\n key_file_name: ssl.key_file_name,\n cert_file_name: ssl.cert_file_name,\n passphrase: ssl.passphrase,\n });\n logger.info('uWebSockets SSL acceptor app created', 'Acceptor');\n } else {\n this.acceptorApp = this.uws.App();\n logger.info('uWebSockets acceptor app created', 'Acceptor');\n }\n\n // Start listening on acceptor\n const port = this.config.port;\n this.acceptorApp.listen(port, (token: any) => {\n if (token) {\n this.acceptorListenSocket = token;\n logger.info(\n `Acceptor listening on port ${port} from thread ${wt().threadId}`,\n 'Acceptor'\n );\n } else {\n throw new Error(`Failed to listen on port ${port} from thread ${wt().threadId}`);\n }\n });\n\n // Spawn workers\n logger.info(`Starting ${this.workerCount} worker threads`, 'Workers');\n\n for (let i = 0; i < this.workerCount; i++) {\n await this.spawnWorker(i);\n }\n\n // Setup graceful shutdown\n this.setupGracefulShutdown();\n } catch (error) {\n logger.error('Failed to start acceptor and workers', 'Startup', {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n private async spawnWorker(index: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const scriptPath = process.argv[1];\n if (scriptPath === undefined) {\n reject(new Error('Cannot spawn worker: process.argv[1] (entry script) is unavailable'));\n return;\n }\n const worker = new (wt().Worker)(scriptPath, {\n workerData: {\n isUWSWorker: true,\n workerIndex: index,\n config: this.config,\n },\n argv: process.argv.slice(2),\n env: {\n ...process.env,\n UWS_WORKER_MODE: 'true',\n UWS_WORKER_INDEX: String(index),\n },\n });\n\n let descriptorReceived = false;\n\n worker.on('message', (message: any) => {\n if (message.type === 'descriptor' && message.descriptor) {\n if (!this.acceptorApp) {\n logger.error('Acceptor app not initialized', 'WorkerSpawn');\n reject(new Error('Acceptor app not initialized'));\n return;\n }\n\n try {\n this.acceptorApp.addChildAppDescriptor(message.descriptor);\n descriptorReceived = true;\n logger.info(\n `Worker ${index} (thread ${message.threadId}) registered with acceptor`,\n 'WorkerSpawn'\n );\n resolve();\n } catch (error) {\n logger.error('Failed to add child app descriptor', 'WorkerSpawn', {\n error: error instanceof Error ? error.message : String(error),\n });\n reject(error);\n }\n } else if (message.type === 'error') {\n logger.error(`Worker ${index} initialization error: ${message.error}`, 'WorkerSpawn');\n reject(new Error(message.error));\n }\n });\n\n worker.on('error', (error: Error) => {\n logger.error(`Worker ${index} error`, 'Worker', {\n error: error.message,\n });\n if (!descriptorReceived) {\n reject(error);\n }\n });\n\n worker.on('exit', (code: number) => {\n // Worker threads don't have exitedAfterDisconnect like cluster workers\n // Restart on any non-zero exit code\n if (code !== 0) {\n logger.warn(`Worker ${index} died unexpectedly (code: ${code}). Restarting...`, 'Worker');\n\n // Remove from workers array\n const workerIdx = this.workers.indexOf(worker);\n if (workerIdx > -1) {\n this.workers.splice(workerIdx, 1);\n }\n\n // Restart worker\n this.spawnWorker(index).catch(error => {\n logger.error('Failed to restart worker', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n });\n\n this.workers.push(worker);\n\n // Timeout for descriptor reception\n setTimeout(() => {\n if (!descriptorReceived) {\n logger.error(`Worker ${index} failed to send descriptor within timeout`, 'WorkerSpawn');\n void worker.terminate();\n reject(new Error(`Worker ${index} initialization timeout`));\n }\n }, 10000); // 10 second timeout\n });\n }\n\n private setupGracefulShutdown(): void {\n const gracefulShutdown = async () => {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n logger.info('Gracefully shutting down worker cluster...', 'Shutdown');\n\n // Close acceptor listen socket first\n if (this.acceptorListenSocket && this.uws) {\n try {\n this.uws.us_listen_socket_close(this.acceptorListenSocket);\n this.acceptorListenSocket = null;\n logger.info('Acceptor listen socket closed', 'Shutdown');\n } catch (error) {\n logger.error('Error closing acceptor socket', 'Shutdown', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Send shutdown message to all workers and wait for them to close\n const shutdownPromises = this.workers.map((worker, index) => {\n return new Promise<void>(resolve => {\n let workerExited = false;\n\n const timeout = setTimeout(() => {\n if (!workerExited) {\n logger.info(\n `Worker ${index} taking too long to exit, terminating forcefully`,\n 'Shutdown'\n );\n try {\n // Terminate is the last resort for stuck workers\n void worker.terminate();\n } catch {\n logger.error(`Error terminating worker ${index}`, 'Shutdown');\n }\n }\n resolve();\n }, 2000); // Give 2 seconds for graceful exit\n\n worker.once('exit', code => {\n workerExited = true;\n clearTimeout(timeout);\n if (code === 0) {\n logger.info(`Worker ${index} exited cleanly`, 'Shutdown');\n } else if (code === 1) {\n logger.info(`Worker ${index} terminated (exit code ${code})`, 'Shutdown');\n } else {\n logger.warn(`Worker ${index} exited with unexpected code ${code}`, 'Shutdown');\n }\n resolve();\n });\n\n // Send shutdown message\n try {\n worker.postMessage({ type: 'shutdown' });\n } catch {\n logger.error(`Failed to send shutdown message to worker ${index}`, 'Shutdown');\n clearTimeout(timeout);\n try {\n void worker.terminate();\n } catch {\n // Ignore termination errors\n }\n resolve();\n }\n });\n });\n\n await Promise.all(shutdownPromises);\n logger.info('All workers shut down', 'Shutdown');\n\n // Clear acceptor app reference to allow uWebSockets to cleanup\n if (this.acceptorApp) {\n this.acceptorApp = null;\n }\n\n // Give uWebSockets event loop time to fully cleanup before exiting\n await new Promise(resolve => setTimeout(resolve, 200));\n\n logger.info('Shutdown complete', 'Shutdown');\n process.exit(0);\n };\n\n process.on('SIGINT', () => void gracefulShutdown());\n process.on('SIGTERM', () => void gracefulShutdown());\n }\n\n static isUWSWorker(): boolean {\n return !wt().isMainThread && process.env.UWS_WORKER_MODE === 'true';\n }\n\n static async sendDescriptorToAcceptor(app: any): Promise<void> {\n const { isMainThread, parentPort, threadId } = wt();\n if (isMainThread || !parentPort) {\n throw new Error('sendDescriptorToAcceptor can only be called from worker thread');\n }\n\n try {\n const descriptor = app.getDescriptor();\n\n parentPort.postMessage({\n type: 'descriptor',\n descriptor,\n threadId,\n });\n\n logger.info(`Worker thread ${threadId} sent descriptor to acceptor`, 'Worker');\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Failed to send descriptor to acceptor', 'Worker', { error: errorMessage });\n\n if (parentPort) {\n parentPort.postMessage({\n type: 'error',\n error: errorMessage,\n });\n }\n\n throw error;\n }\n }\n\n static setupWorkerShutdownHandler(closeCallback: () => void | Promise<void>): void {\n const { parentPort, threadId } = wt();\n if (!parentPort) {\n return;\n }\n\n parentPort.on('message', (message: any) => {\n void (async () => {\n if (message.type === 'shutdown') {\n logger.info(`Worker thread ${threadId} shutting down...`, 'Worker');\n\n try {\n // Call the close callback and wait if it's async\n const result = closeCallback();\n if (result && typeof result.then === 'function') {\n await result;\n }\n\n // Give uWebSockets event loop time to fully cleanup\n await new Promise(resolve => setTimeout(resolve, 150));\n\n // Close parent port to signal we're ready to exit\n if (parentPort) {\n parentPort.close();\n }\n\n logger.info(`Worker thread ${threadId} cleanup complete`, 'Worker');\n\n // DON'T call process.exit() - let Node.js exit naturally\n // This allows the event loop to properly drain all uWebSockets handles\n // The worker will exit on its own once all handles are closed\n } catch (error) {\n logger.error('Error during worker shutdown', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n // Still don't force exit - let it drain naturally\n }\n }\n })();\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"uws-worker-clustering.js","sourceRoot":"","sources":["../../../../src/core/http/utils/uws-worker-clustering.ts"],"names":[],"mappings":"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","sourcesContent":["// uWebSockets.js Worker Thread Clustering Implementation\n// Uses worker threads with acceptor pattern for maximum performance\n//\n// NOTE: When terminating, you may see \"uv_loop_close() while having open handles\" warnings\n// from Node.js. This is a known limitation when using uWebSockets.js with worker threads.\n// The uWS C++ addon maintains internal libuv poll handles that cannot be cleanly closed\n// through JavaScript APIs. These warnings are harmless - the process will still exit cleanly.\n// To suppress these warnings, you can run Node.js with: --no-warnings or\n// NODE_NO_WARNINGS=1 environment variable.\n\nimport type { Worker } from 'worker_threads';\nimport { requireBuiltin } from '../../utilities/builtin.js';\n\n// worker_threads is loaded on first use (core/utilities/builtin.ts).\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nimport { usableCpuCount, usableMemoryBytes } from '../../cluster/thread-cluster.js';\nimport { createFrameworkLogger } from '../../logger/index.js';\nimport { loadNativeEngine } from '../../utilities/package-utils.js';\n\nconst logger = createFrameworkLogger('UWSClustering');\n\nexport interface UWSClusterConfig {\n workers?: number | 'auto';\n memoryPerWorkerGB?: number;\n port: number;\n host?: string;\n ssl?: {\n key_file_name?: string;\n cert_file_name?: string;\n passphrase?: string;\n };\n}\n\nexport class UWSWorkerClusterManager {\n private workers: Worker[] = [];\n private acceptorApp: any = null;\n private acceptorListenSocket: any = null;\n private uws: any = null;\n private config: UWSClusterConfig;\n private workerCount: number;\n private isShuttingDown = false;\n\n constructor(config: UWSClusterConfig) {\n this.config = config;\n this.workerCount = this.calculateWorkerCount();\n }\n\n private calculateWorkerCount(): number {\n // Container-aware counts (cgroup quota / affinity mask), not the host's\n let workerCount = this.config.workers || usableCpuCount();\n\n if (workerCount === 'auto') {\n const cpuCount = usableCpuCount();\n const totalMemoryGB = usableMemoryBytes() / (1024 * 1024 * 1024);\n\n let memoryPerWorkerGB = this.config.memoryPerWorkerGB;\n\n if (!memoryPerWorkerGB) {\n const headroomGB = 4;\n memoryPerWorkerGB = Math.max(0.5, Math.floor((totalMemoryGB - headroomGB) / cpuCount));\n }\n\n workerCount = Math.min(cpuCount, Math.floor(totalMemoryGB / memoryPerWorkerGB));\n\n logger.info(\n `Auto-calculated worker count: ${workerCount} (CPU: ${cpuCount}, RAM: ${totalMemoryGB.toFixed(1)}GB, ${memoryPerWorkerGB}GB per worker)`,\n 'WorkerCalculation'\n );\n } else if (typeof workerCount === 'number') {\n logger.info(`Using specified worker count: ${workerCount}`, 'WorkerCalculation');\n }\n\n return workerCount;\n }\n\n async startAcceptorAndWorkers(_appFactory: () => any): Promise<void> {\n if (!wt().isMainThread) {\n throw new Error('startAcceptorAndWorkers can only be called from main thread');\n }\n\n try {\n // Worker clustering uses the uWS-shaped App/SSLApp + SO_REUSEPORT API,\n // so it needs uWebSockets.js specifically (not @morojs/engine).\n const engine = loadNativeEngine({ candidates: ['uWebSockets.js'] });\n if (!engine) {\n throw new Error('uWebSockets.js is not available for uWS worker clustering');\n }\n const uwsModule = engine.module;\n this.uws = uwsModule.default || uwsModule;\n\n // Create acceptor app - must match SSL config of worker apps\n const ssl = this.config.ssl;\n if (ssl && ssl.key_file_name && ssl.cert_file_name) {\n this.acceptorApp = this.uws.SSLApp({\n key_file_name: ssl.key_file_name,\n cert_file_name: ssl.cert_file_name,\n passphrase: ssl.passphrase,\n });\n logger.info('uWebSockets SSL acceptor app created', 'Acceptor');\n } else {\n this.acceptorApp = this.uws.App();\n logger.info('uWebSockets acceptor app created', 'Acceptor');\n }\n\n // Start listening on acceptor\n const port = this.config.port;\n this.acceptorApp.listen(port, (token: any) => {\n if (token) {\n this.acceptorListenSocket = token;\n logger.info(\n `Acceptor listening on port ${port} from thread ${wt().threadId}`,\n 'Acceptor'\n );\n } else {\n throw new Error(`Failed to listen on port ${port} from thread ${wt().threadId}`);\n }\n });\n\n // Spawn workers\n logger.info(`Starting ${this.workerCount} worker threads`, 'Workers');\n\n for (let i = 0; i < this.workerCount; i++) {\n await this.spawnWorker(i);\n }\n\n // Setup graceful shutdown\n this.setupGracefulShutdown();\n } catch (error) {\n logger.error('Failed to start acceptor and workers', 'Startup', {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n private async spawnWorker(index: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const scriptPath = process.argv[1];\n if (scriptPath === undefined) {\n reject(new Error('Cannot spawn worker: process.argv[1] (entry script) is unavailable'));\n return;\n }\n const worker = new (wt().Worker)(scriptPath, {\n workerData: {\n isUWSWorker: true,\n workerIndex: index,\n config: this.config,\n },\n argv: process.argv.slice(2),\n env: {\n ...process.env,\n UWS_WORKER_MODE: 'true',\n UWS_WORKER_INDEX: String(index),\n },\n });\n\n let descriptorReceived = false;\n\n worker.on('message', (message: any) => {\n if (message.type === 'descriptor' && message.descriptor) {\n if (!this.acceptorApp) {\n logger.error('Acceptor app not initialized', 'WorkerSpawn');\n reject(new Error('Acceptor app not initialized'));\n return;\n }\n\n try {\n this.acceptorApp.addChildAppDescriptor(message.descriptor);\n descriptorReceived = true;\n logger.info(\n `Worker ${index} (thread ${message.threadId}) registered with acceptor`,\n 'WorkerSpawn'\n );\n resolve();\n } catch (error) {\n logger.error('Failed to add child app descriptor', 'WorkerSpawn', {\n error: error instanceof Error ? error.message : String(error),\n });\n reject(error);\n }\n } else if (message.type === 'error') {\n logger.error(`Worker ${index} initialization error: ${message.error}`, 'WorkerSpawn');\n reject(new Error(message.error));\n }\n });\n\n worker.on('error', (error: Error) => {\n logger.error(`Worker ${index} error`, 'Worker', {\n error: error.message,\n });\n if (!descriptorReceived) {\n reject(error);\n }\n });\n\n worker.on('exit', (code: number) => {\n // Worker threads don't have exitedAfterDisconnect like cluster workers\n // Restart on any non-zero exit code\n if (code !== 0) {\n logger.warn(`Worker ${index} died unexpectedly (code: ${code}). Restarting...`, 'Worker');\n\n // Remove from workers array\n const workerIdx = this.workers.indexOf(worker);\n if (workerIdx > -1) {\n this.workers.splice(workerIdx, 1);\n }\n\n // Restart worker\n this.spawnWorker(index).catch(error => {\n logger.error('Failed to restart worker', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n });\n\n this.workers.push(worker);\n\n // Timeout for descriptor reception\n setTimeout(() => {\n if (!descriptorReceived) {\n logger.error(`Worker ${index} failed to send descriptor within timeout`, 'WorkerSpawn');\n void worker.terminate();\n reject(new Error(`Worker ${index} initialization timeout`));\n }\n }, 10000); // 10 second timeout\n });\n }\n\n private setupGracefulShutdown(): void {\n const gracefulShutdown = async () => {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n logger.info('Gracefully shutting down worker cluster...', 'Shutdown');\n\n // Close acceptor listen socket first\n if (this.acceptorListenSocket && this.uws) {\n try {\n this.uws.us_listen_socket_close(this.acceptorListenSocket);\n this.acceptorListenSocket = null;\n logger.info('Acceptor listen socket closed', 'Shutdown');\n } catch (error) {\n logger.error('Error closing acceptor socket', 'Shutdown', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Send shutdown message to all workers and wait for them to close\n const shutdownPromises = this.workers.map((worker, index) => {\n return new Promise<void>(resolve => {\n let workerExited = false;\n\n const timeout = setTimeout(() => {\n if (!workerExited) {\n logger.info(\n `Worker ${index} taking too long to exit, terminating forcefully`,\n 'Shutdown'\n );\n try {\n // Terminate is the last resort for stuck workers\n void worker.terminate();\n } catch {\n logger.error(`Error terminating worker ${index}`, 'Shutdown');\n }\n }\n resolve();\n }, 2000); // Give 2 seconds for graceful exit\n\n worker.once('exit', code => {\n workerExited = true;\n clearTimeout(timeout);\n if (code === 0) {\n logger.info(`Worker ${index} exited cleanly`, 'Shutdown');\n } else if (code === 1) {\n logger.info(`Worker ${index} terminated (exit code ${code})`, 'Shutdown');\n } else {\n logger.warn(`Worker ${index} exited with unexpected code ${code}`, 'Shutdown');\n }\n resolve();\n });\n\n // Send shutdown message\n try {\n worker.postMessage({ type: 'shutdown' });\n } catch {\n logger.error(`Failed to send shutdown message to worker ${index}`, 'Shutdown');\n clearTimeout(timeout);\n try {\n void worker.terminate();\n } catch {\n // Ignore termination errors\n }\n resolve();\n }\n });\n });\n\n await Promise.all(shutdownPromises);\n logger.info('All workers shut down', 'Shutdown');\n\n // Clear acceptor app reference to allow uWebSockets to cleanup\n if (this.acceptorApp) {\n this.acceptorApp = null;\n }\n\n // Give uWebSockets event loop time to fully cleanup before exiting\n await new Promise(resolve => setTimeout(resolve, 200));\n\n logger.info('Shutdown complete', 'Shutdown');\n process.exit(0);\n };\n\n process.on('SIGINT', () => void gracefulShutdown());\n process.on('SIGTERM', () => void gracefulShutdown());\n }\n\n static isUWSWorker(): boolean {\n return !wt().isMainThread && process.env.UWS_WORKER_MODE === 'true';\n }\n\n static async sendDescriptorToAcceptor(app: any): Promise<void> {\n const { isMainThread, parentPort, threadId } = wt();\n if (isMainThread || !parentPort) {\n throw new Error('sendDescriptorToAcceptor can only be called from worker thread');\n }\n\n try {\n const descriptor = app.getDescriptor();\n\n parentPort.postMessage({\n type: 'descriptor',\n descriptor,\n threadId,\n });\n\n logger.info(`Worker thread ${threadId} sent descriptor to acceptor`, 'Worker');\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Failed to send descriptor to acceptor', 'Worker', { error: errorMessage });\n\n if (parentPort) {\n parentPort.postMessage({\n type: 'error',\n error: errorMessage,\n });\n }\n\n throw error;\n }\n }\n\n static setupWorkerShutdownHandler(closeCallback: () => void | Promise<void>): void {\n const { parentPort, threadId } = wt();\n if (!parentPort) {\n return;\n }\n\n parentPort.on('message', (message: any) => {\n void (async () => {\n if (message.type === 'shutdown') {\n logger.info(`Worker thread ${threadId} shutting down...`, 'Worker');\n\n try {\n // Call the close callback and wait if it's async\n const result = closeCallback();\n if (result && typeof result.then === 'function') {\n await result;\n }\n\n // Give uWebSockets event loop time to fully cleanup\n await new Promise(resolve => setTimeout(resolve, 150));\n\n // Close parent port to signal we're ready to exit\n if (parentPort) {\n parentPort.close();\n }\n\n logger.info(`Worker thread ${threadId} cleanup complete`, 'Worker');\n\n // DON'T call process.exit() - let Node.js exit naturally\n // This allows the event loop to properly drain all uWebSockets handles\n // The worker will exit on its own once all handles are closed\n } catch (error) {\n logger.error('Error during worker shutdown', 'Worker', {\n error: error instanceof Error ? error.message : String(error),\n });\n // Still don't force exit - let it drain naturally\n }\n }\n })();\n });\n }\n}\n"]}
|
|
@@ -18,6 +18,9 @@ export declare class UwsRequest extends LazyEventEmitter {
|
|
|
18
18
|
params: Record<string, string>;
|
|
19
19
|
body: any;
|
|
20
20
|
ip: string;
|
|
21
|
+
_rawBody: Buffer | undefined;
|
|
22
|
+
/** The request body exactly as received, before any parsing; null when empty. */
|
|
23
|
+
get rawBody(): Buffer | null;
|
|
21
24
|
_server: UWebSocketsHttpServer;
|
|
22
25
|
_uwsReq: any;
|
|
23
26
|
_queryString: string | null;
|
|
@@ -6,7 +6,8 @@ import { loadNativeEngine, getNativeEngineLoadErrors } from '../utilities/packag
|
|
|
6
6
|
import { parseMultipart as parseMultipartBuffer, } from './utils/multipart-parser.js';
|
|
7
7
|
import { LazyEventEmitter } from './utils/lazy-event-emitter.js';
|
|
8
8
|
import { parseRawQueryString } from './utils/query-parser.js';
|
|
9
|
-
import { resolveCompressionSettings, compressBuffer, isCompressible, negotiateEncoding, } from './utils/compression.js';
|
|
9
|
+
import { resolveCompressionSettings, compressBuffer, compressBufferSync, shouldInline, isCompressible, negotiateEncoding, } from './utils/compression.js';
|
|
10
|
+
import { bodyIsText } from './utils/body-type.js';
|
|
10
11
|
// Back-compat alias (this parser is now the transport-neutral one in utils/)
|
|
11
12
|
const parseUwsQueryString = parseRawQueryString;
|
|
12
13
|
/**
|
|
@@ -25,6 +26,11 @@ export class UwsRequest extends LazyEventEmitter {
|
|
|
25
26
|
params = {};
|
|
26
27
|
body = null;
|
|
27
28
|
ip = '';
|
|
29
|
+
_rawBody = undefined;
|
|
30
|
+
/** The request body exactly as received, before any parsing; null when empty. */
|
|
31
|
+
get rawBody() {
|
|
32
|
+
return this._rawBody ?? null;
|
|
33
|
+
}
|
|
28
34
|
_server;
|
|
29
35
|
_uwsReq; // valid ONLY synchronously inside the uWS route callback
|
|
30
36
|
_queryString;
|
|
@@ -845,6 +851,30 @@ export class UWebSocketsHttpServer {
|
|
|
845
851
|
const encoding = negotiateEncoding(this._acceptEncoding, s.encodings);
|
|
846
852
|
if (!encoding)
|
|
847
853
|
return false;
|
|
854
|
+
// A small body is compressed on this thread and written in the same
|
|
855
|
+
// cork an uncompressed reply uses; a failure hands back to the caller,
|
|
856
|
+
// which sends the body as it is.
|
|
857
|
+
if (shouldInline(bytes, s)) {
|
|
858
|
+
let compressed;
|
|
859
|
+
try {
|
|
860
|
+
compressed = compressBufferSync(body, encoding, s.level);
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
this.responseHeaders['content-encoding'] = encoding;
|
|
866
|
+
const vary = this.responseHeaders['vary'];
|
|
867
|
+
this.responseHeaders['vary'] = vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding';
|
|
868
|
+
this._res.cork(() => {
|
|
869
|
+
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
870
|
+
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
871
|
+
this._res.end(compressed);
|
|
872
|
+
});
|
|
873
|
+
this.headersSent = true;
|
|
874
|
+
this._ended = true;
|
|
875
|
+
this._emitDone();
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
848
878
|
void compressBuffer(body, encoding, s.level)
|
|
849
879
|
.then(compressed => {
|
|
850
880
|
if (this._ended || this._res.aborted)
|
|
@@ -1557,9 +1587,24 @@ export class UWebSocketsHttpServer {
|
|
|
1557
1587
|
const stable = buffer === view ? Buffer.from(buffer) : buffer;
|
|
1558
1588
|
httpReq.body = parseMultipartBuffer(stable, contentType, this.multipartLimits);
|
|
1559
1589
|
}
|
|
1560
|
-
else {
|
|
1590
|
+
else if (bodyIsText(contentType)) {
|
|
1561
1591
|
httpReq.body = buffer.toString('utf-8');
|
|
1562
1592
|
}
|
|
1593
|
+
else {
|
|
1594
|
+
// Binary stays a Buffer. The single-chunk fast path aliases
|
|
1595
|
+
// uWS-owned memory that is recycled after this callback, so a
|
|
1596
|
+
// Buffer that outlives it must be a copy.
|
|
1597
|
+
httpReq.body = buffer === view ? Buffer.from(buffer) : buffer;
|
|
1598
|
+
}
|
|
1599
|
+
// Raw bytes for signature checks: reuse the stable binary copy when
|
|
1600
|
+
// one was made, otherwise copy out of uWS-owned memory now.
|
|
1601
|
+
if (totalLength > 0) {
|
|
1602
|
+
httpReq._rawBody = Buffer.isBuffer(httpReq.body)
|
|
1603
|
+
? httpReq.body
|
|
1604
|
+
: buffer === view
|
|
1605
|
+
? Buffer.from(buffer)
|
|
1606
|
+
: buffer;
|
|
1607
|
+
}
|
|
1563
1608
|
finish(resolve);
|
|
1564
1609
|
}
|
|
1565
1610
|
catch (parseError) {
|