fulmine.js 5.19.2 → 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/src/response.js CHANGED
@@ -43,17 +43,22 @@ const {
43
43
  cachedStat,
44
44
  NullObject
45
45
  } = require("./utils.js");
46
- const { Writable } = require("stream");
47
46
  const { isAbsolute } = require("path");
48
47
  const fs = require("fs");
49
48
  const Path = require("path");
50
49
  const statuses = require("statuses");
51
50
  const { sign } = require("cookie-signature");
52
- // events is faster at init, tseep is faster at sending events
53
- // since we create a ton of objects and dont send a ton of events, its better to use events here
54
- const { EventEmitter } = require("events");
55
- const http = require("http");
56
51
  const ms = require("ms");
52
+ const Socket = require("./socket.js");
53
+ const { LazyWritable } = require("./lazy-writable.js");
54
+ const {
55
+ kOutHeaders,
56
+ kShapeMode,
57
+ VALIDATED_HEADER_NAMES,
58
+ HEADER_NAME_BUF,
59
+ HEADER_VALUE_BUF,
60
+ statusLine
61
+ } = require("./response-utils.js");
57
62
 
58
63
  // How much a chunked response may gather before it is handed to uWS. Measured on uWS alone with
59
64
  // 33 KB written in 500 pieces: 16.2ms handed over one piece at a time, 0.79ms in 4 KB blocks,
@@ -67,48 +72,6 @@ const COALESCE_LIMIT = 16 * 1024;
67
72
  // here up goes straight through.
68
73
  const COALESCE_BELOW = 4 * 1024;
69
74
 
70
- const outgoingMessage = new http.OutgoingMessage();
71
- const symbols = Object.getOwnPropertySymbols(outgoingMessage);
72
- // if a future node renames it, fall back to a private symbol rather than writing a property
73
- // literally named "undefined", which is what indexing with undefined would do
74
- const kOutHeaders = symbols.find((s) => s.toString() === "Symbol(kOutHeaders)") ?? Symbol("kOutHeaders");
75
- // node's emitters tombstone a removed listener's slot instead of deleting it when this flag is
76
- // set, which is what keeps _events in a stable shape. EventEmitter.init sets it, and never runs
77
- // for the lazily-materialized response, so it is set by hand; a future rename degrades to the
78
- // delete, not to an error
79
- const kShapeMode =
80
- Object.getOwnPropertySymbols(new EventEmitter()).find((s) => s.toString() === "Symbol(shapeMode)") ??
81
- Symbol("shapeMode");
82
- // names setHeader has validated and lowercased, so the constant names middleware writes per
83
- // request are one Map hit. Insert-only after validation, bounded; only setHeader may insert,
84
- // the never-throwing readers keep their plain toLowerCase
85
- const VALIDATED_HEADER_NAMES = new Map();
86
- // The names and values that recur on every response, kept as Buffers for the uWS crossing: a
87
- // Buffer is memcpy'd as it is, a string pays a UTF-8 scan and copy per call. A header that is
88
- // not here just misses the lookup and crosses as the string it was. Names must stay lowercase,
89
- // which is how writeHeaders receives them.
90
- const HEADER_NAME_BUF = { __proto__: null };
91
- const HEADER_VALUE_BUF = { __proto__: null };
92
- for (const s of ["connection", "keep-alive", "content-type", "vary", "x-powered-by", "content-encoding"]) {
93
- HEADER_NAME_BUF[s] = Buffer.from(s);
94
- }
95
- for (const s of [
96
- "keep-alive",
97
- "timeout=10",
98
- "close",
99
- "Fulmine",
100
- "Accept-Encoding",
101
- "text/html; charset=utf-8",
102
- "text/plain; charset=utf-8",
103
- "application/json; charset=utf-8",
104
- "application/octet-stream",
105
- "gzip",
106
- "br",
107
- "deflate",
108
- "zstd"
109
- ]) {
110
- HEADER_VALUE_BUF[s] = Buffer.from(s);
111
- }
112
75
  const HIGH_WATERMARK = 128 * 1024;
113
76
  // the exact string json() writes, so send() can skip recomputing the charset on it
114
77
  const JSON_UTF8 = "application/json; charset=utf-8";
@@ -116,284 +79,6 @@ const JSON_UTF8 = "application/json; charset=utf-8";
116
79
  // than written out, since a year is already longer than any cache will honour.
117
80
  const MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000;
118
81
 
119
- class Socket extends EventEmitter {
120
- /**
121
- * The Socket's own error listener, shared across sockets: an error on the stand-in closes it,
122
- * which is the close the connection trackers wait for. EventEmitter calls it with this = the
123
- * emitter.
124
- *
125
- * @this {any}
126
- * @param {any} err
127
- */
128
- static _onError(err) {
129
- this.emit("close");
130
- }
131
-
132
- /**
133
- * Enough of a node socket for the middleware that reaches for one. There is no socket object
134
- * in uWS to hand over, so this stands in and forwards what it can to the response.
135
- *
136
- * @param {any} response
137
- */
138
- constructor(response) {
139
- super();
140
- this.response = response;
141
- this[kShapeMode] = true;
142
- // middleware assigns to this one, which is why it is a field rather than a getter: express
143
- // reads socket.encrypted for req.protocol and a proxy shim writes it
144
- this.encrypted = response.req.app.ssl;
145
- this.localPort = response.req.app.port;
146
- // on-finished reads socket.readable before anything else, and a socket without one reads
147
- // as a request that is already over
148
- this.readable = true;
149
-
150
- // shared, not an arrow: one per process instead of one per materialized socket
151
- this.on("error", Socket._onError);
152
- }
153
-
154
- /** Whether anything more can be written, which stops being true once the response is done. */
155
- get writable() {
156
- return !this.response.finished;
157
- }
158
-
159
- /** The peer, as node reports it. Reading it out of µWS is slow, so the request caches it. */
160
- get remoteAddress() {
161
- return this.response.req.parsedIp;
162
- }
163
-
164
- /** A native µWS call almost no caller makes, so it stays behind its getter. */
165
- get remotePort() {
166
- return this.response.req._res.getRemotePort();
167
- }
168
-
169
- /**
170
- * node's socket carries these three and applications call them on a request they mean to hold
171
- * open, almost always to take the timeout off. µWS has no per socket timeout reachable from
172
- * javascript, so they do nothing and hand the socket back the way node's do. n8n's chat trigger
173
- * calls setTimeout on every webhook, and without it the workflow answered 500.
174
- * @returns {this}
175
- */
176
- setTimeout() {
177
- return this;
178
- }
179
-
180
- /** @returns {this} */
181
- setKeepAlive() {
182
- return this;
183
- }
184
-
185
- /** @returns {this} */
186
- setNoDelay() {
187
- return this;
188
- }
189
-
190
- /**
191
- * Finishes the response through the socket, which is how the middleware that only knows
192
- * about sockets ends one.
193
- * @param {any} [body]
194
- */
195
- end(body) {
196
- this.response.end(body);
197
- }
198
-
199
- /**
200
- * What a server side socket answers about itself. µWS owns the connection, so these follow the
201
- * response: it is open until the response is over, and it was never a socket being dialled.
202
- */
203
- get destroyed() {
204
- return this.response.finished === true;
205
- }
206
-
207
- /** @returns {string} "open" until the response is over, as a served socket reads. */
208
- get readyState() {
209
- return this.response.finished === true ? "closed" : "open";
210
- }
211
-
212
- /** @returns {boolean} never: this end was accepted, not dialled. */
213
- get connecting() {
214
- return false;
215
- }
216
-
217
- /** @returns {boolean} never, for the same reason. */
218
- get pending() {
219
- return false;
220
- }
221
-
222
- /**
223
- * The end of the connection node reports here. There is no address to read back from µWS, so
224
- * this is the port the application bound and the family the peer arrived on.
225
- * @returns {{address: string|undefined, family: string, port: number|undefined}}
226
- */
227
- address() {
228
- const remote = this.response.req.parsedIp;
229
- return {
230
- address: this.response.req.app._listenHost,
231
- family: remote?.includes(":") ? "IPv6" : "IPv4",
232
- port: this.localPort
233
- };
234
- }
235
-
236
- /**
237
- * Drops the connection, which is what an application does to a client it will not serve. node
238
- * takes an error and re-emits it; this closes and says so through 'close', since there is no
239
- * socket underneath to carry an error of its own.
240
- * @returns {this}
241
- */
242
- destroy() {
243
- this.close();
244
- return this;
245
- }
246
-
247
- /** @returns {this} */
248
- destroySoon() {
249
- this.close();
250
- return this;
251
- }
252
-
253
- /**
254
- * Holds and resumes the body arriving on this connection, which is the only half of node's
255
- * pause() that means anything here: the response is written when the application writes it.
256
- * @returns {this}
257
- */
258
- pause() {
259
- this.response.req.pause();
260
- return this;
261
- }
262
-
263
- /** @returns {this} */
264
- resume() {
265
- this.response.req.resume();
266
- return this;
267
- }
268
-
269
- /**
270
- * node writes these bytes past the response, straight onto the connection. There is no way
271
- * past µWS's framing here, so they go through the response instead, which is what the
272
- * middleware writing to a socket means by it.
273
- *
274
- * @param {any} chunk
275
- * @param {any} [encoding]
276
- * @param {any} [callback]
277
- * @returns {boolean}
278
- */
279
- write(chunk, encoding, callback) {
280
- return this.response.write(chunk, encoding, callback);
281
- }
282
-
283
- /** The event loop is µWS's, so there is nothing to hold open or let go. @returns {this} */
284
- ref() {
285
- return this;
286
- }
287
-
288
- /** @returns {this} */
289
- unref() {
290
- return this;
291
- }
292
-
293
- /** Closes the connection outright, without finishing a response first. */
294
- close() {
295
- if (this.response.finished) {
296
- return;
297
- }
298
- this.response.finished = true;
299
- this.emit("close");
300
- this.response._res.close();
301
- }
302
- }
303
-
304
- // One status line per code, built on first use: the default path, with no custom reason phrase,
305
- // paid a template string and a trim per request for a line that never changes. Bounded to real
306
- // HTTP codes so a wild writeHead value cannot grow the array or flip it into dictionary mode.
307
- const STATUS_LINES = [];
308
- /**
309
- * @param {number} code
310
- * @param {string|undefined} text an explicit reason phrase, which bypasses the cache
311
- * @returns {string} the uWS status line, e.g. "200 OK"
312
- */
313
- function statusLine(code, text) {
314
- if (text === undefined && Number.isInteger(code) && code >= 100 && code <= 999) {
315
- return STATUS_LINES[code] ?? (STATUS_LINES[code] = code + " " + (statuses.message[code] ?? "unknown"));
316
- }
317
- return `${code} ${text ?? statuses.message[code] ?? "unknown"}`.trim();
318
- }
319
-
320
- /**
321
- * A Writable that has not built its state yet, the mirror of LazyReadable in request.js and there
322
- * for the same reason: a response is a Writable because middleware expects one, and the ordinary
323
- * one never uses it. `send()` reaches `end()`, which is overridden here and goes straight to
324
- * _finish, so the WritableState is allocated for every response and read by nobody. It is needed
325
- * only by res.write(), by a stream piped into the response, by cork and by the writableX getters.
326
- *
327
- * `Response extends LazyWritable`, whose prototype is Writable's, so `res instanceof Writable`
328
- * stays true and every Writable method is reachable; what is missing is `_writableState`, built on
329
- * the first touch. Measured at 45 nanoseconds a response on the machine this was written on.
330
- *
331
- * As on the Readable side the wrapping is generated rather than written out: every own member of
332
- * Writable's prototype gets a version that materialises first, so there is no list to keep in step.
333
- * Missing one would not be a slow path, it would be a TypeError on `undefined._writableState`.
334
- */
335
- class LazyWritableBase {}
336
- Object.setPrototypeOf(LazyWritableBase.prototype, Writable.prototype);
337
- Object.setPrototypeOf(LazyWritableBase, Writable);
338
-
339
- // what the chain says at runtime, said again for the type checker, which cannot see a prototype
340
- // being reassigned
341
- const LazyWritable = /** @type {typeof Writable} */ (/** @type {unknown} */ (LazyWritableBase));
342
-
343
- /**
344
- * Builds the stream this object has been pretending to be. Idempotent: everything reachable from
345
- * outside goes through it, so it is called far more often than it does anything.
346
- *
347
- * EventEmitter's init keeps an _events that is already there, so both the shape the constructor
348
- * wrote and any listener added before this survive it.
349
- *
350
- * @param {any} stream
351
- */
352
- function materialiseWritable(stream) {
353
- if (stream._writableState === undefined) {
354
- Writable.call(stream);
355
- }
356
- }
357
-
358
- for (const member of [
359
- ...Object.getOwnPropertyNames(Writable.prototype),
360
- ...Object.getOwnPropertySymbols(Writable.prototype)
361
- ]) {
362
- if (member === "constructor") {
363
- continue;
364
- }
365
- const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Writable.prototype, member));
366
- if (typeof descriptor.value === "function") {
367
- const inner = descriptor.value;
368
- Object.defineProperty(LazyWritableBase.prototype, member, {
369
- ...descriptor,
370
- /** @this {any} @param {...any} args */
371
- value: function (...args) {
372
- materialiseWritable(this);
373
- return inner.apply(this, args);
374
- }
375
- });
376
- } else if (descriptor.get || descriptor.set) {
377
- const innerGet = descriptor.get;
378
- const innerSet = descriptor.set;
379
- Object.defineProperty(LazyWritableBase.prototype, member, {
380
- ...descriptor,
381
- get: innerGet
382
- ? /** @this {any} */ function () {
383
- materialiseWritable(this);
384
- return innerGet.call(this);
385
- }
386
- : undefined,
387
- set: innerSet
388
- ? /** @this {any} @param {any} value */ function (value) {
389
- materialiseWritable(this);
390
- innerSet.call(this, value);
391
- }
392
- : undefined
393
- });
394
- }
395
- }
396
-
397
82
  module.exports = class Response extends LazyWritable {
398
83
  /** @type {Socket|null} */
399
84
  #socket = null;
@@ -409,10 +94,8 @@ module.exports = class Response extends LazyWritable {
409
94
  *
410
95
  * uWS charges for a write against everything already buffered behind it, so a chunked response
411
96
  * written in many small pieces costs quadratically once it passes the socket's own buffer:
412
- * measured on uWS alone, 500 writes of 66 bytes take 13ms against 0.06ms for 100 of them.
413
- * Handing it the same bytes in blocks costs 0.4ms. Nothing here changes what goes on the wire,
414
- * only how many calls it takes to put it there.
415
- * Null until the first chunked write, since a res.send never queues anything.
97
+ * measured on uWS alone, 500 writes of 66 bytes take 13ms against 0.06ms for 100 of them, and
98
+ * the same bytes in blocks cost 0.4ms. Null until the first chunked write.
416
99
  * @type {Buffer[]|null}
417
100
  */
418
101
  #queued = null;
@@ -426,6 +109,13 @@ module.exports = class Response extends LazyWritable {
426
109
  /** @type {any} */
427
110
  #outHeaders = null;
428
111
 
112
+ /**
113
+ * Whether node's writeHead has run, which only the per-app subclass sets. _sendOptionsReply
114
+ * refuses to write a second head over it, as node's setHeader does.
115
+ * @type {boolean|undefined}
116
+ */
117
+ _headWritten;
118
+
429
119
  /**
430
120
  * The request this response answers, linked so either reaches the other.
431
121
  * @type {InstanceType<typeof import("./request.js")>}
@@ -438,7 +128,8 @@ module.exports = class Response extends LazyWritable {
438
128
  * when the setting asks for it.
439
129
  *
440
130
  * @param {any} res the uWS response
441
- * @param {any} req the Request, already built, which is where the connection header is read from
131
+ * @param {any} req the Request, already built. Loose because the per-app subclass in
132
+ * application.js inherits this constructor and its own shape has to stay assignable
442
133
  * @param {any} app the application or router this request arrived at
443
134
  */
444
135
  constructor(res, req, app) {
@@ -505,20 +196,17 @@ module.exports = class Response extends LazyWritable {
505
196
  // false while the uWS route handler is still in its synchronous window, where uWS holds
506
197
  // the socket corked itself; the two uWS entry points flip it once that window closes
507
198
  this._corkNeeded = false;
508
- // shared methods, not arrows: two closures and a once() wrapper here were four
509
- // allocations per request. EventEmitter calls listeners with this = the emitter.
199
+ // shared methods, not arrows: two closures and a once() wrapper here were four allocations
200
+ // per request. EventEmitter calls listeners with this = the emitter.
510
201
  //
511
202
  // Written into the map rather than through on(). A stream arrives with its _events already
512
203
  // shaped, "close" and "error" among the keys and every value undefined, so filling two of
513
- // them is the same hidden class on() would have produced and none of its work: the argument
514
- // check, the newListener emission, the is-there-one-already branch and the max listener
515
- // count. Two calls per response, and the profile put them at 6% of a hello-world.
204
+ // them is the same hidden class on() would produce and none of its work. Two calls per
205
+ // response, and the profile put them at 6% of a hello-world.
516
206
  //
517
- // The condition is the whole safety of it: a fresh response has no listeners, so both slots
518
- // are free, and anything else falls back to on(). Adding one later goes through on() as
519
- // usual and finds what this wrote, because this wrote what on() writes.
520
- // cast because _events and _eventsCount are EventEmitter's own bookkeeping and carry no
521
- // type: they are what on() writes, and this writes the same two entries
207
+ // The condition is the safety of it: a fresh response has no listeners, so both slots are
208
+ // free, and anything else falls back to on(), which finds what this wrote.
209
+ // cast because _events and _eventsCount are EventEmitter's own bookkeeping and have no type
522
210
  const self = /** @type {any} */ (this);
523
211
  const events = self._events;
524
212
  if (
@@ -556,17 +244,15 @@ module.exports = class Response extends LazyWritable {
556
244
  /**
557
245
  * Drops the connection, which is how an application abandons a response it cannot finish: a
558
246
  * download whose source dies mid-transfer has to leave the client with a reset rather than a
559
- * truncated body it would take for the whole file. node destroys the socket here and µWS's
560
- * close() is the same thing; without it the client waited for bytes that were never coming and
561
- * the request hung until its own timeout. LibreChat's download route is written exactly that
562
- * way, `stream.on("error", () => res.destroy())`.
247
+ * truncated body. node destroys the socket here and uWS's close() is the same thing; without it
248
+ * the client waited for bytes that never came. LibreChat's download route is written that way,
249
+ * `stream.on("error", () => res.destroy())`.
563
250
  *
564
- * A response that is over, or one whose client is already gone, only tears the stream down:
565
- * there is nothing left to close, and touching an aborted µWS response is a use after free.
566
- * One difference from node stays: writableEnded reads true after this, because it is answered
567
- * from the same finished flag the close sets, where node leaves it false until end() is called.
251
+ * A response that is over, or one whose client is gone, only tears the stream down: touching an
252
+ * aborted uWS response is a use after free. writableEnded reads true after this, where node
253
+ * leaves it false until end() is called.
568
254
  *
569
- * @param {any} [error]
255
+ * @param {any} [error] whatever the caller is destroying the response with
570
256
  * @returns {this}
571
257
  */
572
258
  destroy(error) {
@@ -613,12 +299,9 @@ module.exports = class Response extends LazyWritable {
613
299
 
614
300
  /**
615
301
  * Where node keeps the outgoing headers of an OutgoingMessage. Only code going through node's
616
- * own header path ever looks, cookie-session being the one in this project's tests, so the
617
- * proxy standing in for it is built on the first look rather than on every response: it was a
618
- * proxy, a handler object and two closures each time, for something almost nothing reads.
619
- *
620
- * A setter as well, because node assigns to this slot when it resets the headers, and a getter
621
- * on its own would make that throw.
302
+ * own header path looks, cookie-session being the one in this project's tests, so the proxy
303
+ * standing in for it is built on the first look: it was a proxy, a handler object and two
304
+ * closures per response otherwise. A setter too, because node assigns to this slot on a reset.
622
305
  */
623
306
  get [kOutHeaders]() {
624
307
  if (!this.#outHeaders) {
@@ -712,7 +395,8 @@ module.exports = class Response extends LazyWritable {
712
395
  * The booked flush. Static, so a response that never writes in pieces allocates nothing for it:
713
396
  * nextTick forwards the receiver as an argument.
714
397
  *
715
- * @param {any} res
398
+ * @param {any} res the response whose queue is being flushed. Loose because naming the class
399
+ * inside its own body makes the checker see two unrelated `this` types
716
400
  */
717
401
  static #flushOnTick(res) {
718
402
  res.#flushBooked = false;
@@ -726,7 +410,7 @@ module.exports = class Response extends LazyWritable {
726
410
  * said how much there would be. Backpressure comes back as onWritable, which is what defers the
727
411
  * callback rather than dropping the chunk.
728
412
  *
729
- * @param {any} chunk
413
+ * @param {any} chunk whatever a Writable was handed, which node does not narrow
730
414
  * @param {BufferEncoding} encoding
731
415
  * @param {(err?: Error|null) => void} callback
732
416
  */
@@ -819,16 +503,13 @@ module.exports = class Response extends LazyWritable {
819
503
 
820
504
  /**
821
505
  * Sets the status and, optionally, a batch of headers, the way node does. The second argument
822
- * is either the status message or the headers, since node allows both shapes.
823
- *
824
- * Nothing is written here despite the name: the headers go out when the body does.
506
+ * is either the status message or the headers, since node allows both shapes. Nothing is
507
+ * written here despite the name: the headers go out when the body does.
825
508
  *
826
509
  * Every header goes through setHeader and not through set. This is node's method, not
827
- * Express's: Express does not override it, so a content-type given here keeps the value it was
828
- * given, where `res.set("content-type", "text/html")` would have a charset appended to it. A
829
- * handler that builds its own response and writes it with writeHead is how every meta-framework
830
- * on top of Express answers, @astrojs/node and @sveltejs/adapter-node included, so the charset
831
- * was being added to pages nobody asked it for.
510
+ * Express's: a content-type given here keeps the value it was given, where res.set would append
511
+ * a charset. Every meta-framework on Express answers with writeHead, @astrojs/node and
512
+ * @sveltejs/adapter-node included, so the charset was added to pages nobody asked it for.
832
513
  *
833
514
  * @param {number} statusCode
834
515
  * @param {string|Record<string, any>|any[]} [statusMessage] the reason phrase, or the headers
@@ -867,13 +548,12 @@ module.exports = class Response extends LazyWritable {
867
548
  }
868
549
 
869
550
  /**
870
- * Writes every header set so far to uWS, which is the point of no return. Content-Length is
871
- * not one of them: uWS wants the length through tryEnd or endWithoutBody, so it is taken out
872
- * here and kept on totalSize, where it also turns chunked framing off.
551
+ * Writes every header set so far to uWS, which is the point of no return. Content-Length is not
552
+ * one of them: uWS wants the length through tryEnd or endWithoutBody, so it is taken out here
553
+ * and kept on totalSize, where it also turns chunked framing off.
873
554
  *
874
- * One writeHeader per header on purpose. Packing the whole head into a single writeStatus
875
- * works on the wire, and was measured slower: constant header strings cross the boundary
876
- * already flat, while the packed head is concatenated fresh per response, see issue #11.
555
+ * One writeHeader per header on purpose. Packing the whole head into a single writeStatus works
556
+ * on the wire and measured slower: constant header strings cross already flat, see issue #11.
877
557
  *
878
558
  * @param {boolean} utf8 unused, kept because node's equivalent takes it and the two callers
879
559
  * differ on what they know about the body
@@ -890,12 +570,10 @@ module.exports = class Response extends LazyWritable {
890
570
  // the value here is nearly always the 10-char "keep-alive", which paid a scan per response
891
571
  const closing =
892
572
  typeof connection === "string" && connection.length === 5 && connection.toLowerCase() === "close";
893
- // for..in over an object some responses delete from, which is the shape the request side
894
- // was taken off for #rawHeadersEntries. It stays here, and the difference is where the
895
- // deletes are: a 200 with a body performs none. Only 204, 304, 205, the freshness branch of
896
- // sendFile and removeHeader do, this object is built fresh per response, so a dictionary one
897
- // of them made costs that response and nothing after it. The request side deleted on every
898
- // request, which is what made it worth a different structure
573
+ // for..in over an object some responses delete from, the shape the request side was taken
574
+ // off for #rawHeadersEntries. It stays here because the deletes are rare: only 204, 304,
575
+ // 205, the freshness branch of sendFile and removeHeader do it, and this object is built
576
+ // fresh per response. The request side deleted on every request
899
577
  for (const header in headers) {
900
578
  if (closing && header === "keep-alive") {
901
579
  continue;
@@ -967,7 +645,7 @@ module.exports = class Response extends LazyWritable {
967
645
  }
968
646
 
969
647
  /**
970
- * @param {any} [data]
648
+ * @param {any} [data] the last body piece, or the callback in node's two-argument shape
971
649
  * @param {any} [cb]
972
650
  * @returns {this}
973
651
  */
@@ -1005,7 +683,7 @@ module.exports = class Response extends LazyWritable {
1005
683
  * The corked tail of end(): status, headers, body and the finish events. Split out so a
1006
684
  * synchronous answer calls it straight, already inside uWS's own cork.
1007
685
  *
1008
- * @param {any} data
686
+ * @param {any} data the last body piece
1009
687
  * @param {any} cb
1010
688
  */
1011
689
  _finish(data, cb) {
@@ -1014,13 +692,12 @@ module.exports = class Response extends LazyWritable {
1014
692
  // res.write(), not whether this call is about to write the head itself
1015
693
  const headWasAlreadyOut = this.headersSent;
1016
694
  if (!this.headersSent) {
1017
- // freshness is not decided here. node's end() knows nothing about conditional
1018
- // requests, and Express answers 304 from send() and from sendFile(), each of
1019
- // which strips the entity headers first. Deciding it here meant res.end("body")
1020
- // answered 304 and dropped the body that the caller had just written.
1021
- // "unknown" for a code without a message, as node's status line has it.
1022
- // The default 200 with no phrase is not written at all: uWS emits the identical
1023
- // "HTTP/1.1 200 OK" head on its own, and the crossing costs more than it says
695
+ // freshness is not decided here. node's end() knows nothing about conditional requests,
696
+ // and Express answers 304 from send() and from sendFile(), each of which strips the
697
+ // entity headers first. Deciding it here made res.end("body") answer 304 and drop the
698
+ // body the caller had just written.
699
+ // "unknown" for a code without a message, as node's status line has it. The default 200
700
+ // with no phrase is not written at all: uWS emits the identical head on its own
1024
701
  if (this.statusCode !== 200 || this.statusText !== undefined) {
1025
702
  this._res.writeStatus(statusLine(this.statusCode, this.statusText));
1026
703
  }
@@ -1028,15 +705,12 @@ module.exports = class Response extends LazyWritable {
1028
705
  }
1029
706
  const contentLength = this.headers["content-length"];
1030
707
  // The client said this connection ends here, and it is this end() that has to make it so.
1031
- // µWS closes by itself for a bare "close" and not for a list, so "keep-alive, close" left
1032
- // the socket open and the bytes after that request were read as another one: node closes
1033
- // there, and a server that does not is a server the client and it disagree with about how
1034
- // many requests were sent. See saysClose.
708
+ // uWS closes by itself for a bare "close" and not for a list, so "keep-alive, close" left
709
+ // the socket open and the bytes after that request were read as another one. See saysClose.
1035
710
  //
1036
- // Only where a length goes out with it. endWithoutBody takes the flag as its second
711
+ // Only where a length goes out with it: endWithoutBody takes the flag as its second
1037
712
  // argument and reads the first as the length whatever it holds, so asking it to close
1038
- // without one writes "Content-Length: 9223372036854775808" onto a 204. Those two paths keep
1039
- // µWS's own rule, which closes for a bare "close" and not for a list.
713
+ // without one writes "Content-Length: 9223372036854775808" onto a 204.
1040
714
  const closeConnection = this.req._connectionClose === true;
1041
715
  // 204 and 304 carry no body, so no Content-Length may describe one either; 1xx is the
1042
716
  // third case, by range
@@ -1051,11 +725,9 @@ module.exports = class Response extends LazyWritable {
1051
725
  // whatever is still queued goes first: end() must not overtake the body written before it
1052
726
  this.#flushQueued(null);
1053
727
  // The head has already gone out without a length, which is what flushHeaders() and the
1054
- // first res.write() both do, so this response is committed to chunked framing and a
1055
- // length can no longer describe it. node is committed the same way: after a flush,
1056
- // res.end("body") sends a chunk, not a Content-Length. Handing the body to uWS's end()
1057
- // here would have it append a length to a head that already said otherwise, which is
1058
- // what the comparison test caught.
728
+ // first res.write() both do, so this response is committed to chunked framing. node is
729
+ // committed the same way: after a flush, res.end("body") sends a chunk. Handing the
730
+ // body to uWS's end() here would append a length to a head that already said otherwise
1059
731
  if (data) {
1060
732
  this._res.write(data);
1061
733
  this._sentBody = data;
@@ -1157,13 +829,10 @@ module.exports = class Response extends LazyWritable {
1157
829
  }
1158
830
  // the ETag belongs here rather than in end(): node's end() does not produce one, so
1159
831
  // res.end() and res.redirect() must not either. It has to be set before end() reads
1160
- // req.fresh, which compares If-None-Match against it.
1161
- // body is defined by the time it gets here, so an empty one still earns an ETag. Testing
1162
- // its truthiness instead meant send("") and send(null) came back without one.
1163
- // Every method by default, not only GET and HEAD: gating it looked safe, freshness being
1164
- // defined over those two alone, and express's own suite failed on it, "should send ETag
1165
- // in response to <METHOD> request" exists per method. The "etag methods" setting is that
1166
- // gate as an opt-in, see issue #10.
832
+ // req.fresh, which compares If-None-Match against it. body is defined by the time it gets
833
+ // here, so an empty one still earns an ETag; testing truthiness left send("") and
834
+ // send(null) without one. Every method by default, not only GET and HEAD: express's own
835
+ // suite has "should send ETag in response to <METHOD> request" per method, see issue #10
1167
836
  const hot = this.app._hot();
1168
837
  const etagFn = hot.etagFn;
1169
838
  if (
@@ -1225,14 +894,11 @@ module.exports = class Response extends LazyWritable {
1225
894
  options = new NullObject();
1226
895
  }
1227
896
  if (!options) options = new NullObject();
1228
- // the callback is optional: without one, errors go to next(). The router assigns req.next
1229
- // before any handler can run, so by the time sendFile is reachable it is always there.
1230
- // Express's completion handler, exactly: a callback hears everything and the response is
1231
- // left alone, so it can still answer 200 after a 404 error. Without one, a directory
1232
- // falls through as a plain next(), and aborts and write errors go nowhere.
1233
- // the router's next and not the route's: express reports a file it could not serve past
1234
- // the rest of the route, so a four argument handler written inside the route never sees
1235
- // it. _leaveRoute is that; req.next is kept for everything else, see Walk#stepOutOfRoute
897
+ // the callback is optional: without one, errors go to next(). Express's completion handler
898
+ // exactly: a callback hears everything and the response is left alone, so it can still
899
+ // answer 200 after a 404 error. Without one, a directory falls through as a plain next().
900
+ // The router's next and not the route's: express reports a file it could not serve past the
901
+ // rest of the route, so a four argument handler inside the route never sees it
1236
902
  const next = this.req._leaveRoute ?? this.req.next;
1237
903
  const done = /** @type {(err?: any) => void} */ (
1238
904
  (err) => {
@@ -1242,14 +908,11 @@ module.exports = class Response extends LazyWritable {
1242
908
  }
1243
909
  );
1244
910
  // default options
1245
- // Normalised the way send does, and it is not fussiness: max-age takes a non-negative
1246
- // integer count of seconds, so 0.5, -1 and Infinity are all invalid, and a client that
1247
- // cannot read the directive may throw away the whole Cache-Control header with it. A
1248
- // fractional maxAge came out as "max-age=0.5" here, a negative one as "max-age=-1" and
1249
- // Infinity as "max-age=Infinity".
1250
- // Number() around the lot, and not only around the branch that is already a number: ms()
1251
- // answers undefined for a duration it cannot read, and Number.isNaN(undefined) is false,
1252
- // so an unreadable string reached the header as "max-age=NaN".
911
+ // Normalised the way send does: max-age takes a non-negative integer count of seconds, so
912
+ // 0.5, -1 and Infinity are all invalid, and a client that cannot read the directive may
913
+ // throw away the whole Cache-Control header. Number() around the lot and not only around
914
+ // the branch that is already a number: ms() answers undefined for a duration it cannot
915
+ // read, and Number.isNaN(undefined) is false, so an unreadable string reached it as NaN
1253
916
  const maxAge = Number(
1254
917
  typeof options.maxAge === "string" ? ms(/** @type {any} */ (options.maxAge)) : options.maxAge
1255
918
  );
@@ -1642,14 +1305,13 @@ module.exports = class Response extends LazyWritable {
1642
1305
  }
1643
1306
 
1644
1307
  /**
1645
- * Throws away any header that could not be written, so that flushing this response cannot fail
1646
- * on one. setHeader refuses these on the way in, but `res.headers` is the live object, so an
1308
+ * Throws away any header that could not be written, so flushing this response cannot fail on
1309
+ * one. setHeader refuses these on the way in, but `res.headers` is the live object, so an
1647
1310
  * assignment into that still gets a value in here.
1648
1311
  *
1649
1312
  * Only the error page calls it. A throw out of the flush there is not recoverable: the error
1650
- * page is what runs after a throw, so it would be the second one, with nobody left to catch
1651
- * it, and on the node shim that is the process. Everything writable is left alone, since a
1652
- * middleware's own headers belong on the error response too.
1313
+ * page is what runs after a throw, so nobody is left to catch it. Everything writable is left
1314
+ * alone, since a middleware's own headers belong on the error response too.
1653
1315
  *
1654
1316
  * @returns {void}
1655
1317
  */
@@ -1663,17 +1325,15 @@ module.exports = class Response extends LazyWritable {
1663
1325
 
1664
1326
  /**
1665
1327
  * Hands the status line and the headers over now, without waiting for a body, which is node's
1666
- * flushHeaders(). Callers use it to let the client start on the head while the body is still
1667
- * being produced, and one of them is `@angular/ssr`'s writeResponseToNodeResponse, which calls
1668
- * it before streaming a rendered page.
1328
+ * flushHeaders(). `@angular/ssr`'s writeResponseToNodeResponse calls it before streaming a page.
1669
1329
  *
1670
1330
  * **The head does not reach the wire here.** uWS holds it until the first body chunk, so a
1671
1331
  * client sees nothing until then, where express answers at once. `beginWrite` is uWS's API for
1672
1332
  * this and is unusable: it emits a stray CRLF before the first chunk size, which node's parser
1673
- * rejects as HPE_INVALID_CHUNK_SIZE. Checked against v20.69.0, the latest release.
1333
+ * rejects as HPE_INVALID_CHUNK_SIZE. Checked against v20.69.0.
1674
1334
  *
1675
1335
  * A second call does nothing, as node's does. Nothing is written for a response already
1676
- * finished or aborted: uWS has let go of it by then.
1336
+ * finished or aborted.
1677
1337
  *
1678
1338
  * @returns {void}
1679
1339
  */
@@ -1695,13 +1355,11 @@ module.exports = class Response extends LazyWritable {
1695
1355
  }
1696
1356
 
1697
1357
  /**
1698
- * node's `writeEarlyHints`, which sends a `103` carrying the resources the page will want, so a
1699
- * browser can start fetching them while the server is still rendering.
1358
+ * node's `writeEarlyHints`, which sends a `103` carrying the resources the page will want.
1700
1359
  *
1701
- * **Nothing is sent here.** µWebSockets.js has no API for an informational response, so the
1702
- * hints cannot reach the wire, and this exists so that code written for Express keeps running
1703
- * rather than dying on "res.writeEarlyHints is not a function". The callback is still called,
1704
- * because node calls it once the hints are out and a caller may be waiting on it.
1360
+ * **Nothing is sent here.** uWebSockets.js has no API for an informational response, so this
1361
+ * exists only so code written for Express keeps running instead of dying on
1362
+ * "res.writeEarlyHints is not a function". The callback is still called, as node calls it.
1705
1363
  *
1706
1364
  * `writeContinue` and `writeProcessing` below are the same story with `100` and `102`.
1707
1365
  *
@@ -1781,7 +1439,7 @@ module.exports = class Response extends LazyWritable {
1781
1439
  * node's `assignSocket`, which the http server uses when a response is
1782
1440
  * handed a raw socket. There is no such socket here.
1783
1441
  *
1784
- * @param {any} [socket]
1442
+ * @param {any} [socket] node takes one here and there is none to take
1785
1443
  * @returns {void}
1786
1444
  */
1787
1445
  assignSocket(socket) {}
@@ -1789,7 +1447,7 @@ module.exports = class Response extends LazyWritable {
1789
1447
  /**
1790
1448
  * node's `detachSocket`, which the http server uses when a response is
1791
1449
  * handed a raw socket. There is no such socket here.
1792
- * @param {any} [socket]
1450
+ * @param {any} [socket] node takes one here and there is none to take
1793
1451
  * @returns {void}
1794
1452
  */
1795
1453
  detachSocket(socket) {}
@@ -1894,7 +1552,7 @@ module.exports = class Response extends LazyWritable {
1894
1552
  /**
1895
1553
  * The Express name for set(), including the charset it adds to a content-type.
1896
1554
  * @param {any} field a header name, or an object of them
1897
- * @param {any} [value]
1555
+ * @param {any} [value] the header value, or nothing when the first argument is an object
1898
1556
  * @returns {this}
1899
1557
  */
1900
1558
  header(field, value) {
@@ -2365,10 +2023,9 @@ module.exports = class Response extends LazyWritable {
2365
2023
 
2366
2024
  /**
2367
2025
  * Whether end() has been called. node sets this one there and writableFinished later, once the
2368
- * bytes are out; here end() hands the whole response to µWS, so the two are the same moment.
2026
+ * bytes are out; here end() hands the whole response to uWS, so the two are the same moment.
2369
2027
  * Without it the base property answered false forever, and an application that asks whether it
2370
- * has already answered - LibreChat's agent stream does, before it decides whether to keep a
2371
- * subscription - carried on writing to a response that was over.
2028
+ * has already answered, as LibreChat's agent stream does, kept writing to a dead response.
2372
2029
  */
2373
2030
  // @ts-expect-error TS2611, the accessor replacing the base property is deliberate. Expect
2374
2031
  // rather than ignore, so it fails loudly if it ever stops applying.