fulmine.js 5.19.3 → 5.19.5
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 +9 -4
- package/package.json +1 -1
- package/src/application.js +25 -36
- package/src/cli.js +22 -16
- package/src/cluster.js +2 -2
- package/src/compression.js +102 -61
- package/src/declarative.js +61 -32
- package/src/hot-settings.js +5 -5
- package/src/lazy-readable.js +8 -6
- package/src/lazy-writable.js +3 -3
- package/src/middlewares.js +168 -96
- package/src/nest.js +3 -2
- package/src/node-shim.js +10 -5
- package/src/optimizer.js +9 -7
- package/src/options.d.ts +9 -4
- package/src/request-utils.js +3 -2
- package/src/request.js +49 -38
- package/src/response-utils.js +4 -1
- package/src/response.js +244 -119
- package/src/route.js +3 -3
- package/src/router-utils.js +62 -14
- package/src/router.js +69 -35
- package/src/server-shape.js +14 -10
- package/src/server-timing.js +16 -4
- package/src/socket.js +3 -3
- package/src/testing.js +4 -3
- package/src/usage.js +10 -5
- package/src/utils.js +132 -23
- package/src/verify.js +4 -3
- package/src/view.js +1 -1
- package/src/walk.js +8 -7
- package/src/websocket.js +17 -8
- package/src/work.js +3 -3
package/src/response.js
CHANGED
|
@@ -34,11 +34,13 @@ const {
|
|
|
34
34
|
validateHeaderName,
|
|
35
35
|
validateHeaderValue,
|
|
36
36
|
headerIsWritable,
|
|
37
|
-
withDefaultCharset,
|
|
38
37
|
withUtf8Charset,
|
|
39
38
|
asStatError,
|
|
40
39
|
httpError,
|
|
40
|
+
headersSentError,
|
|
41
|
+
applyWriteHead,
|
|
41
42
|
contentTypeFor,
|
|
43
|
+
contentTypeSet,
|
|
42
44
|
statTag,
|
|
43
45
|
cachedStat,
|
|
44
46
|
NullObject
|
|
@@ -47,6 +49,35 @@ const { isAbsolute } = require("path");
|
|
|
47
49
|
const fs = require("fs");
|
|
48
50
|
const Path = require("path");
|
|
49
51
|
const statuses = require("statuses");
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The TypeError node throws for a chunk that is not a string, a Buffer or a Uint8Array, named the
|
|
55
|
+
* way node names it: a primitive by type and value, an object by its constructor.
|
|
56
|
+
*
|
|
57
|
+
* @param {unknown} chunk what end() was handed, known here not to be a string or a Uint8Array
|
|
58
|
+
* @returns {NodeJS.ErrnoException}
|
|
59
|
+
*/
|
|
60
|
+
function invalidChunkError(chunk) {
|
|
61
|
+
let received;
|
|
62
|
+
if (chunk === null) {
|
|
63
|
+
received = "null";
|
|
64
|
+
} else if (typeof chunk === "object" || typeof chunk === "function") {
|
|
65
|
+
const name = /** @type {any} */ (chunk).constructor?.name;
|
|
66
|
+
received = name ? `an instance of ${name}` : "an instance of Object";
|
|
67
|
+
} else if (typeof chunk === "bigint") {
|
|
68
|
+
received = `type bigint (${chunk}n)`;
|
|
69
|
+
} else if (typeof chunk === "symbol") {
|
|
70
|
+
received = `type symbol (${String(chunk)})`;
|
|
71
|
+
} else {
|
|
72
|
+
received = `type ${typeof chunk} (${chunk})`;
|
|
73
|
+
}
|
|
74
|
+
/** @type {NodeJS.ErrnoException} */
|
|
75
|
+
const err = new TypeError(
|
|
76
|
+
`The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received ${received}`
|
|
77
|
+
);
|
|
78
|
+
err.code = "ERR_INVALID_ARG_TYPE";
|
|
79
|
+
return err;
|
|
80
|
+
}
|
|
50
81
|
const { sign } = require("cookie-signature");
|
|
51
82
|
const ms = require("ms");
|
|
52
83
|
const Socket = require("./socket.js");
|
|
@@ -75,10 +106,17 @@ const COALESCE_BELOW = 4 * 1024;
|
|
|
75
106
|
const HIGH_WATERMARK = 128 * 1024;
|
|
76
107
|
// the exact string json() writes, so send() can skip recomputing the charset on it
|
|
77
108
|
const JSON_UTF8 = "application/json; charset=utf-8";
|
|
109
|
+
|
|
110
|
+
// The Keep-Alive every response is seeded with, kept as a constant so setHeader can tell it apart
|
|
111
|
+
// from one the application set itself.
|
|
112
|
+
const SEEDED_KEEP_ALIVE = "timeout=10";
|
|
78
113
|
// send's ceiling for maxAge, one year in milliseconds. Anything larger is clamped to it rather
|
|
79
114
|
// than written out, since a year is already longer than any cache will honour.
|
|
80
115
|
const MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000;
|
|
81
116
|
|
|
117
|
+
// what send takes as a range request: the bytes unit, checked on the header's text before parsing
|
|
118
|
+
const BYTES_RANGE = /^ *bytes=/;
|
|
119
|
+
|
|
82
120
|
module.exports = class Response extends LazyWritable {
|
|
83
121
|
/** @type {Socket|null} */
|
|
84
122
|
#socket = null;
|
|
@@ -106,15 +144,21 @@ module.exports = class Response extends LazyWritable {
|
|
|
106
144
|
/** Whether a flush is already booked for the end of this turn. */
|
|
107
145
|
#flushBooked = false;
|
|
108
146
|
|
|
109
|
-
/**
|
|
110
|
-
#
|
|
147
|
+
/** Whether the status line and the headers have reached uWS, which only a body write does. */
|
|
148
|
+
#headOut = false;
|
|
111
149
|
|
|
112
150
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @type {
|
|
151
|
+
* The status line as writeHead settled it, which is the one the wire gets: node stores the
|
|
152
|
+
* head at writeHead, so a status set later never reaches the client.
|
|
153
|
+
* @type {number}
|
|
116
154
|
*/
|
|
117
|
-
|
|
155
|
+
#status = 200;
|
|
156
|
+
|
|
157
|
+
/** @type {string|undefined} */
|
|
158
|
+
#statusText = undefined;
|
|
159
|
+
|
|
160
|
+
/** @type {Response["headers"]|null} */
|
|
161
|
+
#outHeaders = null;
|
|
118
162
|
|
|
119
163
|
/**
|
|
120
164
|
* The request this response answers, linked so either reaches the other.
|
|
@@ -127,10 +171,9 @@ module.exports = class Response extends LazyWritable {
|
|
|
127
171
|
* two that describe the connection, since every response carries them, and x-powered-by only
|
|
128
172
|
* when the setting asks for it.
|
|
129
173
|
*
|
|
130
|
-
* @param {
|
|
131
|
-
* @param {
|
|
132
|
-
*
|
|
133
|
-
* @param {any} app the application or router this request arrived at
|
|
174
|
+
* @param {import("uWebSockets.js").HttpResponse} res the uWS response
|
|
175
|
+
* @param {InstanceType<typeof import("./request.js")>} req the Request, already built
|
|
176
|
+
* @param {import("./application.js").Application} app the application this request arrived at
|
|
134
177
|
*/
|
|
135
178
|
constructor(res, req, app) {
|
|
136
179
|
super();
|
|
@@ -139,6 +182,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
139
182
|
// the order node's own Writable constructor lays down, so the hidden class is the one every
|
|
140
183
|
// other stream in the process has, and node's init keeps this object when the state is
|
|
141
184
|
// finally built
|
|
185
|
+
/** @type {Record<string, Function|undefined>} */
|
|
142
186
|
this._events = {
|
|
143
187
|
close: undefined,
|
|
144
188
|
error: undefined,
|
|
@@ -176,7 +220,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
176
220
|
? {}
|
|
177
221
|
: {
|
|
178
222
|
connection: "keep-alive",
|
|
179
|
-
"keep-alive":
|
|
223
|
+
"keep-alive": SEEDED_KEEP_ALIVE
|
|
180
224
|
};
|
|
181
225
|
// the client asked for the connection to be closed, and uWS closes it, so saying otherwise
|
|
182
226
|
// would be telling the client something the transport contradicts. A declarative response
|
|
@@ -191,7 +235,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
191
235
|
this.body = undefined;
|
|
192
236
|
// what was handed to uWS, kept so a caller asking for content-length after the fact can be
|
|
193
237
|
// answered, see get(). Undefined until the response ends, and for one that sends no body
|
|
194
|
-
/** @type {string|Buffer|undefined} */
|
|
238
|
+
/** @type {string|Buffer|Uint8Array|undefined} */
|
|
195
239
|
this._sentBody = undefined;
|
|
196
240
|
// false while the uWS route handler is still in its synchronous window, where uWS holds
|
|
197
241
|
// the socket corked itself; the two uWS entry points flip it once that window closes
|
|
@@ -206,8 +250,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
206
250
|
//
|
|
207
251
|
// The condition is the safety of it: a fresh response has no listeners, so both slots are
|
|
208
252
|
// free, and anything else falls back to on(), which finds what this wrote.
|
|
209
|
-
|
|
210
|
-
const self = /** @type {any} */ (this);
|
|
253
|
+
const self = this;
|
|
211
254
|
const events = self._events;
|
|
212
255
|
if (
|
|
213
256
|
self._eventsCount === 0 &&
|
|
@@ -252,7 +295,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
252
295
|
* aborted uWS response is a use after free. writableEnded reads true after this, where node
|
|
253
296
|
* leaves it false until end() is called.
|
|
254
297
|
*
|
|
255
|
-
* @param {
|
|
298
|
+
* @param {Error} [error] whatever the caller is destroying the response with
|
|
256
299
|
* @returns {this}
|
|
257
300
|
*/
|
|
258
301
|
destroy(error) {
|
|
@@ -282,7 +325,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
282
325
|
return;
|
|
283
326
|
}
|
|
284
327
|
this._pendingLinked = false;
|
|
285
|
-
const pending = /** @type {
|
|
328
|
+
const pending = /** @type {{_pendingIn?: {head: Response|null}}} */ (this)._pendingIn;
|
|
286
329
|
const prev = this._pendingPrev;
|
|
287
330
|
const next = this._pendingNext;
|
|
288
331
|
if (prev) {
|
|
@@ -337,7 +380,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
337
380
|
* over, as node's does; the request's `socket` is the same object and stays, so it comes
|
|
338
381
|
* through here instead.
|
|
339
382
|
*
|
|
340
|
-
* @returns {
|
|
383
|
+
* @returns {Socket}
|
|
341
384
|
*/
|
|
342
385
|
_socketShim() {
|
|
343
386
|
if (!this.#socket) {
|
|
@@ -428,13 +471,15 @@ module.exports = class Response extends LazyWritable {
|
|
|
428
471
|
|
|
429
472
|
this.writingChunk = true;
|
|
430
473
|
this._res.cork(() => {
|
|
431
|
-
if (!this
|
|
432
|
-
|
|
474
|
+
if (!this.#headOut) {
|
|
475
|
+
if (!this.headersSent) {
|
|
476
|
+
this.writeHead(this.statusCode);
|
|
477
|
+
}
|
|
433
478
|
// "unknown" and not the bare number: node writes that reason phrase for a code it
|
|
434
479
|
// has no message for, so the raw status lines match. The default 200 with no
|
|
435
480
|
// phrase is uWS's own head, byte for byte, so it is not written at all
|
|
436
|
-
if (this
|
|
437
|
-
this._res.writeStatus(statusLine(this
|
|
481
|
+
if (this.#status !== 200 || this.#statusText !== undefined) {
|
|
482
|
+
this._res.writeStatus(statusLine(this.#status, this.#statusText));
|
|
438
483
|
}
|
|
439
484
|
this.writeHeaders(typeof chunk === "string");
|
|
440
485
|
}
|
|
@@ -503,8 +548,9 @@ module.exports = class Response extends LazyWritable {
|
|
|
503
548
|
|
|
504
549
|
/**
|
|
505
550
|
* Sets the status and, optionally, a batch of headers, the way node does. The second argument
|
|
506
|
-
* is either the status message or the headers, since node allows both shapes.
|
|
507
|
-
*
|
|
551
|
+
* is either the status message or the headers, since node allows both shapes. The bytes go out
|
|
552
|
+
* with the body, but the head is settled here, as node's is: headersSent reads true from now
|
|
553
|
+
* on, what is set later throws, and a status written later never reaches the wire.
|
|
508
554
|
*
|
|
509
555
|
* Every header goes through setHeader and not through set. This is node's method, not
|
|
510
556
|
* Express's: a content-type given here keeps the value it was given, where res.set would append
|
|
@@ -512,38 +558,33 @@ module.exports = class Response extends LazyWritable {
|
|
|
512
558
|
* @sveltejs/adapter-node included, so the charset was added to pages nobody asked it for.
|
|
513
559
|
*
|
|
514
560
|
* @param {number} statusCode
|
|
515
|
-
* @param {string|
|
|
516
|
-
*
|
|
561
|
+
* @param {string|import("http").OutgoingHttpHeaders|import("http").OutgoingHttpHeader[]} [statusMessage] the
|
|
562
|
+
* reason phrase, or the headers
|
|
563
|
+
* @param {import("http").OutgoingHttpHeaders|import("http").OutgoingHttpHeader[]} [headers]
|
|
517
564
|
* @returns {this}
|
|
518
565
|
*/
|
|
519
566
|
writeHead(statusCode, statusMessage, headers) {
|
|
567
|
+
if (this.headersSent) {
|
|
568
|
+
throw headersSentError("write");
|
|
569
|
+
}
|
|
520
570
|
this.statusCode = statusCode;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
if (Array.isArray(headers)) {
|
|
531
|
-
// node takes a flat list here, name then value, and not a list of pairs. An odd length
|
|
532
|
-
// is the caller's mistake and node names the argument in what it throws
|
|
533
|
-
if (headers.length % 2 !== 0) {
|
|
534
|
-
/** @type {NodeJS.ErrnoException} */
|
|
535
|
-
const err = new TypeError(`The argument 'headers' is invalid. Received ${JSON.stringify(headers)}`);
|
|
536
|
-
err.code = "ERR_INVALID_ARG_VALUE";
|
|
537
|
-
throw err;
|
|
538
|
-
}
|
|
539
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
540
|
-
this.setHeader(headers[i], headers[i + 1]);
|
|
571
|
+
let reason;
|
|
572
|
+
try {
|
|
573
|
+
reason = applyWriteHead(this, statusMessage, headers);
|
|
574
|
+
} catch (err) {
|
|
575
|
+
// node fixes the phrase before it reads the headers and keeps it, so a 500 after a
|
|
576
|
+
// writeHead that threw goes out as "500 OK"
|
|
577
|
+
if (!this.statusText) {
|
|
578
|
+
this.statusText = typeof statusMessage === "string" ? statusMessage : statuses.message[statusCode];
|
|
541
579
|
}
|
|
542
|
-
|
|
580
|
+
throw err;
|
|
543
581
|
}
|
|
544
|
-
|
|
545
|
-
this.
|
|
582
|
+
if (reason !== undefined) {
|
|
583
|
+
this.statusText = reason;
|
|
546
584
|
}
|
|
585
|
+
this.#status = statusCode;
|
|
586
|
+
this.#statusText = this.statusText;
|
|
587
|
+
this.headersSent = true;
|
|
547
588
|
return this;
|
|
548
589
|
}
|
|
549
590
|
|
|
@@ -597,16 +638,19 @@ module.exports = class Response extends LazyWritable {
|
|
|
597
638
|
}
|
|
598
639
|
}
|
|
599
640
|
this.headersSent = true;
|
|
641
|
+
this.#headOut = true;
|
|
600
642
|
}
|
|
601
643
|
|
|
602
644
|
/**
|
|
603
|
-
* What node calls before writing a body when the caller never called writeHead
|
|
604
|
-
* nothing
|
|
645
|
+
* What node calls before writing a body when the caller never called writeHead: it settles the
|
|
646
|
+
* head, and nothing is flushed, since the headers are written with the body. Not once the head
|
|
647
|
+
* is settled: the compression module calls this on the strength of node's _header, which this
|
|
648
|
+
* response does not keep, so the guard is here instead of there.
|
|
605
649
|
*/
|
|
606
650
|
_implicitHeader() {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
651
|
+
if (!this.headersSent) {
|
|
652
|
+
this.writeHead(this.statusCode);
|
|
653
|
+
}
|
|
610
654
|
}
|
|
611
655
|
|
|
612
656
|
/**
|
|
@@ -645,17 +689,38 @@ module.exports = class Response extends LazyWritable {
|
|
|
645
689
|
}
|
|
646
690
|
|
|
647
691
|
/**
|
|
648
|
-
* @param {
|
|
649
|
-
*
|
|
692
|
+
* @param {string|Buffer|Uint8Array|null|(() => void)} [data] the last body piece, or the callback in
|
|
693
|
+
* node's one-argument shape
|
|
694
|
+
* @param {BufferEncoding|(() => void)} [encoding] how a string body is encoded, or the callback in
|
|
695
|
+
* node's two-argument shape
|
|
696
|
+
* @param {() => void} [cb]
|
|
650
697
|
* @returns {this}
|
|
651
698
|
*/
|
|
652
|
-
end(data, cb) {
|
|
699
|
+
end(data, encoding, cb) {
|
|
653
700
|
if (typeof data === "function") {
|
|
654
701
|
cb = data;
|
|
655
702
|
data = undefined;
|
|
703
|
+
encoding = undefined;
|
|
704
|
+
} else if (typeof encoding === "function") {
|
|
705
|
+
cb = encoding;
|
|
706
|
+
encoding = undefined;
|
|
656
707
|
}
|
|
657
708
|
if (typeof cb !== "function") {
|
|
658
|
-
cb = undefined;
|
|
709
|
+
cb = undefined;
|
|
710
|
+
}
|
|
711
|
+
// node refuses a chunk that is not a string, a Buffer or a Uint8Array, and writes it only
|
|
712
|
+
// `if (chunk)`, so end(0) sends nothing. A number reached uWS and answered nothing at all.
|
|
713
|
+
// The typeof goes first so a string body, which is nearly every body, leaves on it alone
|
|
714
|
+
if (typeof data !== "string" && data !== undefined && !(data instanceof Uint8Array)) {
|
|
715
|
+
if (data) {
|
|
716
|
+
throw invalidChunkError(data);
|
|
717
|
+
}
|
|
718
|
+
data = undefined;
|
|
719
|
+
}
|
|
720
|
+
// uWS takes a string as utf-8 and nothing else, so any other encoding is applied here, the
|
|
721
|
+
// way write() applies it: res.end(data, "binary") is how old code sends an image
|
|
722
|
+
if (typeof data === "string" && encoding !== undefined && encoding !== "utf8" && encoding !== "utf-8") {
|
|
723
|
+
data = Buffer.from(data, encoding);
|
|
659
724
|
}
|
|
660
725
|
|
|
661
726
|
if (this.writingChunk) {
|
|
@@ -667,7 +732,11 @@ module.exports = class Response extends LazyWritable {
|
|
|
667
732
|
if (this.finished) {
|
|
668
733
|
return this;
|
|
669
734
|
}
|
|
670
|
-
|
|
735
|
+
// as node's end() calls _implicitHeader: not after an explicit writeHead, which settled the
|
|
736
|
+
// head already and told on-headers' listeners
|
|
737
|
+
if (!this.headersSent) {
|
|
738
|
+
this.writeHead(this.statusCode);
|
|
739
|
+
}
|
|
671
740
|
// uWS holds the socket corked for the synchronous window of its route handler, and
|
|
672
741
|
// cork inside cork is a passthrough: the wrapper and its closure are only paid once the
|
|
673
742
|
// answer has outlived that window, which is what _corkNeeded records
|
|
@@ -683,23 +752,23 @@ module.exports = class Response extends LazyWritable {
|
|
|
683
752
|
* The corked tail of end(): status, headers, body and the finish events. Split out so a
|
|
684
753
|
* synchronous answer calls it straight, already inside uWS's own cork.
|
|
685
754
|
*
|
|
686
|
-
* @param {
|
|
687
|
-
* @param {
|
|
755
|
+
* @param {string|Buffer|Uint8Array|null|undefined} data the last body piece
|
|
756
|
+
* @param {(() => void)|undefined} cb
|
|
688
757
|
*/
|
|
689
758
|
_finish(data, cb) {
|
|
690
759
|
// read before the head is written below, which is what sets the flag: what matters further
|
|
691
760
|
// down is whether something had already committed the framing, a flushHeaders() or a first
|
|
692
761
|
// res.write(), not whether this call is about to write the head itself
|
|
693
|
-
const headWasAlreadyOut = this
|
|
694
|
-
if (!this
|
|
762
|
+
const headWasAlreadyOut = this.#headOut;
|
|
763
|
+
if (!this.#headOut) {
|
|
695
764
|
// freshness is not decided here. node's end() knows nothing about conditional requests,
|
|
696
765
|
// and Express answers 304 from send() and from sendFile(), each of which strips the
|
|
697
766
|
// entity headers first. Deciding it here made res.end("body") answer 304 and drop the
|
|
698
767
|
// body the caller had just written.
|
|
699
768
|
// "unknown" for a code without a message, as node's status line has it. The default 200
|
|
700
769
|
// with no phrase is not written at all: uWS emits the identical head on its own
|
|
701
|
-
if (this
|
|
702
|
-
this._res.writeStatus(statusLine(this
|
|
770
|
+
if (this.#status !== 200 || this.#statusText !== undefined) {
|
|
771
|
+
this._res.writeStatus(statusLine(this.#status, this.#statusText));
|
|
703
772
|
}
|
|
704
773
|
this.writeHeaders(true);
|
|
705
774
|
}
|
|
@@ -714,7 +783,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
714
783
|
const closeConnection = this.req._connectionClose === true;
|
|
715
784
|
// 204 and 304 carry no body, so no Content-Length may describe one either; 1xx is the
|
|
716
785
|
// third case, by range
|
|
717
|
-
if (this
|
|
786
|
+
if (this.#status === 204 || this.#status === 304 || this.#status < 200) {
|
|
718
787
|
// no body and no length describing one, whatever the caller passed. node decides
|
|
719
788
|
// this the same way, from the status alone, so res.status(304).end("x") sends the
|
|
720
789
|
// status and nothing else on either.
|
|
@@ -739,7 +808,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
739
808
|
if (this.req.method === "HEAD") {
|
|
740
809
|
const length = Buffer.byteLength(data ?? "");
|
|
741
810
|
this.headers["content-length"] = String(length);
|
|
742
|
-
this._res.endWithoutBody(length
|
|
811
|
+
this._res.endWithoutBody(length, closeConnection);
|
|
743
812
|
} else {
|
|
744
813
|
// remembered rather than measured: only a caller that asks for content-length pays
|
|
745
814
|
// for it, and uWS is measuring the same bytes for the wire anyway
|
|
@@ -774,7 +843,8 @@ module.exports = class Response extends LazyWritable {
|
|
|
774
843
|
*/
|
|
775
844
|
send(body) {
|
|
776
845
|
if (this.headersSent) {
|
|
777
|
-
|
|
846
|
+
// what express's send meets first once the head is out is setHeader's refusal
|
|
847
|
+
throw headersSentError("set");
|
|
778
848
|
}
|
|
779
849
|
// a typed array is bytes to send, not an object to serialise: res.send(new Uint8Array([104,
|
|
780
850
|
// 101, 121])) is "hey" and not {"0":104,"1":101,"2":121}. Uint8Array and not every view
|
|
@@ -788,6 +858,16 @@ module.exports = class Response extends LazyWritable {
|
|
|
788
858
|
// that happens to be empty: no content-type and no ETag for send(), both for send(null)
|
|
789
859
|
// and send("").
|
|
790
860
|
if (body === undefined) {
|
|
861
|
+
// still through freshness and the bodiless statuses, as every express send is: a 204
|
|
862
|
+
// answered with json(undefined) loses the type json had set
|
|
863
|
+
if (this.req.fresh) {
|
|
864
|
+
this.status(304);
|
|
865
|
+
}
|
|
866
|
+
if (this.statusCode === 204 || this.statusCode === 304) {
|
|
867
|
+
delete this.headers["content-type"];
|
|
868
|
+
delete this.headers["content-length"];
|
|
869
|
+
delete this.headers["transfer-encoding"];
|
|
870
|
+
}
|
|
791
871
|
return this.end("");
|
|
792
872
|
}
|
|
793
873
|
// null is an object as far as Express's switch is concerned, so it becomes the empty
|
|
@@ -805,8 +885,15 @@ module.exports = class Response extends LazyWritable {
|
|
|
805
885
|
return this.json(body);
|
|
806
886
|
} else if (typeof body === "boolean") {
|
|
807
887
|
return this.json(body);
|
|
808
|
-
} else if (!isBuffer) {
|
|
809
|
-
|
|
888
|
+
} else if (!isBuffer && typeof body !== "string") {
|
|
889
|
+
// a symbol, a bigint or a function: express sizes it with byteLength or from(), and
|
|
890
|
+
// neither takes it, so what node throws is the answer. A string never gets here: it
|
|
891
|
+
// is the common body, and measuring it twice cost 157us per thousand requests
|
|
892
|
+
const generateETag = !this.headers["etag"] && typeof this.app._hot().etagFn === "function";
|
|
893
|
+
if (!generateETag && body.length < 1000) {
|
|
894
|
+
Buffer.byteLength(body, "utf8");
|
|
895
|
+
}
|
|
896
|
+
Buffer.from(body, "utf8");
|
|
810
897
|
}
|
|
811
898
|
if (typeof body === "string" && !isBuffer) {
|
|
812
899
|
const contentType = this.headers["content-type"];
|
|
@@ -874,10 +961,11 @@ module.exports = class Response extends LazyWritable {
|
|
|
874
961
|
* options position is the callback.
|
|
875
962
|
*
|
|
876
963
|
* Options: `root`, `maxAge`, `lastModified`, `headers`, `dotfiles` ("allow", "deny" or
|
|
877
|
-
* "ignore"), `acceptRanges`, `cacheControl`, `immutable
|
|
964
|
+
* "ignore"), `acceptRanges`, `cacheControl`, `immutable` and `etag`.
|
|
878
965
|
*
|
|
879
966
|
* @param {string} path
|
|
880
|
-
* @param {import("./options").SendFileOptions} [options]
|
|
967
|
+
* @param {import("./options").SendFileOptions|((err?: Error) => void)} [options] or the callback in
|
|
968
|
+
* its place
|
|
881
969
|
* @param {(err?: Error) => void} [callback] called once sent, or with the error
|
|
882
970
|
*/
|
|
883
971
|
sendFile(path, options = new NullObject(), callback) {
|
|
@@ -890,7 +978,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
890
978
|
throw new TypeError("path must be a string to res.sendFile");
|
|
891
979
|
}
|
|
892
980
|
if (typeof options === "function") {
|
|
893
|
-
callback =
|
|
981
|
+
callback = options;
|
|
894
982
|
options = new NullObject();
|
|
895
983
|
}
|
|
896
984
|
if (!options) options = new NullObject();
|
|
@@ -900,7 +988,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
900
988
|
// The router's next and not the route's: express reports a file it could not serve past the
|
|
901
989
|
// rest of the route, so a four argument handler inside the route never sees it
|
|
902
990
|
const next = this.req._leaveRoute ?? this.req.next;
|
|
903
|
-
const done = /** @type {(err?:
|
|
991
|
+
const done = /** @type {(err?: NodeJS.ErrnoException) => void} */ (
|
|
904
992
|
(err) => {
|
|
905
993
|
if (callback) return callback(err);
|
|
906
994
|
if (err && err.code === "EISDIR") return next();
|
|
@@ -914,7 +1002,9 @@ module.exports = class Response extends LazyWritable {
|
|
|
914
1002
|
// the branch that is already a number: ms() answers undefined for a duration it cannot
|
|
915
1003
|
// read, and Number.isNaN(undefined) is false, so an unreadable string reached it as NaN
|
|
916
1004
|
const maxAge = Number(
|
|
917
|
-
typeof options.maxAge === "string"
|
|
1005
|
+
typeof options.maxAge === "string"
|
|
1006
|
+
? ms(/** @type {import("ms").StringValue} */ (options.maxAge))
|
|
1007
|
+
: options.maxAge
|
|
918
1008
|
);
|
|
919
1009
|
options.maxAge = Number.isNaN(maxAge) ? 0 : Math.min(Math.max(0, maxAge), MAX_MAXAGE);
|
|
920
1010
|
if (typeof options.lastModified === "undefined") {
|
|
@@ -1005,14 +1095,15 @@ module.exports = class Response extends LazyWritable {
|
|
|
1005
1095
|
} catch (err) {
|
|
1006
1096
|
// the fs error itself, carrying its errno and path, with send's status written on
|
|
1007
1097
|
// it: a missing file is the request's 404, an unreadable one is the server's 500
|
|
1008
|
-
return done(asStatError(/** @type {
|
|
1098
|
+
return done(asStatError(/** @type {import("./utils.js").HttpError} */ (err)));
|
|
1009
1099
|
}
|
|
1010
1100
|
if (stat.isDirectory()) {
|
|
1011
1101
|
// Express reports a directory as an EISDIR with no status, because send tells it
|
|
1012
1102
|
// apart from an error: it emits "directory", and res.sendFile has no listener for
|
|
1013
1103
|
// one. So this is not a 404, and an error handler reading err.code sees the code
|
|
1014
1104
|
// it expects. Without a callback, done() turns it into a plain next().
|
|
1015
|
-
|
|
1105
|
+
/** @type {NodeJS.ErrnoException} */
|
|
1106
|
+
const err = new Error("EISDIR, read");
|
|
1016
1107
|
err.code = "EISDIR";
|
|
1017
1108
|
return done(err);
|
|
1018
1109
|
}
|
|
@@ -1040,8 +1131,10 @@ module.exports = class Response extends LazyWritable {
|
|
|
1040
1131
|
this.setHeader(header, options.headers[header]);
|
|
1041
1132
|
}
|
|
1042
1133
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1134
|
+
// express.static's setHeaders, which res.sendFile does not take: express ignores it here,
|
|
1135
|
+
// so it travels under a name only the middleware writes
|
|
1136
|
+
if (options._setHeaders) {
|
|
1137
|
+
options._setHeaders(this, fullpath, stat);
|
|
1045
1138
|
}
|
|
1046
1139
|
|
|
1047
1140
|
// etag, from the stat and never from the app's "etag fn". send computes this itself with
|
|
@@ -1090,7 +1183,10 @@ module.exports = class Response extends LazyWritable {
|
|
|
1090
1183
|
|
|
1091
1184
|
// range requests
|
|
1092
1185
|
if (options.acceptRanges) {
|
|
1093
|
-
|
|
1186
|
+
// only the bytes unit, and send checks the header's text for it before parsing:
|
|
1187
|
+
// "items=0-1" or "Bytes=0-1" is not a range request, and answers the whole file
|
|
1188
|
+
const rangeHeader = this.req.headers.range;
|
|
1189
|
+
if (rangeHeader !== undefined && BYTES_RANGE.test(rangeHeader)) {
|
|
1094
1190
|
// the branch above established the header is there, so range() cannot answer
|
|
1095
1191
|
// the undefined it uses to mean "no Range header"
|
|
1096
1192
|
let ranges = /** @type {ReturnType<typeof import("range-parser")>} */ (
|
|
@@ -1103,10 +1199,13 @@ module.exports = class Response extends LazyWritable {
|
|
|
1103
1199
|
}
|
|
1104
1200
|
|
|
1105
1201
|
if (ranges === -1) {
|
|
1106
|
-
//
|
|
1107
|
-
// the
|
|
1108
|
-
|
|
1109
|
-
|
|
1202
|
+
// on the response as send writes it, and on the error too: the error page
|
|
1203
|
+
// drops the content headers and writes back only what the error carries
|
|
1204
|
+
const unsatisfiable = `bytes */${len}`;
|
|
1205
|
+
this.headers["content-range"] = unsatisfiable;
|
|
1206
|
+
const err = httpError(416);
|
|
1207
|
+
err.headers = { "Content-Range": unsatisfiable };
|
|
1208
|
+
return done(err);
|
|
1110
1209
|
}
|
|
1111
1210
|
if (ranges !== -2 && ranges.length === 1) {
|
|
1112
1211
|
this.status(206);
|
|
@@ -1149,7 +1248,8 @@ module.exports = class Response extends LazyWritable {
|
|
|
1149
1248
|
// ECONNABORTED to a callback and never to next(), so aborts stay out of
|
|
1150
1249
|
// the error middleware.
|
|
1151
1250
|
if (this.aborted && callback) {
|
|
1152
|
-
|
|
1251
|
+
/** @type {NodeJS.ErrnoException} */
|
|
1252
|
+
const err = new Error("Request aborted");
|
|
1153
1253
|
err.code = "ECONNABORTED";
|
|
1154
1254
|
callback(err);
|
|
1155
1255
|
}
|
|
@@ -1267,7 +1367,8 @@ module.exports = class Response extends LazyWritable {
|
|
|
1267
1367
|
* what a media type is. res.set does that, and is what Express code should use.
|
|
1268
1368
|
*
|
|
1269
1369
|
* @param {string} field
|
|
1270
|
-
* @param {
|
|
1370
|
+
* @param {number|string|readonly string[]|undefined} value an array sends the header once per
|
|
1371
|
+
* entry; undefined is refused, as node refuses it
|
|
1271
1372
|
* @returns {this}
|
|
1272
1373
|
* @throws {Error} once the headers have gone out
|
|
1273
1374
|
* @throws {TypeError} if the name is not a token, the value is undefined, or the value holds a
|
|
@@ -1275,7 +1376,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1275
1376
|
*/
|
|
1276
1377
|
setHeader(field, value) {
|
|
1277
1378
|
if (this.headersSent) {
|
|
1278
|
-
throw
|
|
1379
|
+
throw headersSentError("set");
|
|
1279
1380
|
}
|
|
1280
1381
|
// one Map hit for a name already validated and lowercased: middleware writes the same
|
|
1281
1382
|
// constant names on every request. Insert-only after validation, so no bad name can enter
|
|
@@ -1300,6 +1401,14 @@ module.exports = class Response extends LazyWritable {
|
|
|
1300
1401
|
// catch it
|
|
1301
1402
|
const out = Array.isArray(value) ? value.map(String) : String(value);
|
|
1302
1403
|
validateHeaderValue(field, out);
|
|
1404
|
+
// node writes Connection and Keep-Alive as a pair, and writes neither once the response has
|
|
1405
|
+
// set Connection itself, so the seeded Keep-Alive goes with it. Anything that opens an
|
|
1406
|
+
// event stream sets Connection: the MCP transport does, and so does every SSE library.
|
|
1407
|
+
if (key === "connection" && this.headers["keep-alive"] === SEEDED_KEEP_ALIVE) {
|
|
1408
|
+
// through an index signature: the seeded pair is not optional in what the constructor
|
|
1409
|
+
// infers, so a plain delete of it is a type error
|
|
1410
|
+
delete (/** @type {Record<string, string|string[]>} */ (this.headers)["keep-alive"]);
|
|
1411
|
+
}
|
|
1303
1412
|
this.headers[key] = out;
|
|
1304
1413
|
return this;
|
|
1305
1414
|
}
|
|
@@ -1338,15 +1447,17 @@ module.exports = class Response extends LazyWritable {
|
|
|
1338
1447
|
* @returns {void}
|
|
1339
1448
|
*/
|
|
1340
1449
|
flushHeaders() {
|
|
1341
|
-
if (this
|
|
1450
|
+
if (this.#headOut || this.finished || this.aborted) {
|
|
1342
1451
|
return;
|
|
1343
1452
|
}
|
|
1344
1453
|
this._res.cork(() => {
|
|
1345
|
-
|
|
1454
|
+
if (!this.headersSent) {
|
|
1455
|
+
this.writeHead(this.statusCode);
|
|
1456
|
+
}
|
|
1346
1457
|
// the same rule the chunked write path follows: uWS emits the 200 head itself, byte for
|
|
1347
1458
|
// byte, so writing it again would only cost a crossing
|
|
1348
|
-
if (this
|
|
1349
|
-
this._res.writeStatus(statusLine(this
|
|
1459
|
+
if (this.#status !== 200 || this.#statusText !== undefined) {
|
|
1460
|
+
this._res.writeStatus(statusLine(this.#status, this.#statusText));
|
|
1350
1461
|
}
|
|
1351
1462
|
// true, as the chunked path passes for a string chunk: what follows a flush is a body
|
|
1352
1463
|
// written in pieces, and the framing has to be the one that allows them
|
|
@@ -1403,10 +1514,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1403
1514
|
*/
|
|
1404
1515
|
#refuseInformationAfterHead() {
|
|
1405
1516
|
if (this.headersSent) {
|
|
1406
|
-
|
|
1407
|
-
const err = new Error("Cannot write headers after they are sent to the client");
|
|
1408
|
-
err.code = "ERR_HTTP_HEADERS_SENT";
|
|
1409
|
-
throw err;
|
|
1517
|
+
throw headersSentError("write");
|
|
1410
1518
|
}
|
|
1411
1519
|
}
|
|
1412
1520
|
|
|
@@ -1439,7 +1547,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1439
1547
|
* node's `assignSocket`, which the http server uses when a response is
|
|
1440
1548
|
* handed a raw socket. There is no such socket here.
|
|
1441
1549
|
*
|
|
1442
|
-
* @param {
|
|
1550
|
+
* @param {import("net").Socket} [socket] node takes one here and there is none to take
|
|
1443
1551
|
* @returns {void}
|
|
1444
1552
|
*/
|
|
1445
1553
|
assignSocket(socket) {}
|
|
@@ -1447,7 +1555,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1447
1555
|
/**
|
|
1448
1556
|
* node's `detachSocket`, which the http server uses when a response is
|
|
1449
1557
|
* handed a raw socket. There is no such socket here.
|
|
1450
|
-
* @param {
|
|
1558
|
+
* @param {import("net").Socket} [socket] node takes one here and there is none to take
|
|
1451
1559
|
* @returns {void}
|
|
1452
1560
|
*/
|
|
1453
1561
|
detachSocket(socket) {}
|
|
@@ -1508,10 +1616,10 @@ module.exports = class Response extends LazyWritable {
|
|
|
1508
1616
|
const key = name.toLowerCase();
|
|
1509
1617
|
const current = this.headers[key];
|
|
1510
1618
|
if (current === undefined) {
|
|
1511
|
-
return this.setHeader(name,
|
|
1619
|
+
return this.setHeader(name, value);
|
|
1512
1620
|
}
|
|
1513
|
-
const merged =
|
|
1514
|
-
return this.setHeader(name,
|
|
1621
|
+
const merged = /** @type {string[]} */ ([]).concat(current, value);
|
|
1622
|
+
return this.setHeader(name, merged);
|
|
1515
1623
|
}
|
|
1516
1624
|
|
|
1517
1625
|
/**
|
|
@@ -1526,15 +1634,15 @@ module.exports = class Response extends LazyWritable {
|
|
|
1526
1634
|
if (typeof Headers === "function" && headers instanceof Headers) {
|
|
1527
1635
|
for (const name of new Set([...headers.keys()])) {
|
|
1528
1636
|
if (name === "set-cookie") {
|
|
1529
|
-
this.setHeader(name,
|
|
1637
|
+
this.setHeader(name, headers.getSetCookie());
|
|
1530
1638
|
} else {
|
|
1531
|
-
this.setHeader(name, /** @type {
|
|
1639
|
+
this.setHeader(name, /** @type {string} */ (headers.get(name)));
|
|
1532
1640
|
}
|
|
1533
1641
|
}
|
|
1534
1642
|
return this;
|
|
1535
1643
|
}
|
|
1536
1644
|
for (const [name, value] of headers) {
|
|
1537
|
-
this.setHeader(name,
|
|
1645
|
+
this.setHeader(name, value);
|
|
1538
1646
|
}
|
|
1539
1647
|
return this;
|
|
1540
1648
|
}
|
|
@@ -1551,8 +1659,8 @@ module.exports = class Response extends LazyWritable {
|
|
|
1551
1659
|
|
|
1552
1660
|
/**
|
|
1553
1661
|
* The Express name for set(), including the charset it adds to a content-type.
|
|
1554
|
-
* @param {
|
|
1555
|
-
* @param {
|
|
1662
|
+
* @param {string|object} field a header name, or an object of them
|
|
1663
|
+
* @param {string|string[]} [value] the header value, or nothing when the first argument is an object
|
|
1556
1664
|
* @returns {this}
|
|
1557
1665
|
*/
|
|
1558
1666
|
header(field, value) {
|
|
@@ -1581,9 +1689,17 @@ module.exports = class Response extends LazyWritable {
|
|
|
1581
1689
|
if (Array.isArray(out)) {
|
|
1582
1690
|
throw new TypeError("Content-Type cannot be set to an Array");
|
|
1583
1691
|
}
|
|
1584
|
-
//
|
|
1585
|
-
|
|
1586
|
-
|
|
1692
|
+
// an extension becomes its media type here, charset included
|
|
1693
|
+
const resolved = contentTypeSet(out);
|
|
1694
|
+
if (resolved === false) {
|
|
1695
|
+
// what the mime database knows nothing about is stored as the false express
|
|
1696
|
+
// stores, so send() and json() read it as unset and write their own type.
|
|
1697
|
+
// Through setHeader first, for the checks, then the boolean over the string
|
|
1698
|
+
this.setHeader(field, "false");
|
|
1699
|
+
this.headers[name] = /** @type {any} */ (false);
|
|
1700
|
+
return this;
|
|
1701
|
+
}
|
|
1702
|
+
out = resolved;
|
|
1587
1703
|
}
|
|
1588
1704
|
// the name as it was written, not the lowercased one: setHeader lowercases it itself,
|
|
1589
1705
|
// and it is the name that a refused header is reported by, which Express takes from
|
|
@@ -1644,6 +1760,9 @@ module.exports = class Response extends LazyWritable {
|
|
|
1644
1760
|
* @param {string} field
|
|
1645
1761
|
*/
|
|
1646
1762
|
removeHeader(field) {
|
|
1763
|
+
if (this.headersSent) {
|
|
1764
|
+
throw headersSentError("remove");
|
|
1765
|
+
}
|
|
1647
1766
|
const key = field.toLowerCase();
|
|
1648
1767
|
// the delete is a runtime call, and helmet removes a header most responses never carry
|
|
1649
1768
|
if (key in this.headers) {
|
|
@@ -1677,12 +1796,13 @@ module.exports = class Response extends LazyWritable {
|
|
|
1677
1796
|
* Renders a view and sends it. With a callback the result goes to the callback instead, and
|
|
1678
1797
|
* nothing is sent. A function in the options position is taken as the callback.
|
|
1679
1798
|
* @param {string} view view name
|
|
1680
|
-
* @param {Record<string, any
|
|
1799
|
+
* @param {Record<string, any>|((err: Error|null, html?: string) => void)} [options] locals for the
|
|
1800
|
+
* view, or the callback in its place
|
|
1681
1801
|
* @param {(err: Error|null, html?: string) => void} [callback]
|
|
1682
1802
|
*/
|
|
1683
1803
|
render(view, options, callback) {
|
|
1684
1804
|
if (typeof options === "function") {
|
|
1685
|
-
callback = /** @type {
|
|
1805
|
+
callback = /** @type {(err: Error|null, html?: string) => void} */ (options);
|
|
1686
1806
|
options = {};
|
|
1687
1807
|
}
|
|
1688
1808
|
if (!options) {
|
|
@@ -1720,7 +1840,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1720
1840
|
const opt = { ...(options ?? {}) }; // create a new ref because we change original object (https://github.com/dimdenGD/ultimate-express/issues/68)
|
|
1721
1841
|
// cookie-parser hangs the secret on the request, so it is read off it rather than
|
|
1722
1842
|
// declared here: without that middleware there is none, which is what this checks
|
|
1723
|
-
const req = /** @type {
|
|
1843
|
+
const req = /** @type {{secret?: string}} */ (this.req);
|
|
1724
1844
|
if (opt.signed && !req.secret) {
|
|
1725
1845
|
// the message has to read like this: it is the one Express throws, and it names the
|
|
1726
1846
|
// thing that is actually missing rather than the library that noticed
|
|
@@ -1739,7 +1859,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1739
1859
|
delete opt.maxAge;
|
|
1740
1860
|
}
|
|
1741
1861
|
if (opt.signed) {
|
|
1742
|
-
val = "s:" + sign(val, req.secret);
|
|
1862
|
+
val = "s:" + sign(val, /** @type {string} */ (req.secret));
|
|
1743
1863
|
}
|
|
1744
1864
|
|
|
1745
1865
|
if (opt.path == null) {
|
|
@@ -1784,7 +1904,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1784
1904
|
* Answers according to the Accept header, calling the handler whose key matches best. A
|
|
1785
1905
|
* `default` key catches everything else; without one an unmatched request gets 406.
|
|
1786
1906
|
* Sets Vary: Accept.
|
|
1787
|
-
* @param {Record<string,
|
|
1907
|
+
* @param {Record<string, Function>} object handlers keyed by extension or mime type
|
|
1788
1908
|
* @returns {this}
|
|
1789
1909
|
*/
|
|
1790
1910
|
format(object) {
|
|
@@ -1822,11 +1942,14 @@ module.exports = class Response extends LazyWritable {
|
|
|
1822
1942
|
* @returns {this}
|
|
1823
1943
|
*/
|
|
1824
1944
|
json(body) {
|
|
1945
|
+
const hot = this.app._hot();
|
|
1946
|
+
// serialised before the type is set, as express orders it: a body JSON.stringify refuses,
|
|
1947
|
+
// a BigInt for one, throws out of here with the response's headers as they were
|
|
1948
|
+
const json = stringify(body, hot.jsonReplacer, hot.jsonSpaces, hot.jsonEscape);
|
|
1825
1949
|
if (!this.headers["content-type"]) {
|
|
1826
1950
|
this.headers["content-type"] = JSON_UTF8;
|
|
1827
1951
|
}
|
|
1828
|
-
|
|
1829
|
-
return this.send(stringify(body, hot.jsonReplacer, hot.jsonSpaces, hot.jsonEscape));
|
|
1952
|
+
return this.send(json);
|
|
1830
1953
|
}
|
|
1831
1954
|
|
|
1832
1955
|
/**
|
|
@@ -1878,7 +2001,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
1878
2001
|
|
|
1879
2002
|
/**
|
|
1880
2003
|
* Adds to the Link header, one entry per key, the key being the rel.
|
|
1881
|
-
* @param {Record<string,
|
|
2004
|
+
* @param {Record<string, string>} links rel to url
|
|
1882
2005
|
* @returns {this}
|
|
1883
2006
|
*/
|
|
1884
2007
|
links(links) {
|
|
@@ -1981,7 +2104,8 @@ module.exports = class Response extends LazyWritable {
|
|
|
1981
2104
|
|
|
1982
2105
|
/**
|
|
1983
2106
|
* Sets Content-Type. An extension is looked up as a mime type and gets a charset; anything
|
|
1984
|
-
* containing a slash is used as written.
|
|
2107
|
+
* containing a slash is used as written. An extension the database does not know falls back
|
|
2108
|
+
* to octet-stream here, which res.set does not do. Also available as `contentType()`.
|
|
1985
2109
|
* @param {string} type
|
|
1986
2110
|
* @returns {this}
|
|
1987
2111
|
*/
|
|
@@ -2001,7 +2125,7 @@ module.exports = class Response extends LazyWritable {
|
|
|
2001
2125
|
vary(field) {
|
|
2002
2126
|
// the vary package decides: it throws when there is no field at all, and does nothing at
|
|
2003
2127
|
// all for an empty list, which is not the same thing and used to be refused here as well
|
|
2004
|
-
vary(/** @type {
|
|
2128
|
+
vary(/** @type {import("http").ServerResponse} */ (/** @type {unknown} */ (this)), field);
|
|
2005
2129
|
return this;
|
|
2006
2130
|
}
|
|
2007
2131
|
|
|
@@ -2036,4 +2160,5 @@ module.exports = class Response extends LazyWritable {
|
|
|
2036
2160
|
|
|
2037
2161
|
// res.contentType is res.type under express's other name. On the prototype rather than an instance
|
|
2038
2162
|
// field, which wrote one own property per response in the constructor.
|
|
2039
|
-
/** @type {
|
|
2163
|
+
/** @type {{contentType?: typeof module.exports.prototype.type}} */ (module.exports.prototype).contentType =
|
|
2164
|
+
module.exports.prototype.type;
|