@morojs/moro 1.8.15 → 1.8.16
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/http/moro-engine-server.d.ts +4 -0
- package/dist/core/http/moro-engine-server.js +96 -47
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/compression.d.ts +34 -4
- package/dist/core/http/utils/compression.js +146 -27
- package/dist/core/http/utils/compression.js.map +1 -1
- package/dist/core/http/uws-http-server.js +2 -2
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +4 -2
- package/dist/core/middleware/built-in/compression/core.js +13 -3
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +88 -0
- package/dist/core/middleware/built-in/static/core.js +321 -15
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.js +65 -10
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
- package/dist/types/config.d.ts +6 -0
- package/dist/types/config.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,32 @@
|
|
|
1
|
+
import type { BrotliOptions, ZlibOptions } from 'zlib';
|
|
1
2
|
export type Encoding = 'br' | 'gzip' | 'deflate';
|
|
2
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Per-encoding encoder settings. The shared `level` (1-9) picks a gzip level
|
|
5
|
+
* and a brotli quality for an app that does not care; an app that does pins
|
|
6
|
+
* the encoder it wants here, and what it pins wins over the level.
|
|
7
|
+
*/
|
|
8
|
+
export interface EncoderOptions {
|
|
9
|
+
/**
|
|
10
|
+
* zlib options for gzip and deflate. A `level` here replaces the shared
|
|
11
|
+
* level; `windowBits`, `memLevel` and `strategy` pass through.
|
|
12
|
+
*/
|
|
13
|
+
gzip?: ZlibOptions;
|
|
14
|
+
/**
|
|
15
|
+
* brotli options. A quality under `params` replaces the one the shared
|
|
16
|
+
* level maps to; the window is still fitted to the body unless
|
|
17
|
+
* `BROTLI_PARAM_LGWIN` is given.
|
|
18
|
+
*/
|
|
19
|
+
brotli?: BrotliOptions;
|
|
20
|
+
/**
|
|
21
|
+
* Keep one gzip encoder per setting and reset it between bodies instead
|
|
22
|
+
* of building a new one for every response: the encoder's hash tables and
|
|
23
|
+
* window are a fair share of what compressing a few kilobytes costs. The
|
|
24
|
+
* handle is proven with a round trip once and the one-call API stands in
|
|
25
|
+
* whenever it cannot be driven that way. Default true.
|
|
26
|
+
*/
|
|
27
|
+
reuseEncoders?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface CompressionSettings extends EncoderOptions {
|
|
3
30
|
enabled: boolean;
|
|
4
31
|
threshold: number;
|
|
5
32
|
level: number;
|
|
@@ -27,6 +54,9 @@ export declare function resolveCompressionSettings(config?: {
|
|
|
27
54
|
level?: number;
|
|
28
55
|
encodings?: Encoding[];
|
|
29
56
|
maxInlineBytes?: number;
|
|
57
|
+
gzip?: ZlibOptions;
|
|
58
|
+
brotli?: BrotliOptions;
|
|
59
|
+
reuseEncoders?: boolean;
|
|
30
60
|
};
|
|
31
61
|
minimal?: boolean;
|
|
32
62
|
}): CompressionSettings;
|
|
@@ -50,10 +80,10 @@ export declare function isCompressible(contentType: string | undefined): boolean
|
|
|
50
80
|
* accepts none / only identity.
|
|
51
81
|
*/
|
|
52
82
|
export declare function negotiateEncoding(acceptEncoding: string | undefined, allowed?: Encoding[]): Encoding | null;
|
|
53
|
-
/** Compress a buffer/string with the chosen encoding. */
|
|
54
|
-
export declare function compressBuffer(data: Buffer | string, encoding: Encoding, level?: number): Promise<Buffer>;
|
|
83
|
+
/** Compress a buffer/string with the chosen encoding, on the threadpool. */
|
|
84
|
+
export declare function compressBuffer(data: Buffer | string, encoding: Encoding, level?: number, encoders?: EncoderOptions): Promise<Buffer>;
|
|
55
85
|
/** Compress a buffer/string on the calling thread (see shouldInline). */
|
|
56
|
-
export declare function compressBufferSync(data: Buffer | string, encoding: Encoding, level?: number): Buffer;
|
|
86
|
+
export declare function compressBufferSync(data: Buffer | string, encoding: Encoding, level?: number, encoders?: EncoderOptions): Buffer;
|
|
57
87
|
export interface MaybeCompressResult {
|
|
58
88
|
body: Buffer | string;
|
|
59
89
|
encoding: Encoding | null;
|
|
@@ -42,6 +42,9 @@ export function resolveCompressionSettings(config) {
|
|
|
42
42
|
level: c?.level ?? 6,
|
|
43
43
|
encodings: c?.encodings ?? DEFAULT_ENCODINGS,
|
|
44
44
|
maxInlineBytes: c?.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES,
|
|
45
|
+
...(c?.gzip !== undefined ? { gzip: c.gzip } : {}),
|
|
46
|
+
...(c?.brotli !== undefined ? { brotli: c.brotli } : {}),
|
|
47
|
+
...(c?.reuseEncoders !== undefined ? { reuseEncoders: c.reuseEncoders } : {}),
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
50
|
/**
|
|
@@ -54,23 +57,132 @@ export function shouldInline(bytes, settings) {
|
|
|
54
57
|
return settings.maxInlineBytes > 0 && bytes <= settings.maxInlineBytes;
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
57
|
-
* Brotli options for one body: quality mapped from the 1-9 level
|
|
58
|
-
* hint, and a window no larger than the input
|
|
59
|
-
* (2^22 bytes) has the encoder allocate and clear
|
|
60
|
-
* which for a response of a few kilobytes is
|
|
61
|
-
* covers the whole input
|
|
62
|
-
* reach back further than the input is long
|
|
60
|
+
* Brotli options for one body: quality mapped from the 1-9 level (unless the
|
|
61
|
+
* app pinned one), the size hint, and a window no larger than the input
|
|
62
|
+
* needs. The default window (2^22 bytes) has the encoder allocate and clear
|
|
63
|
+
* several megabytes per call, which for a response of a few kilobytes is
|
|
64
|
+
* most of its cost; a window that covers the whole input finds the same
|
|
65
|
+
* matches, since none can reach back further than the input is long (only
|
|
66
|
+
* the window field of the stream header differs).
|
|
63
67
|
*/
|
|
64
|
-
function brotliOptions(length, level) {
|
|
68
|
+
function brotliOptions(length, level, custom) {
|
|
65
69
|
const c = z().constants;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
params
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const params = { ...(custom?.params ?? {}) };
|
|
71
|
+
if (params[c.BROTLI_PARAM_QUALITY] === undefined) {
|
|
72
|
+
params[c.BROTLI_PARAM_QUALITY] = Math.max(0, Math.min(11, Math.round((level * 11) / 9)));
|
|
73
|
+
}
|
|
74
|
+
if (params[c.BROTLI_PARAM_SIZE_HINT] === undefined)
|
|
75
|
+
params[c.BROTLI_PARAM_SIZE_HINT] = length;
|
|
76
|
+
if (params[c.BROTLI_PARAM_LGWIN] === undefined) {
|
|
77
|
+
params[c.BROTLI_PARAM_LGWIN] = Math.max(c.BROTLI_MIN_WINDOW_BITS, Math.min(c.BROTLI_DEFAULT_WINDOW, Math.ceil(Math.log2(Math.max(length, 1))) + 1));
|
|
78
|
+
}
|
|
79
|
+
return { ...custom, params };
|
|
80
|
+
}
|
|
81
|
+
/** zlib options for one gzip/deflate body: the app's, with the level filled in. */
|
|
82
|
+
function zlibOptions(level, custom) {
|
|
83
|
+
if (custom === undefined)
|
|
84
|
+
return { level };
|
|
85
|
+
return custom.level === undefined ? { ...custom, level } : custom;
|
|
86
|
+
}
|
|
87
|
+
const retainedGzip = new Map();
|
|
88
|
+
function makeRetainedGzip(options) {
|
|
89
|
+
const zlib = z();
|
|
90
|
+
let stream;
|
|
91
|
+
try {
|
|
92
|
+
stream = zlib.createGzip(options);
|
|
93
|
+
const handle = stream._handle;
|
|
94
|
+
const state = stream._writeState;
|
|
95
|
+
if (!handle ||
|
|
96
|
+
typeof handle.writeSync !== 'function' ||
|
|
97
|
+
typeof handle.reset !== 'function' ||
|
|
98
|
+
!(state instanceof Uint32Array) ||
|
|
99
|
+
state.length < 2) {
|
|
100
|
+
stream.close();
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
// An error the handle raises reaches the stream; it must never take the
|
|
104
|
+
// process down, and the throw below retires the encoder.
|
|
105
|
+
stream.on('error', () => { });
|
|
106
|
+
const FINISH = zlib.constants.Z_FINISH;
|
|
107
|
+
const compress = input => {
|
|
108
|
+
let chunks = null;
|
|
109
|
+
let first = null;
|
|
110
|
+
let inOff = 0;
|
|
111
|
+
let inLen = input.length;
|
|
112
|
+
let outSize = inLen < 512 ? 256 : inLen >> 1;
|
|
113
|
+
for (;;) {
|
|
114
|
+
const out = Buffer.allocUnsafe(outSize);
|
|
115
|
+
handle.writeSync(FINISH, input, inOff, inLen, out, 0, outSize);
|
|
116
|
+
const availOut = state[0];
|
|
117
|
+
const availIn = state[1];
|
|
118
|
+
const produced = outSize - availOut;
|
|
119
|
+
if (produced > 0) {
|
|
120
|
+
const piece = produced === outSize ? out : out.subarray(0, produced);
|
|
121
|
+
if (first === null)
|
|
122
|
+
first = piece;
|
|
123
|
+
else
|
|
124
|
+
(chunks ??= [first]).push(piece);
|
|
125
|
+
}
|
|
126
|
+
inOff += inLen - availIn;
|
|
127
|
+
inLen = availIn;
|
|
128
|
+
if (availOut !== 0)
|
|
129
|
+
break;
|
|
130
|
+
outSize *= 2;
|
|
131
|
+
}
|
|
132
|
+
handle.reset();
|
|
133
|
+
return chunks === null ? (first ?? Buffer.alloc(0)) : Buffer.concat(chunks);
|
|
134
|
+
};
|
|
135
|
+
// Proof: two bodies in a row (the second exercises the reset) decode to
|
|
136
|
+
// the input and are the size the one-call API makes of them, so a handle
|
|
137
|
+
// whose reset forgot its level can never serve.
|
|
138
|
+
const sample = Buffer.from('moro compression sample '.repeat(300) + Math.random());
|
|
139
|
+
const reference = zlib.gzipSync(sample, options).length;
|
|
140
|
+
for (let i = 0; i < 2; i++) {
|
|
141
|
+
const got = compress(sample);
|
|
142
|
+
if (!zlib.gunzipSync(got).equals(sample) || Math.abs(got.length - reference) > 16) {
|
|
143
|
+
stream.close();
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let broken = false;
|
|
148
|
+
return input => {
|
|
149
|
+
if (broken)
|
|
150
|
+
return zlib.gzipSync(input, options);
|
|
151
|
+
try {
|
|
152
|
+
return compress(input);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// The handle's state is unknown now: this body and every later one
|
|
156
|
+
// go the one-call way.
|
|
157
|
+
broken = true;
|
|
158
|
+
try {
|
|
159
|
+
stream.close();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// already closed
|
|
163
|
+
}
|
|
164
|
+
return zlib.gzipSync(input, options);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
try {
|
|
170
|
+
stream?.close();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// nothing to close
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function retainedGzipFor(options) {
|
|
179
|
+
const key = JSON.stringify(options);
|
|
180
|
+
let enc = retainedGzip.get(key);
|
|
181
|
+
if (enc === undefined) {
|
|
182
|
+
enc = makeRetainedGzip(options);
|
|
183
|
+
retainedGzip.set(key, enc);
|
|
184
|
+
}
|
|
185
|
+
return enc;
|
|
74
186
|
}
|
|
75
187
|
/**
|
|
76
188
|
* Content types worth compressing. Text and structured formats yes; already
|
|
@@ -128,29 +240,36 @@ export function negotiateEncoding(acceptEncoding, allowed = DEFAULT_ENCODINGS) {
|
|
|
128
240
|
}
|
|
129
241
|
return null;
|
|
130
242
|
}
|
|
131
|
-
/** Compress a buffer/string with the chosen encoding. */
|
|
132
|
-
export async function compressBuffer(data, encoding, level = 6) {
|
|
243
|
+
/** Compress a buffer/string with the chosen encoding, on the threadpool. */
|
|
244
|
+
export async function compressBuffer(data, encoding, level = 6, encoders) {
|
|
133
245
|
const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
134
246
|
switch (encoding) {
|
|
135
247
|
case 'br':
|
|
136
|
-
return brotliAsync(buf, brotliOptions(buf.length, level));
|
|
248
|
+
return brotliAsync(buf, brotliOptions(buf.length, level, encoders?.brotli));
|
|
137
249
|
case 'gzip':
|
|
138
|
-
return gzipAsync(buf,
|
|
250
|
+
return gzipAsync(buf, zlibOptions(level, encoders?.gzip));
|
|
139
251
|
case 'deflate':
|
|
140
|
-
return deflateAsync(buf,
|
|
252
|
+
return deflateAsync(buf, zlibOptions(level, encoders?.gzip));
|
|
141
253
|
}
|
|
142
254
|
}
|
|
143
255
|
/** Compress a buffer/string on the calling thread (see shouldInline). */
|
|
144
|
-
export function compressBufferSync(data, encoding, level = 6) {
|
|
256
|
+
export function compressBufferSync(data, encoding, level = 6, encoders) {
|
|
145
257
|
const buf = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
146
258
|
const zlib = z();
|
|
147
259
|
switch (encoding) {
|
|
148
260
|
case 'br':
|
|
149
|
-
return zlib.brotliCompressSync(buf, brotliOptions(buf.length, level));
|
|
150
|
-
case 'gzip':
|
|
151
|
-
|
|
261
|
+
return zlib.brotliCompressSync(buf, brotliOptions(buf.length, level, encoders?.brotli));
|
|
262
|
+
case 'gzip': {
|
|
263
|
+
const options = zlibOptions(level, encoders?.gzip);
|
|
264
|
+
if (encoders?.reuseEncoders !== false) {
|
|
265
|
+
const retained = retainedGzipFor(options);
|
|
266
|
+
if (retained !== null)
|
|
267
|
+
return retained(buf);
|
|
268
|
+
}
|
|
269
|
+
return zlib.gzipSync(buf, options);
|
|
270
|
+
}
|
|
152
271
|
case 'deflate':
|
|
153
|
-
return zlib.deflateSync(buf,
|
|
272
|
+
return zlib.deflateSync(buf, zlibOptions(level, encoders?.gzip));
|
|
154
273
|
}
|
|
155
274
|
}
|
|
156
275
|
/**
|
|
@@ -173,8 +292,8 @@ export async function maybeCompress(body, contentType, acceptEncoding, settings)
|
|
|
173
292
|
return { body, encoding: null };
|
|
174
293
|
try {
|
|
175
294
|
const compressed = shouldInline(byteLen, settings)
|
|
176
|
-
? compressBufferSync(body, encoding, settings.level)
|
|
177
|
-
: await compressBuffer(body, encoding, settings.level);
|
|
295
|
+
? compressBufferSync(body, encoding, settings.level, settings)
|
|
296
|
+
: await compressBuffer(body, encoding, settings.level, settings);
|
|
178
297
|
return { body: compressed, encoding };
|
|
179
298
|
}
|
|
180
299
|
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 /** 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"]}
|
|
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 type { BrotliOptions, ZlibOptions } from 'zlib';\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\n/**\n * Per-encoding encoder settings. The shared `level` (1-9) picks a gzip level\n * and a brotli quality for an app that does not care; an app that does pins\n * the encoder it wants here, and what it pins wins over the level.\n */\nexport interface EncoderOptions {\n /**\n * zlib options for gzip and deflate. A `level` here replaces the shared\n * level; `windowBits`, `memLevel` and `strategy` pass through.\n */\n gzip?: ZlibOptions;\n /**\n * brotli options. A quality under `params` replaces the one the shared\n * level maps to; the window is still fitted to the body unless\n * `BROTLI_PARAM_LGWIN` is given.\n */\n brotli?: BrotliOptions;\n /**\n * Keep one gzip encoder per setting and reset it between bodies instead\n * of building a new one for every response: the encoder's hash tables and\n * window are a fair share of what compressing a few kilobytes costs. The\n * handle is proven with a round trip once and the one-call API stands in\n * whenever it cannot be driven that way. Default true.\n */\n reuseEncoders?: boolean;\n}\n\nexport interface CompressionSettings extends EncoderOptions {\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 gzip?: ZlibOptions;\n brotli?: BrotliOptions;\n reuseEncoders?: boolean;\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 ...(c?.gzip !== undefined ? { gzip: c.gzip } : {}),\n ...(c?.brotli !== undefined ? { brotli: c.brotli } : {}),\n ...(c?.reuseEncoders !== undefined ? { reuseEncoders: c.reuseEncoders } : {}),\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 (unless the\n * app pinned one), the size hint, and a window no larger than the input\n * needs. The default window (2^22 bytes) has the encoder allocate and clear\n * several megabytes per call, which for a response of a few kilobytes is\n * most of its cost; a window that covers the whole input finds the same\n * matches, since none can reach back further than the input is long (only\n * the window field of the stream header differs).\n */\nfunction brotliOptions(length: number, level: number, custom?: BrotliOptions): BrotliOptions {\n const c = z().constants;\n const params: { [key: number]: number | boolean } = { ...(custom?.params ?? {}) };\n if (params[c.BROTLI_PARAM_QUALITY] === undefined) {\n params[c.BROTLI_PARAM_QUALITY] = Math.max(0, Math.min(11, Math.round((level * 11) / 9)));\n }\n if (params[c.BROTLI_PARAM_SIZE_HINT] === undefined) params[c.BROTLI_PARAM_SIZE_HINT] = length;\n if (params[c.BROTLI_PARAM_LGWIN] === undefined) {\n params[c.BROTLI_PARAM_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 }\n return { ...custom, params };\n}\n\n/** zlib options for one gzip/deflate body: the app's, with the level filled in. */\nfunction zlibOptions(level: number, custom?: ZlibOptions): ZlibOptions {\n if (custom === undefined) return { level };\n return custom.level === undefined ? { ...custom, level } : custom;\n}\n\n// ---------------------------------------------------------------------------\n// Reused gzip encoders\n//\n// Node's one-call API (gzipSync) builds a fresh encoder for every body and\n// tears it down after: for a body of a few kilobytes the deflate state - a\n// 32 KB window plus hash tables, allocated and cleared - is a fair share of\n// the work. The sync API drives the same native handle a zlib stream owns,\n// so one stream per gzip setting is kept and reset between bodies, the way\n// the encoder would be reused in C. The handle is reached through two\n// stream internals (`_handle`, `_writeState`); they have been stable since\n// Node 10, but a Node that changes them must never produce a wrong body, so\n// the handle is proven once with a round trip against the one-call output,\n// and anything it does not match - or any later throw - retires it in\n// favour of gzipSync. `reuseEncoders: false` turns the whole thing off.\n// ---------------------------------------------------------------------------\ntype SyncEncoder = (input: Buffer) => Buffer;\nconst retainedGzip = new Map<string, SyncEncoder | null>();\n\nfunction makeRetainedGzip(options: ZlibOptions): SyncEncoder | null {\n const zlib = z();\n let stream: any;\n try {\n stream = zlib.createGzip(options);\n const handle = stream._handle;\n const state = stream._writeState;\n if (\n !handle ||\n typeof handle.writeSync !== 'function' ||\n typeof handle.reset !== 'function' ||\n !(state instanceof Uint32Array) ||\n state.length < 2\n ) {\n stream.close();\n return null;\n }\n // An error the handle raises reaches the stream; it must never take the\n // process down, and the throw below retires the encoder.\n stream.on('error', () => {});\n const FINISH = zlib.constants.Z_FINISH;\n const compress: SyncEncoder = input => {\n let chunks: Buffer[] | null = null;\n let first: Buffer | null = null;\n let inOff = 0;\n let inLen = input.length;\n let outSize = inLen < 512 ? 256 : inLen >> 1;\n for (;;) {\n const out = Buffer.allocUnsafe(outSize);\n handle.writeSync(FINISH, input, inOff, inLen, out, 0, outSize);\n const availOut = state[0] as number;\n const availIn = state[1] as number;\n const produced = outSize - availOut;\n if (produced > 0) {\n const piece = produced === outSize ? out : out.subarray(0, produced);\n if (first === null) first = piece;\n else (chunks ??= [first]).push(piece);\n }\n inOff += inLen - availIn;\n inLen = availIn;\n if (availOut !== 0) break;\n outSize *= 2;\n }\n handle.reset();\n return chunks === null ? (first ?? Buffer.alloc(0)) : Buffer.concat(chunks);\n };\n // Proof: two bodies in a row (the second exercises the reset) decode to\n // the input and are the size the one-call API makes of them, so a handle\n // whose reset forgot its level can never serve.\n const sample = Buffer.from('moro compression sample '.repeat(300) + Math.random());\n const reference = zlib.gzipSync(sample, options).length;\n for (let i = 0; i < 2; i++) {\n const got = compress(sample);\n if (!zlib.gunzipSync(got).equals(sample) || Math.abs(got.length - reference) > 16) {\n stream.close();\n return null;\n }\n }\n let broken = false;\n return input => {\n if (broken) return zlib.gzipSync(input, options);\n try {\n return compress(input);\n } catch {\n // The handle's state is unknown now: this body and every later one\n // go the one-call way.\n broken = true;\n try {\n stream.close();\n } catch {\n // already closed\n }\n return zlib.gzipSync(input, options);\n }\n };\n } catch {\n try {\n stream?.close();\n } catch {\n // nothing to close\n }\n return null;\n }\n}\n\nfunction retainedGzipFor(options: ZlibOptions): SyncEncoder | null {\n const key = JSON.stringify(options);\n let enc = retainedGzip.get(key);\n if (enc === undefined) {\n enc = makeRetainedGzip(options);\n retainedGzip.set(key, enc);\n }\n return enc;\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, on the threadpool. */\nexport async function compressBuffer(\n data: Buffer | string,\n encoding: Encoding,\n level = 6,\n encoders?: EncoderOptions\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, encoders?.brotli));\n case 'gzip':\n return gzipAsync(buf, zlibOptions(level, encoders?.gzip));\n case 'deflate':\n return deflateAsync(buf, zlibOptions(level, encoders?.gzip));\n }\n}\n\n/** Compress a buffer/string on the calling thread (see shouldInline). */\nexport function compressBufferSync(\n data: Buffer | string,\n encoding: Encoding,\n level = 6,\n encoders?: EncoderOptions\n): 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, encoders?.brotli));\n case 'gzip': {\n const options = zlibOptions(level, encoders?.gzip);\n if (encoders?.reuseEncoders !== false) {\n const retained = retainedGzipFor(options);\n if (retained !== null) return retained(buf);\n }\n return zlib.gzipSync(buf, options);\n }\n case 'deflate':\n return zlib.deflateSync(buf, zlibOptions(level, encoders?.gzip));\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, settings)\n : await compressBuffer(body, encoding, settings.level, settings);\n return { body: compressed, encoding };\n } catch {\n return { body, encoding: null };\n }\n}\n"]}
|
|
@@ -857,7 +857,7 @@ export class UWebSocketsHttpServer {
|
|
|
857
857
|
if (shouldInline(bytes, s)) {
|
|
858
858
|
let compressed;
|
|
859
859
|
try {
|
|
860
|
-
compressed = compressBufferSync(body, encoding, s.level);
|
|
860
|
+
compressed = compressBufferSync(body, encoding, s.level, s);
|
|
861
861
|
}
|
|
862
862
|
catch {
|
|
863
863
|
return false;
|
|
@@ -875,7 +875,7 @@ export class UWebSocketsHttpServer {
|
|
|
875
875
|
this._emitDone();
|
|
876
876
|
return true;
|
|
877
877
|
}
|
|
878
|
-
void compressBuffer(body, encoding, s.level)
|
|
878
|
+
void compressBuffer(body, encoding, s.level, s)
|
|
879
879
|
.then(compressed => {
|
|
880
880
|
if (this._ended || this._res.aborted)
|
|
881
881
|
return;
|