Skip to content

Commit 919651b

Browse files
committed
doc: update zlib doc
Just some general improvements to zlib docs and examples Signed-off-by: James M Snell <[email protected]>
1 parent cbaae7a commit 919651b

File tree

1 file changed

+145
-45
lines changed

1 file changed

+145
-45
lines changed

doc/api/zlib.md

Lines changed: 145 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,122 @@
44

55
> Stability: 2 - Stable
66
7-
The `zlib` module provides compression functionality implemented using Gzip and
8-
Deflate/Inflate, as well as Brotli. It can be accessed using:
7+
The `zlib` module provides compression functionality implemented using Gzip,
8+
Deflate/Inflate, and Brotli.
9+
10+
To access it:
911

1012
```js
1113
const zlib = require('zlib');
1214
```
1315

16+
Compression and decompression are built around the Node.js [Streams API][].
17+
1418
Compressing or decompressing a stream (such as a file) can be accomplished by
15-
piping the source stream data through a `zlib` stream into a destination stream:
19+
piping the source stream through a `zlib` `Transform` stream into a destination
20+
stream:
1621

1722
```js
18-
const gzip = zlib.createGzip();
19-
const fs = require('fs');
20-
const inp = fs.createReadStream('input.txt');
21-
const out = fs.createWriteStream('input.txt.gz');
22-
23-
inp.pipe(gzip)
24-
.on('error', () => {
25-
// handle error
26-
})
27-
.pipe(out)
28-
.on('error', () => {
29-
// handle error
23+
const { createGzip } = require('zlib');
24+
const { pipeline } = require('stream');
25+
const {
26+
createReadStream,
27+
createWriteStream
28+
} = require('fs');
29+
30+
const gzip = createGzip();
31+
const source = createReadStream('input.txt');
32+
const destination = createWriteStream('input.txt.gz');
33+
34+
pipeline(source, gzip, destination, (err) => {
35+
if (err) {
36+
console.error('An error occurred:', err);
37+
process.exitCode = 1;
38+
}
39+
});
40+
41+
// Or, Promisified
42+
43+
const { promisify } = require('util');
44+
const pipe = promisify(pipeline);
45+
46+
async function do_gzip(input, output) {
47+
const gzip = createGzip();
48+
const source = createReadStream(input);
49+
const destination = createWriteStream(output);
50+
await pipe(source, gzip, destination);
51+
}
52+
53+
do_gzip('input.txt', 'input.txt.gz')
54+
.catch((err) => {
55+
console.error('An error occurred:', err);
56+
process.exitCode = 1;
3057
});
3158
```
3259

3360
It is also possible to compress or decompress data in a single step:
3461

3562
```js
63+
const { deflate, unzip } = require('zlib');
64+
3665
const input = '.................................';
37-
zlib.deflate(input, (err, buffer) => {
38-
if (!err) {
39-
console.log(buffer.toString('base64'));
40-
} else {
41-
// handle error
66+
deflate(input, (err, buffer) => {
67+
if (err) {
68+
console.error('An error occurred:', err);
69+
process.exitCode = 1;
4270
}
71+
console.log(buffer.toString('base64'));
4372
});
4473

4574
const buffer = Buffer.from('eJzT0yMAAGTvBe8=', 'base64');
46-
zlib.unzip(buffer, (err, buffer) => {
47-
if (!err) {
48-
console.log(buffer.toString());
49-
} else {
50-
// handle error
75+
unzip(buffer, (err, buffer) => {
76+
if (err) {
77+
console.error('An error occurred:', err);
78+
process.exitCode = 1;
5179
}
80+
console.log(buffer.toString());
5281
});
82+
83+
// Or, Promisified
84+
85+
const { promisify } = require('util');
86+
const do_unzip = promisify(unzip);
87+
88+
do_unzip(buffer)
89+
.then((buf) => console.log(buf.toString()))
90+
.catch((err) => {
91+
console.error('An error occurred:', err);
92+
process.exitCode = 1;
93+
});
94+
```
95+
96+
## Threadpool Usage and Performance Considerations
97+
98+
All `zlib` APIs, except those that are explicitly synchronous, use the Node.js
99+
internal threadpool. This can lead to surprising effects and performance
100+
limitations in some applications.
101+
102+
Creating and using a large number of zlib objects simultaneously can cause
103+
significant memory fragmentation.
104+
105+
106+
```js
107+
const zlib = require('zlib');
108+
109+
const payload = Buffer.from('This is some data');
110+
111+
// WARNING: DO NOT DO THIS!
112+
for (let i = 0; i < 30000; ++i) {
113+
zlib.deflate(payload, (err, buffer) => {});
114+
}
53115
```
54116

55-
## Threadpool Usage
117+
In the preceding example, 30,000 deflate instances are created concurrently. Because
118+
of how some operating systems handle memory allocation and deallocation,
119+
this may lead to to significant memory fragmentation.
56120

57-
All zlib APIs, except those that are explicitly synchronous, use libuv's
58-
threadpool. This can lead to surprising effects in some applications, such as
59-
subpar performance (which can be mitigated by adjusting the [pool size][])
60-
and/or unrecoverable and catastrophic memory fragmentation.
121+
It is strongly recommended that the results of compression
122+
operations be cached to avoid duplication of effort.
61123

62124
## Compressing HTTP requests and responses
63125

@@ -80,26 +142,35 @@ tradeoffs involved in `zlib` usage.
80142
const zlib = require('zlib');
81143
const http = require('http');
82144
const fs = require('fs');
145+
const { pipeline } = require('stream');
146+
83147
const request = http.get({ host: 'example.com',
84148
path: '/',
85149
port: 80,
86150
headers: { 'Accept-Encoding': 'br,gzip,deflate' } });
87151
request.on('response', (response) => {
88152
const output = fs.createWriteStream('example.com_index.html');
89153

154+
const onError = (err) => {
155+
if (err) {
156+
console.error('An error occurred:', err);
157+
process.exitCode = 1;
158+
}
159+
};
160+
90161
switch (response.headers['content-encoding']) {
91162
case 'br':
92-
response.pipe(zlib.createBrotliDecompress()).pipe(output);
163+
pipeline(response, zlib.createBrotliDecompress(), output, onError);
93164
break;
94165
// Or, just use zlib.createUnzip() to handle both of the following cases:
95166
case 'gzip':
96-
response.pipe(zlib.createGunzip()).pipe(output);
167+
pipeline(response, zlib.createGunzip(), output, onError);
97168
break;
98169
case 'deflate':
99-
response.pipe(zlib.createInflate()).pipe(output);
170+
pipeline(response, zlib.createInflate(), outout, onError);
100171
break;
101172
default:
102-
response.pipe(output);
173+
pipeline(response, output, onError);
103174
break;
104175
}
105176
});
@@ -112,6 +183,8 @@ request.on('response', (response) => {
112183
const zlib = require('zlib');
113184
const http = require('http');
114185
const fs = require('fs');
186+
const { pipeline } = require('stream');
187+
115188
http.createServer((request, response) => {
116189
const raw = fs.createReadStream('index.html');
117190
// Store both a compressed and an uncompressed version of the resource.
@@ -121,20 +194,32 @@ http.createServer((request, response) => {
121194
acceptEncoding = '';
122195
}
123196

197+
const onError = (err) => {
198+
if (err) {
199+
// If an error occurs, there's not much we can do because
200+
// the server has already sent the 200 response code and
201+
// some amount of data has already been sent to the client.
202+
// The best we can do is terminate the response immediately
203+
// and log the error.
204+
response.end();
205+
console.error('An error occurred:', err);
206+
}
207+
};
208+
124209
// Note: This is not a conformant accept-encoding parser.
125210
// See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
126211
if (/\bdeflate\b/.test(acceptEncoding)) {
127212
response.writeHead(200, { 'Content-Encoding': 'deflate' });
128-
raw.pipe(zlib.createDeflate()).pipe(response);
213+
pipeline(raw, zlib.createDeflate(), response, onError);
129214
} else if (/\bgzip\b/.test(acceptEncoding)) {
130215
response.writeHead(200, { 'Content-Encoding': 'gzip' });
131-
raw.pipe(zlib.createGzip()).pipe(response);
216+
pipeline(raw, zlib.createGzip(), response, onError);
132217
} else if (/\bbr\b/.test(acceptEncoding)) {
133218
response.writeHead(200, { 'Content-Encoding': 'br' });
134-
raw.pipe(zlib.createBrotliCompress()).pipe(response);
219+
pipeline(raw, zlib.createBrotliCompress(), response, onError);
135220
} else {
136221
response.writeHead(200, {});
137-
raw.pipe(response);
222+
pipeline(raw, response, onError);
138223
}
139224
}).listen(1337);
140225
```
@@ -154,11 +239,11 @@ zlib.unzip(
154239
// For Brotli, the equivalent is zlib.constants.BROTLI_OPERATION_FLUSH.
155240
{ finishFlush: zlib.constants.Z_SYNC_FLUSH },
156241
(err, buffer) => {
157-
if (!err) {
158-
console.log(buffer.toString());
159-
} else {
160-
// handle error
242+
if (err) {
243+
console.error('An error occurred:', err);
244+
process.exitCode = 1;
161245
}
246+
console.log(buffer.toString());
162247
});
163248
```
164249

@@ -234,14 +319,28 @@ HTTP response to the client:
234319
```js
235320
const zlib = require('zlib');
236321
const http = require('http');
322+
const { pipeline } = require('stream');
237323

238324
http.createServer((request, response) => {
239325
// For the sake of simplicity, the Accept-Encoding checks are omitted.
240326
response.writeHead(200, { 'content-encoding': 'gzip' });
241327
const output = zlib.createGzip();
242-
output.pipe(response);
328+
let i;
329+
330+
pipeline(output, response, (err) => {
331+
if (err) {
332+
// If an error occurs, there's not much we can do because
333+
// the server has already sent the 200 response code and
334+
// some amount of data has already been sent to the client.
335+
// The best we can do is terminate the response immediately
336+
// and log the error.
337+
clearInterval(i);
338+
response.end();
339+
console.error('An error occurred:', err);
340+
}
341+
});
243342

244-
setInterval(() => {
343+
i = setInterval(() => {
245344
output.write(`The current time is ${Date()}\n`, () => {
246345
// The data has been passed to zlib, but the compression algorithm may
247346
// have decided to buffer the data for more efficient compression.
@@ -399,7 +498,7 @@ changes:
399498

400499
<!--type=misc-->
401500

402-
Each zlib-based class takes an `options` object. All options are optional.
501+
Each zlib-based class takes an `options` object. No options are required.
403502

404503
Some options are only relevant when compressing and are
405504
ignored by the decompression classes.
@@ -1059,5 +1158,6 @@ Decompress a chunk of data with [`Unzip`][].
10591158
[Memory Usage Tuning]: #zlib_memory_usage_tuning
10601159
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
10611160
[pool size]: cli.html#cli_uv_threadpool_size_size
1161+
[Streams API]: stream.md
10621162
[zlib documentation]: https://zlib.net/manual.html#Constants
10631163
[zlib.createGzip example]: #zlib_zlib

0 commit comments

Comments
 (0)