Skip to content

Commit 4991e5d

Browse files
jkremsRafaelGSS
authored andcommitted
zlib: add zstd support
Fixes: #48412 PR-URL: #52100 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent f9f611f commit 4991e5d

22 files changed

+997
-21
lines changed

benchmark/zlib/creation.js

+1-1
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

+10-5
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

+6
Original file line numberDiff line numberDiff line change
@@ -3314,6 +3314,12 @@ The requested functionality is not supported in worker threads.
33143314

33153315
Creation of a [`zlib`][] object failed due to incorrect configuration.
33163316

3317+
<a id="ERR_ZSTD_INVALID_PARAM"></a>
3318+
3319+
### `ERR_ZSTD_INVALID_PARAM`
3320+
3321+
An invalid parameter key was passed during construction of a Zstd stream.
3322+
33173323
<a id="HPE_CHUNK_EXTENSIONS_OVERFLOW"></a>
33183324

33193325
### `HPE_CHUNK_EXTENSIONS_OVERFLOW`

doc/api/zlib.md

+175-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<!-- source_link=lib/zlib.js -->
88

99
The `node:zlib` module provides compression functionality implemented using
10-
Gzip, Deflate/Inflate, and Brotli.
10+
Gzip, Deflate/Inflate, Brotli, and Zstd.
1111

1212
To access it:
1313

@@ -220,8 +220,8 @@ operations be cached to avoid duplication of effort.
220220

221221
## Compressing HTTP requests and responses
222222

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

227227
The HTTP [`Accept-Encoding`][] header is used within an HTTP request to identify
@@ -284,7 +284,7 @@ const { pipeline } = require('node:stream');
284284
const request = http.get({ host: 'example.com',
285285
path: '/',
286286
port: 80,
287-
headers: { 'Accept-Encoding': 'br,gzip,deflate' } });
287+
headers: { 'Accept-Encoding': 'br,gzip,deflate,zstd' } });
288288
request.on('response', (response) => {
289289
const output = fs.createWriteStream('example.com_index.html');
290290

@@ -306,6 +306,9 @@ request.on('response', (response) => {
306306
case 'deflate':
307307
pipeline(response, zlib.createInflate(), output, onError);
308308
break;
309+
case 'zstd':
310+
pipeline(response, zlib.createZstdDecompress(), output, onError);
311+
break;
309312
default:
310313
pipeline(response, output, onError);
311314
break;
@@ -396,6 +399,9 @@ http.createServer((request, response) => {
396399
} else if (/\bbr\b/.test(acceptEncoding)) {
397400
response.writeHead(200, { 'Content-Encoding': 'br' });
398401
pipeline(raw, zlib.createBrotliCompress(), response, onError);
402+
} else if (/\bzstd\b/.test(acceptEncoding)) {
403+
response.writeHead(200, { 'Content-Encoding': 'zstd' });
404+
pipeline(raw, zlib.createZstdCompress(), response, onError);
399405
} else {
400406
response.writeHead(200, {});
401407
pipeline(raw, response, onError);
@@ -416,6 +422,7 @@ const buffer = Buffer.from('eJzT0yMA', 'base64');
416422
zlib.unzip(
417423
buffer,
418424
// For Brotli, the equivalent is zlib.constants.BROTLI_OPERATION_FLUSH.
425+
// For Zstd, the equivalent is zlib.constants.ZSTD_e_flush.
419426
{ finishFlush: zlib.constants.Z_SYNC_FLUSH },
420427
(err, buffer) => {
421428
if (err) {
@@ -487,6 +494,16 @@ these options have different ranges than the zlib ones:
487494

488495
See [below][Brotli parameters] for more details on Brotli-specific options.
489496

497+
### For Zstd-based streams
498+
499+
There are equivalents to the zlib options for Zstd-based streams, although
500+
these options have different ranges than the zlib ones:
501+
502+
* zlib's `level` option matches Zstd's `ZSTD_c_compressionLevel` option.
503+
* zlib's `windowBits` option matches Zstd's `ZSTD_c_windowLog` option.
504+
505+
See [below][Zstd parameters] for more details on Zstd-specific options.
506+
490507
## Flushing
491508

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

721+
### Zstd constants
722+
723+
<!-- YAML
724+
added: REPLACEME
725+
-->
726+
727+
There are several options and other constants available for Zstd-based
728+
streams:
729+
730+
#### Flush operations
731+
732+
The following values are valid flush operations for Zstd-based streams:
733+
734+
* `zlib.constants.ZSTD_e_continue` (default for all operations)
735+
* `zlib.constants.ZSTD_e_flush` (default when calling `.flush()`)
736+
* `zlib.constants.ZSTD_e_end` (default for the last chunk)
737+
738+
#### Compressor options
739+
740+
There are several options that can be set on Zstd encoders, affecting
741+
compression efficiency and speed. Both the keys and the values can be accessed
742+
as properties of the `zlib.constants` object.
743+
744+
The most important options are:
745+
746+
* `ZSTD_c_compressionLevel`
747+
* Set compression parameters according to pre-defined cLevel table. Default
748+
level is ZSTD\_CLEVEL\_DEFAULT==3.
749+
750+
#### Pledged Source Size
751+
752+
It's possible to specify the expected total size of the uncompressed input via
753+
`opts.pledgedSrcSize`. If the size doesn't match at the end of the input,
754+
compression will fail with the code `ZSTD_error_srcSize_wrong`.
755+
756+
#### Decompressor options
757+
758+
These advanced options are available for controlling decompression:
759+
760+
* `ZSTD_d_windowLogMax`
761+
* Select a size limit (in power of 2) beyond which the streaming API will
762+
refuse to allocate memory buffer in order to protect the host from
763+
unreasonable memory requirements.
764+
704765
## Class: `Options`
705766

706767
<!-- YAML
@@ -978,6 +1039,51 @@ added: v0.7.0
9781039
Reset the compressor/decompressor to factory defaults. Only applicable to
9791040
the inflate and deflate algorithms.
9801041

1042+
## Class: `ZstdOptions`
1043+
1044+
<!-- YAML
1045+
added: REPLACEME
1046+
-->
1047+
1048+
<!--type=misc-->
1049+
1050+
Each Zstd-based class takes an `options` object. All options are optional.
1051+
1052+
* `flush` {integer} **Default:** `zlib.constants.ZSTD_e_continue`
1053+
* `finishFlush` {integer} **Default:** `zlib.constants.ZSTD_e_end`
1054+
* `chunkSize` {integer} **Default:** `16 * 1024`
1055+
* `params` {Object} Key-value object containing indexed [Zstd parameters][].
1056+
* `maxOutputLength` {integer} Limits output size when using
1057+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
1058+
1059+
For example:
1060+
1061+
```js
1062+
const stream = zlib.createZstdCompress({
1063+
chunkSize: 32 * 1024,
1064+
params: {
1065+
[zlib.constants.ZSTD_c_compressionLevel]: 10,
1066+
[zlib.constants.ZSTD_c_checksumFlag]: 1,
1067+
},
1068+
});
1069+
```
1070+
1071+
## Class: `zlib.ZstdCompress`
1072+
1073+
<!-- YAML
1074+
added: REPLACEME
1075+
-->
1076+
1077+
Compress data using the Zstd algorithm.
1078+
1079+
## Class: `zlib.ZstdDecompress`
1080+
1081+
<!-- YAML
1082+
added: REPLACEME
1083+
-->
1084+
1085+
Decompress data using the Zstd algorithm.
1086+
9811087
## `zlib.constants`
9821088

9831089
<!-- YAML
@@ -1149,6 +1255,26 @@ added: v0.5.8
11491255

11501256
Creates and returns a new [`Unzip`][] object.
11511257

1258+
## `zlib.createZstdCompress([options])`
1259+
1260+
<!-- YAML
1261+
added: REPLACEME
1262+
-->
1263+
1264+
* `options` {zstd options}
1265+
1266+
Creates and returns a new [`ZstdCompress`][] object.
1267+
1268+
## `zlib.createZstdDecompress([options])`
1269+
1270+
<!-- YAML
1271+
added: REPLACEME
1272+
-->
1273+
1274+
* `options` {zstd options}
1275+
1276+
Creates and returns a new [`ZstdDecompress`][] object.
1277+
11521278
## Convenience methods
11531279

11541280
<!--type=misc-->
@@ -1495,11 +1621,54 @@ changes:
14951621

14961622
Decompress a chunk of data with [`Unzip`][].
14971623

1624+
### `zlib.zstdCompress(buffer[, options], callback)`
1625+
1626+
<!-- YAML
1627+
added: REPLACEME
1628+
-->
1629+
1630+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1631+
* `options` {zstd options}
1632+
* `callback` {Function}
1633+
1634+
### `zlib.zstdCompressSync(buffer[, options])`
1635+
1636+
<!-- YAML
1637+
added: REPLACEME
1638+
-->
1639+
1640+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1641+
* `options` {zstd options}
1642+
1643+
Compress a chunk of data with [`ZstdCompress`][].
1644+
1645+
### `zlib.zstdDecompress(buffer[, options], callback)`
1646+
1647+
<!-- YAML
1648+
added: REPLACEME
1649+
-->
1650+
1651+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1652+
* `options` {zstd options}
1653+
* `callback` {Function}
1654+
1655+
### `zlib.zstdDecompressSync(buffer[, options])`
1656+
1657+
<!-- YAML
1658+
added: REPLACEME
1659+
-->
1660+
1661+
* `buffer` {Buffer|TypedArray|DataView|ArrayBuffer|string}
1662+
* `options` {zstd options}
1663+
1664+
Decompress a chunk of data with [`ZstdDecompress`][].
1665+
14981666
[Brotli parameters]: #brotli-constants
14991667
[Cyclic redundancy check]: https://en.wikipedia.org/wiki/Cyclic_redundancy_check
15001668
[Memory usage tuning]: #memory-usage-tuning
15011669
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
15021670
[Streams API]: stream.md
1671+
[Zstd parameters]: #zstd-constants
15031672
[`.flush()`]: #zlibflushkind-callback
15041673
[`Accept-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
15051674
[`BrotliCompress`]: #class-zlibbrotlicompress
@@ -1512,6 +1681,8 @@ Decompress a chunk of data with [`Unzip`][].
15121681
[`InflateRaw`]: #class-zlibinflateraw
15131682
[`Inflate`]: #class-zlibinflate
15141683
[`Unzip`]: #class-zlibunzip
1684+
[`ZstdCompress`]: #class-zlibzstdcompress
1685+
[`ZstdDecompress`]: #class-zlibzstddecompress
15151686
[`buffer.kMaxLength`]: buffer.md#bufferkmaxlength
15161687
[`deflateInit2` and `inflateInit2`]: https://zlib.net/manual.html#Advanced
15171688
[`stream.Transform`]: stream.md#class-streamtransform

lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -1892,3 +1892,4 @@ E('ERR_WORKER_UNSERIALIZABLE_ERROR',
18921892
'Serializing an uncaught exception failed', Error);
18931893
E('ERR_WORKER_UNSUPPORTED_OPERATION',
18941894
'%s is not supported in workers', TypeError);
1895+
E('ERR_ZSTD_INVALID_PARAM', '%s is not a valid zstd parameter', RangeError);

0 commit comments

Comments
 (0)