fulmine.js 5.19.1 → 5.19.3

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.
@@ -14,25 +14,25 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
+ /** @typedef {import("./request.js")} Request */
18
+ /** @typedef {import("./response.js")} Response */
19
+
17
20
  // express.compression(), which answers with a compressed body when the client asked for one.
18
21
  //
19
22
  // The options, the defaults and the order the decision is taken in are the compression module's,
20
- // so a front that already uses it can drop the require and change nothing else. Two things are
21
- // different, and both only ever turn a worse answer into a better one:
23
+ // so a front that already uses it can drop the require and change nothing else. Three things are
24
+ // different, and each one only turns a worse answer into a better one:
22
25
  //
23
26
  // - a response that arrives whole, which is every res.send() and res.json(), is compressed in
24
- // one call instead of through a transform stream, and goes out with a Content-Length. The
25
- // bytes are the same bytes: zlib.gzipSync and a createGzip that receives the same body in one
26
- // write produce the same deflate output.
27
- // - partial content is left alone. The compression module compresses a 206 as well, and the
28
- // result is a byte range of the file described as gzip, which no client can decode.
29
- // - zstd is on offer, which the compression module cannot do at all. It is ranked below brotli,
30
- // so a client that takes both is answered exactly as it was before, and above gzip, which it
31
- // beats on ratio and on time. A Node whose zlib has no zstd never offers it.
27
+ // one call instead of through a transform stream, and goes out with a Content-Length. Same
28
+ // bytes: zlib.gzipSync and a createGzip fed the same body in one write agree.
29
+ // - partial content is left alone. The compression module compresses a 206 too, and the result
30
+ // is a byte range of the file described as gzip, which no client can decode.
31
+ // - zstd is on offer, which the compression module cannot do. Ranked below brotli and above
32
+ // gzip, so a client that takes both is answered as before. A Node with no zstd never offers it.
32
33
  //
33
- // The streaming half is the module's own design, because it is the right one: a transform stream,
34
- // its output written as it comes, and the drain listeners moved onto it so a pipe that fills up
35
- // hears from the compressor rather than from a socket that is no longer what it is waiting for.
34
+ // The streaming half is the module's own design: a transform stream, its output written as it
35
+ // comes, and the drain listeners moved onto it so a pipe that fills up hears from the compressor.
36
36
 
37
37
  "use strict";
38
38
 
@@ -73,7 +73,7 @@ const NO_TRANSFORM = /(?:^|,)\s*?no-transform\s*?(?:,|$)/;
73
73
  * Says the answer depends on Accept-Encoding. res.vary() parses what is there and merges, which on
74
74
  * the usual response is parsing an absent header: only a response that already varies pays for it.
75
75
  *
76
- * @param {any} res
76
+ * @param {Response} res
77
77
  */
78
78
  function addVary(res) {
79
79
  if (res.getHeader("Vary") === undefined) {
@@ -95,28 +95,24 @@ if (HAS_ZSTD) {
95
95
  ENFORCEABLE.add("zstd");
96
96
  }
97
97
 
98
- // Up to this many bytes a whole body is compressed on this thread, and above it on the libuv pool.
99
- // One call either way; what changes is who waits. A small body pays more for the hop onto the pool
100
- // than the compression costs, and a large one is worth handing over, since the pool has four
101
- // threads and the loop has everyone else to serve: measured with gzip at the default level, sync
102
- // wins by 43% at 1.4KB and by 22% at 16KB, and loses by 32% at 32KB and by 90% at 78KB.
98
+ // Up to this many bytes a whole body is compressed on this thread, above it on the libuv pool. One
99
+ // call either way, what changes is who waits. Measured with gzip at the default level: sync wins by
100
+ // 43% at 1.4KB and by 22% at 16KB, and loses by 32% at 32KB and by 90% at 78KB.
103
101
  const SYNC_LIMIT = 24 * 1024;
104
102
 
105
103
  const noop = () => {};
106
104
 
107
105
  /**
108
106
  * A whole-body compressor that keeps one stream instead of letting zlib build and throw one away
109
- * per call, which on a body under the sync limit costs more than the compression does. Same bytes,
110
- * a third of the time.
107
+ * per call, which under the sync limit costs more than the compression does. Same bytes, a third
108
+ * of the time.
111
109
  *
112
- * It is private node, and two things have to be held in place for it: close, because the FINISH
113
- * that ends the member would otherwise take the binding with it, and the handle, which that same
114
- * FINISH drops off the stream before returning. The probe then compresses each body twice and
115
- * gives the whole thing up unless every answer matches `oneShot` exactly, so a node that does any
116
- * of this differently gets the public API back and loses nothing but the speed.
110
+ * It is private node, and two things have to be held in place: close, because the FINISH that ends
111
+ * the member would take the binding with it, and the handle, which the same FINISH drops off the
112
+ * stream. The probe compresses each body twice and gives up unless every answer matches `oneShot`,
113
+ * so a node that does this differently gets the public API back.
117
114
  *
118
- * Only for the deflate formats. A brotli stream carries context across a reset and answers the
119
- * second body with bytes that depend on the first.
115
+ * Only for the deflate formats. A brotli stream carries context across a reset.
120
116
  *
121
117
  * @param {() => any} create
122
118
  * @param {number} finishFlag
@@ -200,8 +196,8 @@ function reusableCompressor(create, finishFlag, oneShot) {
200
196
  * The default filter: whether the content type is worth compressing at all. A response with no
201
197
  * type is left alone, since nothing says what its bytes are.
202
198
  *
203
- * @param {any} req
204
- * @param {any} res
199
+ * @param {Request} req
200
+ * @param {Response} res
205
201
  * @returns {boolean}
206
202
  */
207
203
  function shouldCompress(req, res) {
@@ -219,7 +215,7 @@ const isCompressible = memoizeByString((type) => compressible(type) === true);
219
215
  /**
220
216
  * How many bytes a chunk is, which is what the threshold is compared against.
221
217
  *
222
- * @param {any} chunk
218
+ * @param {any} chunk a body piece, in whatever shape the caller wrote it
223
219
  * @param {BufferEncoding} [encoding]
224
220
  * @returns {number}
225
221
  */
@@ -233,7 +229,7 @@ function chunkLength(chunk, encoding) {
233
229
  /**
234
230
  * The bytes of a chunk, whatever it arrived as.
235
231
  *
236
- * @param {any} chunk
232
+ * @param {any} chunk a body piece, in whatever shape the caller wrote it
237
233
  * @param {BufferEncoding} [encoding]
238
234
  * @returns {Buffer}
239
235
  */
@@ -263,10 +259,8 @@ function toBuffer(chunk, encoding) {
263
259
  * @param {object} [options.zstd] zstd options, `params` included, node's own defaults otherwise.
264
260
  * @param {string[]} [options.encodings] the encodings this middleware may answer with, out of
265
261
  * "br", "zstd", "gzip" and "deflate". What is not named is never used, however the client ranks
266
- * it: a server that prefers cheap gzip over brotli passes ["gzip", "deflate"], one that would
267
- * rather answer zstd than brotli passes ["zstd", "gzip"]. An uncompressed answer is always on
268
- * offer, and enforceEncoding stays its own explicit choice, outside this list. This option is
269
- * fulmine's own, the compression module has no equivalent.
262
+ * it. An uncompressed answer is always on offer, and enforceEncoding is outside this list. This
263
+ * option is fulmine's own, the compression module has no equivalent.
270
264
  * @param {number} [options.level] zlib compression level, for gzip and deflate.
271
265
  * @param {number} [options.chunkSize] zlib chunk size.
272
266
  * @param {number} [options.memLevel] zlib memory level.
@@ -384,14 +378,10 @@ function compression(options) {
384
378
  }
385
379
 
386
380
  return function compression(req, res, next) {
387
- // Negotiated here rather than when the body arrives, because the answer to "could this
388
- // request take a compressed body at all" decides how much of this middleware the response
389
- // has to carry. Most requests to most routes cannot: a client that sent no Accept-Encoding,
390
- // one that refused everything, a HEAD. Those get the Vary and nothing else, since the
391
- // answer still depends on the header even when this particular client did not ask.
392
- // straight from the raw entries where this request keeps them: reading req.headers here
393
- // built the whole object for one name. Folded, so a repeated Accept-Encoding still reads
394
- // as the joined list the headers object would have shown
381
+ // Negotiated here rather than when the body arrives: whether this request could take a
382
+ // compressed body at all decides how much of this middleware the response has to carry.
383
+ // Most requests cannot, and those get the Vary and nothing else. Read straight from the raw
384
+ // entries, folded: reading req.headers here built the whole object for one name
395
385
  const accept =
396
386
  typeof req._foldedHeader === "function"
397
387
  ? req._foldedHeader("accept-encoding")
@@ -458,10 +448,9 @@ function compression(options) {
458
448
  }
459
449
 
460
450
  /**
461
- * Whether this response is compressed, and how. Taken once, when the first byte of the
462
- * body arrives, which is also when the headers are decided: everything read here is set
463
- * by then. The order is the compression module's, and so is the Vary, which is added even
464
- * when the answer goes out uncompressed because the answer still depends on the header.
451
+ * Whether this response is compressed, and how. Taken once, when the first byte of body
452
+ * arrives, which is also when the headers are decided. The order is the compression
453
+ * module's, and so is the Vary, added even when the answer goes out uncompressed.
465
454
  *
466
455
  * @param {number} [length] the size of the body, when end() already has all of it
467
456
  * @returns {string} the encoding chosen, "" to send the body as it is
@@ -510,9 +499,8 @@ function compression(options) {
510
499
  function startStream(method) {
511
500
  stream = compressStream(method);
512
501
  // The parked listeners, and the list itself stays rather than being emptied: res.on
513
- // reads it to know that a drain listener belongs on the compressor from here on. That
514
- // matters because a pipe registers its own the first time write() tells it to slow
515
- // down, which is after this, and from here on the compressor is what fills up.
502
+ // reads it to know a drain listener belongs on the compressor from here on. A pipe
503
+ // registers its own the first time write() says to slow down, which is after this
516
504
  for (const listener of /** @type {any[][]} */ (listeners)) {
517
505
  stream.on(listener[0], listener[1]);
518
506
  }