@morojs/moro 1.8.13 → 1.8.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/config/config-validator.js +5 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/http/http-server.d.ts +2 -0
- package/dist/core/http/http-server.js +54 -0
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +3 -0
- package/dist/core/http/http2-server.js +57 -13
- package/dist/core/http/http2-server.js.map +1 -1
- package/dist/core/http/moro-engine-server.js +23 -1
- package/dist/core/http/moro-engine-server.js.map +1 -1
- 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/uws-http-server.js +25 -1
- package/dist/core/http/uws-http-server.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 +27 -1
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +1 -1
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/types/config.d.ts +8 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/core.d.ts +1 -0
- package/dist/types/core.js.map +1 -1
- package/package.json +2 -2
|
@@ -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":"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;AAWnF,MAAM,CAAC,MAAM,iBAAiB,GAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAG1C;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;KAC7C,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;gBACtB,MAAM,EAAE;oBACN,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC,GAAG,CAC5C,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAC3C;oBACD,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM;iBACnD;aACF,CAAC,CAAC;QACL,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;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,MAAM,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QACxE,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}\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":"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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 /** 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"]}
|
|
@@ -6,7 +6,7 @@ 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
10
|
import { bodyIsText } from './utils/body-type.js';
|
|
11
11
|
// Back-compat alias (this parser is now the transport-neutral one in utils/)
|
|
12
12
|
const parseUwsQueryString = parseRawQueryString;
|
|
@@ -851,6 +851,30 @@ export class UWebSocketsHttpServer {
|
|
|
851
851
|
const encoding = negotiateEncoding(this._acceptEncoding, s.encodings);
|
|
852
852
|
if (!encoding)
|
|
853
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
|
+
}
|
|
854
878
|
void compressBuffer(body, encoding, s.level)
|
|
855
879
|
.then(compressed => {
|
|
856
880
|
if (this._ended || this._res.aborted)
|