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.
- package/README.md +3 -1
- package/package.json +2 -1
- package/src/adopt.js +20 -26
- package/src/application.js +46 -45
- package/src/cli.js +28 -34
- package/src/cluster.js +16 -25
- package/src/compression.js +39 -51
- package/src/declarative.js +586 -538
- package/src/hot-settings.js +80 -0
- package/src/index.js +11 -17
- package/src/lazy-readable.js +129 -0
- package/src/lazy-writable.js +97 -0
- package/src/middlewares.js +61 -77
- package/src/nest.js +19 -34
- package/src/node-shim.js +11 -13
- package/src/optimizer.js +598 -0
- package/src/parse-query.js +3 -3
- package/src/request-utils.js +306 -0
- package/src/request.js +101 -513
- package/src/response-utils.js +88 -0
- package/src/response.js +100 -443
- package/src/route.js +4 -5
- package/src/router-utils.js +950 -0
- package/src/router.js +126 -2148
- package/src/server-shape.js +26 -41
- package/src/server-timing.js +16 -29
- package/src/socket.js +208 -0
- package/src/testing.js +39 -42
- package/src/usage.js +16 -21
- package/src/utils.js +49 -59
- package/src/verify.js +18 -28
- package/src/view.js +5 -7
- package/src/walk.js +580 -0
- package/src/websocket.js +19 -20
- package/src/work.js +21 -27
package/src/compression.js
CHANGED
|
@@ -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.
|
|
21
|
-
// different, and
|
|
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.
|
|
25
|
-
// bytes
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
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
|
|
34
|
-
//
|
|
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 {
|
|
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,
|
|
99
|
-
//
|
|
100
|
-
//
|
|
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
|
|
110
|
-
*
|
|
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
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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
|
|
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 {
|
|
204
|
-
* @param {
|
|
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
|
|
267
|
-
*
|
|
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
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
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
|
|
462
|
-
*
|
|
463
|
-
*
|
|
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
|
|
514
|
-
//
|
|
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
|
}
|