fulmine.js 5.1.0 → 5.1.1
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/package.json +1 -1
- package/src/application.js +5 -1
- package/src/request.js +7 -5
- package/src/response.js +64 -38
- package/src/router.js +4 -0
package/package.json
CHANGED
package/src/application.js
CHANGED
|
@@ -388,7 +388,11 @@ class Application extends Router {
|
|
|
388
388
|
const response = request.res;
|
|
389
389
|
|
|
390
390
|
try {
|
|
391
|
-
const
|
|
391
|
+
const routed = this._routeRequest(request, response);
|
|
392
|
+
// dispatch has run its synchronous stretch inside _routeRequest by now, still
|
|
393
|
+
// under the cork uWS holds for this callback; the await below leaves it
|
|
394
|
+
response._corkNeeded = true;
|
|
395
|
+
const matchedRoute = await routed;
|
|
392
396
|
if (!matchedRoute && !response.headersSent && !response.aborted) {
|
|
393
397
|
this._endUnmatched(request, response);
|
|
394
398
|
}
|
package/src/request.js
CHANGED
|
@@ -140,7 +140,8 @@ module.exports = class Request extends Readable {
|
|
|
140
140
|
|
|
141
141
|
// Flat, name then value: an array of pairs meant one array allocated per header on every
|
|
142
142
|
// request, and a request carries eight or ten of them. Everything that reads this walks it two
|
|
143
|
-
// at a time.
|
|
143
|
+
// at a time. The names are lowercase by contract: uWS lowers them on the wire and the node
|
|
144
|
+
// shim lowers them in its forEach, so readers compare without lowering again.
|
|
144
145
|
#rawHeadersEntries = [];
|
|
145
146
|
|
|
146
147
|
/** @type {string|undefined|null} */
|
|
@@ -665,10 +666,10 @@ module.exports = class Request extends Readable {
|
|
|
665
666
|
const entries = this.#rawHeadersEntries;
|
|
666
667
|
for (let i = 0, len = entries.length; i < len; i += 2) {
|
|
667
668
|
const key = entries[i];
|
|
668
|
-
// 'if-none-match'.length === 13, 'if-modified-since'.length === 17
|
|
669
|
+
// 'if-none-match'.length === 13, 'if-modified-since'.length === 17; the
|
|
670
|
+
// entries carry lowercase names by contract, so this compares them as they are
|
|
669
671
|
if (key.length === 13 || key.length === 17) {
|
|
670
|
-
|
|
671
|
-
if (lower === "if-none-match" || lower === "if-modified-since") {
|
|
672
|
+
if (key === "if-none-match" || key === "if-modified-since") {
|
|
672
673
|
hasConditional = true;
|
|
673
674
|
break;
|
|
674
675
|
}
|
|
@@ -852,7 +853,8 @@ module.exports = class Request extends Readable {
|
|
|
852
853
|
const entries = this.#rawHeadersEntries;
|
|
853
854
|
for (let index = 0, len = entries.length; index < len; index += 2) {
|
|
854
855
|
const value = entries[index + 1];
|
|
855
|
-
|
|
856
|
+
// lowercase by the entries' contract, see the field declaration
|
|
857
|
+
const key = entries[index];
|
|
856
858
|
if (Object.hasOwn(headers, key)) {
|
|
857
859
|
if (discardedDuplicates.has(key)) {
|
|
858
860
|
continue;
|
package/src/response.js
CHANGED
|
@@ -175,6 +175,9 @@ module.exports = class Response extends Writable {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
this.body = undefined;
|
|
178
|
+
// false while the uWS route handler is still in its synchronous window, where uWS holds
|
|
179
|
+
// the socket corked itself; the two uWS entry points flip it once that window closes
|
|
180
|
+
this._corkNeeded = false;
|
|
178
181
|
// shared methods, not arrows: two closures and a once() wrapper here were four
|
|
179
182
|
// allocations per request. EventEmitter calls listeners with this = the emitter.
|
|
180
183
|
this.on("error", this._onAbortError);
|
|
@@ -272,8 +275,11 @@ module.exports = class Response extends Writable {
|
|
|
272
275
|
if (!this.headersSent) {
|
|
273
276
|
this.writeHead(this.statusCode);
|
|
274
277
|
// "unknown" and not the bare number: node writes that reason phrase for a code it
|
|
275
|
-
// has no message for, so the raw status lines match
|
|
276
|
-
|
|
278
|
+
// has no message for, so the raw status lines match. The default 200 with no
|
|
279
|
+
// phrase is uWS's own head, byte for byte, so it is not written at all
|
|
280
|
+
if (this.statusCode !== 200 || this.statusText !== undefined) {
|
|
281
|
+
this._res.writeStatus(statusLine(this.statusCode, this.statusText));
|
|
282
|
+
}
|
|
277
283
|
this.writeHeaders(typeof chunk === "string");
|
|
278
284
|
}
|
|
279
285
|
|
|
@@ -475,47 +481,67 @@ module.exports = class Response extends Writable {
|
|
|
475
481
|
return this;
|
|
476
482
|
}
|
|
477
483
|
this.writeHead(this.statusCode);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
484
|
+
// uWS holds the socket corked for the synchronous window of its route handler, and
|
|
485
|
+
// cork inside cork is a passthrough: the wrapper and its closure are only paid once the
|
|
486
|
+
// answer has outlived that window, which is what _corkNeeded records
|
|
487
|
+
if (this._corkNeeded) {
|
|
488
|
+
this._res.cork(() => this._finish(data, cb));
|
|
489
|
+
} else {
|
|
490
|
+
this._finish(data, cb);
|
|
491
|
+
}
|
|
492
|
+
return this;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* The corked tail of end(): status, headers, body and the finish events. Split out so a
|
|
497
|
+
* synchronous answer calls it straight, already inside uWS's own cork.
|
|
498
|
+
*
|
|
499
|
+
* @param {any} data
|
|
500
|
+
* @param {any} cb
|
|
501
|
+
*/
|
|
502
|
+
_finish(data, cb) {
|
|
503
|
+
if (!this.headersSent) {
|
|
504
|
+
// freshness is not decided here. node's end() knows nothing about conditional
|
|
505
|
+
// requests, and Express answers 304 from send() and from sendFile(), each of
|
|
506
|
+
// which strips the entity headers first. Deciding it here meant res.end("body")
|
|
507
|
+
// answered 304 and dropped the body that the caller had just written.
|
|
508
|
+
// "unknown" for a code without a message, as node's status line has it.
|
|
509
|
+
// The default 200 with no phrase is not written at all: uWS emits the identical
|
|
510
|
+
// "HTTP/1.1 200 OK" head on its own, and the crossing costs more than it says
|
|
511
|
+
if (this.statusCode !== 200 || this.statusText !== undefined) {
|
|
485
512
|
this._res.writeStatus(statusLine(this.statusCode, this.statusText));
|
|
486
|
-
this.writeHeaders(true);
|
|
487
513
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
514
|
+
this.writeHeaders(true);
|
|
515
|
+
}
|
|
516
|
+
const contentLength = this.headers["content-length"];
|
|
517
|
+
if (STATUSES_WITHOUT_BODY.has(this.statusCode) || this.statusCode < 200) {
|
|
518
|
+
// no body and no length describing one, whatever the caller passed. node decides
|
|
519
|
+
// this the same way, from the status alone, so res.status(304).end("x") sends the
|
|
520
|
+
// status and nothing else on either.
|
|
521
|
+
this._res.endWithoutBody();
|
|
522
|
+
} else if (!data && contentLength) {
|
|
523
|
+
this._res.endWithoutBody(contentLength.toString());
|
|
524
|
+
} else {
|
|
525
|
+
if (data instanceof Buffer) {
|
|
526
|
+
data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
527
|
+
}
|
|
528
|
+
if (this.req.method === "HEAD") {
|
|
529
|
+
const length = Buffer.byteLength(data ?? "");
|
|
530
|
+
this._res.endWithoutBody(length.toString());
|
|
496
531
|
} else {
|
|
497
|
-
|
|
498
|
-
data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
499
|
-
}
|
|
500
|
-
if (this.req.method === "HEAD") {
|
|
501
|
-
const length = Buffer.byteLength(data ?? "");
|
|
502
|
-
this._res.endWithoutBody(length.toString());
|
|
503
|
-
} else {
|
|
504
|
-
this._res.end(data);
|
|
505
|
-
}
|
|
532
|
+
this._res.end(data);
|
|
506
533
|
}
|
|
534
|
+
}
|
|
507
535
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
});
|
|
518
|
-
return this;
|
|
536
|
+
this.finished = true;
|
|
537
|
+
this.#socket?.emit("close");
|
|
538
|
+
this.emit("finish");
|
|
539
|
+
this.emit("close");
|
|
540
|
+
cb &&
|
|
541
|
+
queueMicrotask(() => {
|
|
542
|
+
this.#ended = true;
|
|
543
|
+
cb();
|
|
544
|
+
});
|
|
519
545
|
}
|
|
520
546
|
|
|
521
547
|
/**
|
package/src/router.js
CHANGED
|
@@ -1408,6 +1408,10 @@ module.exports = class Router extends EventEmitter {
|
|
|
1408
1408
|
} catch (err) {
|
|
1409
1409
|
// what a throw inside a promise executor did: reject, once
|
|
1410
1410
|
nativeFail.call(walk, err);
|
|
1411
|
+
} finally {
|
|
1412
|
+
// whatever runs after this line is outside the cork uWS held for this
|
|
1413
|
+
// callback, so later writes have to open their own
|
|
1414
|
+
response._corkNeeded = true;
|
|
1411
1415
|
}
|
|
1412
1416
|
};
|
|
1413
1417
|
};
|