Skip to content

Commit f67c1f5

Browse files
committed
zlib: add zstd support
Fixes: nodejs#48412 PR-URL: nodejs#52100
1 parent 79e8e57 commit f67c1f5

23 files changed

+1001
-21
lines changed

benchmark/zlib/creation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const zlib = require('zlib');
55
const bench = common.createBenchmark(main, {
66
type: [
77
'Deflate', 'DeflateRaw', 'Inflate', 'InflateRaw', 'Gzip', 'Gunzip', 'Unzip',
8-
'BrotliCompress', 'BrotliDecompress',
8+
'BrotliCompress', 'BrotliDecompress', 'ZstdCompress', 'ZstdDecompress',
99
],
1010
options: ['true', 'false'],
1111
n: [5e5],

benchmark/zlib/pipe.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@ const bench = common.createBenchmark(main, {
77
inputLen: [1024],
88
duration: [5],
99
type: ['string', 'buffer'],
10-
algorithm: ['gzip', 'brotli'],
10+
algorithm: ['gzip', 'brotli', 'zstd'],
1111
}, {
1212
test: {
1313
inputLen: 1024,
1414
duration: 0.2,
1515
},
1616
});
1717

18+
const algorithms = {
19+
'gzip': [zlib.createGzip, zlib.createGunzip],
20+
'brotli': [zlib.createBrotliCompress, zlib.createBrotliDecompress],
21+
'zstd': [zlib.createZstdCompress, zlib.createZstdDecompress],
22+
};
23+
1824
function main({ inputLen, duration, type, algorithm }) {
1925
const buffer = Buffer.alloc(inputLen, fs.readFileSync(__filename));
2026
const chunk = type === 'buffer' ? buffer : buffer.toString('utf8');
2127

22-
const input = algorithm === 'gzip' ?
23-
zlib.createGzip() : zlib.createBrotliCompress();
24-
const output = algorithm === 'gzip' ?
25-
zlib.createGunzip() : zlib.createBrotliDecompress();
28+
const [createCompress, createUncompress] = algorithms[algorithm];
29+
const input = createCompress();
30+
const output = createUncompress();
2631

2732
let readFromOutput = 0;
2833
input.pipe(output);

doc/api/errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3800,6 +3800,12 @@ removed: v10.0.0
38003800
Used when an attempt is made to use a `zlib` object after it has already been
38013801
closed.
38023802

3803+
<a id="ERR_ZSTD_INVALID_PARAM"></a>
3804+
3805+
### `ERR_ZSTD_INVALID_PARAM`
3806+
3807+
An invalid parameter key was passed during construction of a Zstd stream.
3808+
38033809
<a id="ERR_CPU_USAGE"></a>
38043810

38053811
### `ERR_CPU_USAGE`

doc/api/zlib.md

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ operations be cached to avoid duplication of effort.
124124

125125
## Compressing HTTP requests and responses
126126

127-
The `node:zlib` module can be used to implement support for the `gzip`, `deflate`
128-
and `br` content-encoding mechanisms defined by
127+
The `node:zlib` module can be used to implement support for the `gzip`, `deflate`,
128+
`br` and `zstd` content-encoding mechanisms defined by
129129
[HTTP](https://tools.ietf.org/html/rfc7230#section-4.2).
130130

131131
The HTTP [`Accept-Encoding`][] header is used within an HTTP request to identify
@@ -148,7 +148,7 @@ const { pipeline } = require('node:stream');
148148
const request = http.get({ host: 'example.com',
149149
path: '/',
150150
port: 80,
151-
headers: { 'Accept-Encoding': 'br,gzip,deflate' } });
151+
headers: { 'Accept-Encoding': 'br,gzip,deflate,zstd' } });
152152
request.on('response', (response) => {
153153
const output = fs.createWriteStream('example.com_index.html');
154154

@@ -170,6 +170,9 @@ request.on('response', (response) => {
170170
case 'deflate':
171171
pipeline(response, zlib.createInflate(), output, onError);
172172
break;
173+
case 'zstd':
174+
pipeline(response, zlib.createZstdDecompress(), output, onError);
175+
break;
173176
default:
174177
pipeline(response, output, onError);
175178
break;
@@ -218,6 +221,9 @@ http.createServer((request, response) => {
218221
} else if (/\bbr\b/.test(acceptEncoding)) {
219222
response.writeHead(200, { 'Content-Encoding': 'br' });
220223
pipeline(raw, zlib.createBrotliCompress(), response, onError);
224+
} else if (/\bzstd\b/.test(acceptEncoding)) {
225+
response.writeHead(200, { 'Content-Encoding': 'zstd' });
226+
pipeline(raw, zlib.createZstdCompress(), response, onError);
221227
} else {
222228
response.writeHead(200, {});
223229
pipeline(raw, response, onError);
@@ -238,6 +244,7 @@ const buffer = Buffer.from('eJzT0yMA', 'base64');
238244
zlib.unzip(
239245
buffer,
240246
// For Brotli, the equivalent is zlib.constants.BROTLI_OPERATION_FLUSH.
247+
// For Zstd, the equivalent is zlib.constants.ZSTD_e_flush.
241248
{ finishFlush: zlib.constants.Z_SYNC_FLUSH },
242249
(err, buffer) => {
243250
if (err) {
@@ -309,6 +316,16 @@ these options have different ranges than the zlib ones:
309316

310317
See [below][Brotli parameters] for more details on Brotli-specific options.
311318

319+
### For Zstd-based streams
320+
321+
There are equivalents to the zlib options for Zstd-based streams, although
322+
these options have different ranges than the zlib ones:
323+
324+
* zlib's `level` option matches Zstd's `ZSTD_c_compressionLevel` option.
325+
* zlib's `windowBits` option matches Zstd's `ZSTD_c_windowLog` option.
326+
327+
See [below][Zstd parameters] for more details on Zstd-specific options.
328+
312329
## Flushing
313330

314331
Calling [`.flush()`][] on a compression stream will make `zlib` return as much
@@ -487,6 +504,50 @@ These advanced options are available for controlling decompression:
487504
* Boolean flag enabling “Large Window Brotli” mode (not compatible with the
488505
Brotli format as standardized in [RFC 7932][]).
489506

507+
### Zstd constants
508+
509+
<!-- YAML
510+
added: REPLACEME
511+
-->
512+
513+
There are several options and other constants available for Zstd-based
514+
streams:
515+
516+
#### Flush operations
517+
518+
The following values are valid flush operations for Zstd-based streams:
519+
520+
* `zlib.constants.ZSTD_e_continue` (default for all operations)
521+
* `zlib.constants.ZSTD_e_flush` (default when calling `.flush()`)
522+
* `zlib.constants.ZSTD_e_end` (default for the last chunk)
523+
524+
#### Compressor options
525+
526+
There are several options that can be set on Zstd encoders, affecting
527+
compression efficiency and speed. Both the keys and the values can be accessed
528+
as properties of the `zlib.constants` object.
529+
530+
The most important options are:
531+
532+
* `ZSTD_c_compressionLevel`
533+
* Set compression parameters according to pre-defined cLevel table. Default
534+
level is ZSTD\_CLEVEL\_DEFAULT==3.
535+
536+
#### Pledged Source Size
537+
538+
It's possible to specify the expected total size of the uncompressed input via
539+
`opts.pledgedSrcSize`. If the size doesn't match at the end of the input,
540+
compression will fail with the code `ZSTD_error_srcSize_wrong`.
541+
542+
#### Decompressor options
543+
544+
These advanced options are available for controlling decompression:
545+
546+
* `ZSTD_d_windowLogMax`
547+
* Select a size limit (in power of 2) beyond which the streaming API will
548+
refuse to allocate memory buffer in order to protect the host from
549+
unreasonable memory requirements.
550+
490551
## Class: `Options`
491552

492553
<!-- YAML
@@ -684,6 +745,51 @@ base class of the compressor/decompressor classes.
684745
This class inherits from [`stream.Transform`][], allowing `node:zlib` objects to
685746
be used in pipes and similar stream operations.
686747

748+
## Class: `ZstdOptions`
749+
750+
<!-- YAML
751+
added: REPLACEME
752+
-->
753+
754+
<!--type=misc-->
755+
756+
Each Zstd-based class takes an `options` object. All options are optional.
757+
758+
* `flush` {integer} **Default:** `zlib.constants.ZSTD_e_continue`
759+
* `finishFlush` {integer} **Default:** `zlib.constants.ZSTD_e_end`
760+
* `chunkSize` {integer} **Default:** `16 * 1024`
761+
* `params` {Object} Key-value object containing indexed [Zstd parameters][].
762+
* `maxOutputLength` {integer} Limits output size when using
763+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
764+
765+
For example:
766+
767+
```js
768+
const stream = zlib.createZstdCompress({
769+
chunkSize: 32 * 1024,
770+
params: {
771+
[zlib.constants.ZSTD_c_compressionLevel]: 10,
772+
[zlib.constants.ZSTD_c_checksumFlag]: 1,
773+
},
774+
});
775+
```
776+
777+
## Class: `zlib.ZstdCompress`
778+
779+
<!-- YAML
780+
added: REPLACEME
781+
-->
782+
783+
Compress data using the Zstd algorithm.
784+
785+
## Class: `zlib.ZstdDecompress`
786+
787+
<!-- YAML
788+
added: REPLACEME
789+
-->
790+
791+
Decompress data using the Zstd algorithm.
792+
687793
### `zlib.bytesRead`
688794

689795
<!-- YAML
@@ -937,6 +1043,26 @@ added: v0.5.8
9371043

9381044
Creates and returns a new [`Unzip`][] object.
9391045

1046+
## `zlib.createZstdCompress([options])`
1047+
1048+
<!-- YAML
1049+
added: REPLACEME
1050+
-->
1051+
1052+
* `options` {zstd options}
1053+
1054+
Creates and returns a new [`ZstdCompress`][] object.
1055+
1056+
## `zlib.createZstdDecompress([options])`
1057+
1058+
<!-- YAML
1059+
added: REPLACEME
1060+
-->
1061+
1062+
* `options` {zstd options}
1063+
1064+
Creates and returns a new [`ZstdDecompress`][] object.
1065+
9401066
## Convenience methods
9411067

9421068
<!--type=misc-->
@@ -1283,11 +1409,54 @@ changes:
12831409

12841410
Decompress a chunk of data with [`Unzip`][].
12851411

1412+
### `zlib.zstdCompress(buffer[, options], callback)`
1413+
1414+
<!-- YAML
1415+
added: REPLACEME
1416+
-->
1417+
1418+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1419+
* `options` {zstd options}
1420+
* `callback` {Function}
1421+
1422+
### `zlib.zstdCompressSync(buffer[, options])`
1423+
1424+
<!-- YAML
1425+
added: REPLACEME
1426+
-->
1427+
1428+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1429+
* `options` {zstd options}
1430+
1431+
Compress a chunk of data with [`ZstdCompress`][].
1432+
1433+
### `zlib.zstdDecompress(buffer[, options], callback)`
1434+
1435+
<!-- YAML
1436+
added: REPLACEME
1437+
-->
1438+
1439+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1440+
* `options` {zstd options}
1441+
* `callback` {Function}
1442+
1443+
### `zlib.zstdDecompressSync(buffer[, options])`
1444+
1445+
<!-- YAML
1446+
added: REPLACEME
1447+
-->
1448+
1449+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1450+
* `options` {zstd options}
1451+
1452+
Decompress a chunk of data with [`ZstdDecompress`][].
1453+
12861454
[Brotli parameters]: #brotli-constants
12871455
[Cyclic redundancy check]: https://en.wikipedia.org/wiki/Cyclic_redundancy_check
12881456
[Memory usage tuning]: #memory-usage-tuning
12891457
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
12901458
[Streams API]: stream.md
1459+
[Zstd parameters]: #zstd-constants
12911460
[`.flush()`]: #zlibflushkind-callback
12921461
[`Accept-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
12931462
[`ArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
@@ -1304,6 +1473,8 @@ Decompress a chunk of data with [`Unzip`][].
13041473
[`Inflate`]: #class-zlibinflate
13051474
[`TypedArray`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
13061475
[`Unzip`]: #class-zlibunzip
1476+
[`ZstdCompress`]: #class-zlibzstdcompress
1477+
[`ZstdDecompress`]: #class-zlibzstddecompress
13071478
[`buffer.kMaxLength`]: buffer.md#bufferkmaxlength
13081479
[`deflateInit2` and `inflateInit2`]: https://zlib.net/manual.html#Advanced
13091480
[`stream.Transform`]: stream.md#class-streamtransform

lib/internal/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,3 +1887,4 @@ E('ERR_WORKER_UNSERIALIZABLE_ERROR',
18871887
E('ERR_WORKER_UNSUPPORTED_OPERATION',
18881888
'%s is not supported in workers', TypeError);
18891889
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed', Error);
1890+
E('ERR_ZSTD_INVALID_PARAM', '%s is not a valid zstd parameter', RangeError);

0 commit comments

Comments
 (0)