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/package.json +2 -1
- package/src/adopt.js +20 -26
- package/src/application.js +46 -45
- package/src/cli.js +28 -34
- package/src/cluster.js +16 -25
- package/src/compression.js +39 -51
- package/src/declarative.js +586 -538
- package/src/hot-settings.js +80 -0
- package/src/index.js +11 -17
- package/src/lazy-readable.js +129 -0
- package/src/lazy-writable.js +97 -0
- package/src/middlewares.js +61 -77
- package/src/nest.js +19 -34
- package/src/node-shim.js +11 -13
- package/src/optimizer.js +598 -0
- package/src/parse-query.js +3 -3
- package/src/request-utils.js +306 -0
- package/src/request.js +101 -513
- package/src/response-utils.js +88 -0
- package/src/response.js +100 -443
- package/src/route.js +4 -5
- package/src/router-utils.js +950 -0
- package/src/router.js +126 -2148
- package/src/server-shape.js +26 -41
- package/src/server-timing.js +16 -29
- package/src/socket.js +208 -0
- package/src/testing.js +39 -42
- package/src/usage.js +16 -21
- package/src/utils.js +49 -59
- package/src/verify.js +18 -28
- package/src/view.js +5 -7
- package/src/walk.js +580 -0
- package/src/websocket.js +19 -20
- package/src/work.js +21 -27
package/src/server-shape.js
CHANGED
|
@@ -16,40 +16,31 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
// What makes an application answer the questions a library asks about an http.Server.
|
|
18
18
|
//
|
|
19
|
-
// `app.listen()` returns the app
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// *recognise* one: `server instanceof http.Server`, then close(), address(), getConnections() and
|
|
23
|
-
// the events around them. Everything they need can be answered honestly here.
|
|
19
|
+
// `app.listen()` returns the app and there is no node server under it, the socket belongs to uWS.
|
|
20
|
+
// Graceful shutdown libraries, connection trackers and health check wrappers recognise a server
|
|
21
|
+
// with `server instanceof http.Server`, then call close(), address(), getConnections().
|
|
24
22
|
//
|
|
25
|
-
// Two halves
|
|
23
|
+
// Two halves:
|
|
26
24
|
//
|
|
27
|
-
// - the members. close(), address(), listening and the events already
|
|
28
|
-
// because Express hands back an http.Server
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// chain of its own that cannot be spliced into it without re-parenting node's classes for the
|
|
33
|
-
// whole process. So instanceof is taught about applications instead, through the hook the
|
|
34
|
-
// language provides for exactly this: Symbol.hasInstance. The patch is additive. Everything
|
|
35
|
-
// that was an http.Server before still is, and the only new answer is for an application.
|
|
25
|
+
// - the members. close(), address(), listening and the events are already on the application,
|
|
26
|
+
// because Express hands back an http.Server. The rest of net.Server is added below.
|
|
27
|
+
// - the recognition. An application cannot inherit from http.Server, its prototype chain runs
|
|
28
|
+
// through Router and this project's own EventEmitter. So instanceof is taught instead, with
|
|
29
|
+
// Symbol.hasInstance. The patch is additive, nothing loses the answer it had.
|
|
36
30
|
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// counts connections through them counts zero, and socket.io still wants app.uwsApp. The shape is
|
|
40
|
-
// honest about what is behind it, which is why getConnections answers with the requests in flight
|
|
41
|
-
// rather than with a number nobody could stand behind.
|
|
31
|
+
// Nothing emits 'request', 'connection' or 'upgrade': those carry node sockets and there are none.
|
|
32
|
+
// A library counting connections through them counts zero, and socket.io wants app.uwsApp.
|
|
42
33
|
|
|
43
34
|
const http = require("http");
|
|
44
35
|
const net = require("net");
|
|
45
36
|
|
|
46
|
-
// what marks an application, read by the instanceof hook below. A symbol
|
|
47
|
-
//
|
|
37
|
+
// what marks an application, read by the instanceof hook below. A symbol, so no plain field name
|
|
38
|
+
// can be mistaken for it
|
|
48
39
|
const kIsApplication = Symbol.for("fulmine.application");
|
|
49
40
|
|
|
50
41
|
/**
|
|
51
42
|
* Teaches `instanceof` that an application is a server, once per class. The original answer is
|
|
52
|
-
* asked first and
|
|
43
|
+
* asked first and never overruled.
|
|
53
44
|
*
|
|
54
45
|
* @param {Function} klass http.Server or net.Server
|
|
55
46
|
*/
|
|
@@ -65,8 +56,8 @@ function acceptApplications(klass) {
|
|
|
65
56
|
if (previous.call(this, value)) {
|
|
66
57
|
return true;
|
|
67
58
|
}
|
|
68
|
-
// an application is a function
|
|
69
|
-
// primitives and
|
|
59
|
+
// an application is a function and a property read works on one. The guard is for the
|
|
60
|
+
// primitives and nulls that reach any instanceof
|
|
70
61
|
return value != null && /** @type {any} */ (value)[kIsApplication] === true;
|
|
71
62
|
},
|
|
72
63
|
configurable: true,
|
|
@@ -79,8 +70,8 @@ acceptApplications(http.Server);
|
|
|
79
70
|
acceptApplications(net.Server);
|
|
80
71
|
|
|
81
72
|
/**
|
|
82
|
-
* The net.Server members
|
|
83
|
-
*
|
|
73
|
+
* The net.Server members Express's API does not give, on the application prototype. Each one
|
|
74
|
+
* answers for uWS, not for a node socket.
|
|
84
75
|
*
|
|
85
76
|
* @param {any} prototype Application.prototype
|
|
86
77
|
*/
|
|
@@ -88,11 +79,8 @@ function addServerMembers(prototype) {
|
|
|
88
79
|
Object.defineProperty(prototype, kIsApplication, { value: true, configurable: true });
|
|
89
80
|
|
|
90
81
|
/**
|
|
91
|
-
* How many requests this application is serving right now.
|
|
92
|
-
*
|
|
93
|
-
* node counts sockets; there are none to count here, and the number a graceful shutdown is
|
|
94
|
-
* waiting for is this one anyway: it reaches zero when the last answer has gone out. An idle
|
|
95
|
-
* keep-alive connection is not counted, and closing does not wait for one either.
|
|
82
|
+
* How many requests this application is serving right now. node counts sockets, there are none
|
|
83
|
+
* here, and this is the number a graceful shutdown waits for. An idle keep-alive is not counted.
|
|
96
84
|
*
|
|
97
85
|
* @param {(err: Error|null, count: number) => void} callback
|
|
98
86
|
*/
|
|
@@ -106,9 +94,8 @@ function addServerMembers(prototype) {
|
|
|
106
94
|
};
|
|
107
95
|
|
|
108
96
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* written against node's API keeps working.
|
|
97
|
+
* A handle this does not own: uWS's loop keeps the process alive and a caller cannot unref it.
|
|
98
|
+
* Both are no-ops returning the server, so a chain written against node's API keeps working.
|
|
112
99
|
*
|
|
113
100
|
* @returns {any}
|
|
114
101
|
*/
|
|
@@ -122,8 +109,8 @@ function addServerMembers(prototype) {
|
|
|
122
109
|
};
|
|
123
110
|
|
|
124
111
|
/**
|
|
125
|
-
* Registers the callback
|
|
126
|
-
*
|
|
112
|
+
* Registers the callback like node's does and remembers the value, which is all a caller can
|
|
113
|
+
* observe. The timeout belongs to uWS and is set through uwsOptions.idleTimeout.
|
|
127
114
|
*
|
|
128
115
|
* @this {any}
|
|
129
116
|
* @param {number} [msecs]
|
|
@@ -138,10 +125,8 @@ function addServerMembers(prototype) {
|
|
|
138
125
|
return this;
|
|
139
126
|
};
|
|
140
127
|
|
|
141
|
-
// The numbers node's http.Server carries
|
|
142
|
-
//
|
|
143
|
-
// out what it is talking to: `server.keepAliveTimeout` undefined has been read as "not a
|
|
144
|
-
// server" before now.
|
|
128
|
+
// The numbers node's http.Server carries. Inert here, but declared rather than left undefined:
|
|
129
|
+
// a library reads `server.keepAliveTimeout` to work out what it is talking to.
|
|
145
130
|
for (const [name, value] of /** @type {[string, any][]} */ ([
|
|
146
131
|
["timeout", 0],
|
|
147
132
|
["keepAliveTimeout", 5000],
|
package/src/server-timing.js
CHANGED
|
@@ -16,37 +16,27 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
// express.serverTiming(): Server-Timing, with the two things only this framework can put in it.
|
|
18
18
|
//
|
|
19
|
-
//
|
|
20
|
-
// is how the request was routed, because in every other framework there is only one way:
|
|
19
|
+
// What no other stopwatch middleware can add is how the request was routed:
|
|
21
20
|
//
|
|
22
21
|
// Server-Timing: route;desc="native", hdr;desc="not copied", total;dur=0.42
|
|
23
22
|
//
|
|
24
|
-
// `route;desc="native"` means
|
|
23
|
+
// `route;desc="native"` means uWS matched the path in C++ and handed over a chain worked out at
|
|
25
24
|
// startup. `route;desc="router"` means this request was matched here, in javascript, layer by
|
|
26
|
-
// layer.
|
|
27
|
-
//
|
|
25
|
+
// layer. A handler compiled into a response never enters javascript, so there is nothing to time
|
|
26
|
+
// on it: `npx fulmine profile` counts those.
|
|
28
27
|
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// profile` is where those are counted.
|
|
32
|
-
//
|
|
33
|
-
// The other field only this framework can write is `work`, which names what the request was made to
|
|
34
|
-
// build: the folded headers object, the parsed query, the body, the Readable, the Writable, the
|
|
35
|
-
// socket stand-in. A fast request builds none of them and the field is absent, so it appears
|
|
36
|
-
// exactly when something is worth looking at. See src/work.js.
|
|
28
|
+
// `work` names what the request was made to build: folded headers, parsed query, body, Readable,
|
|
29
|
+
// Writable, socket stand-in. A fast request builds none and the field is absent. See src/work.js.
|
|
37
30
|
//
|
|
38
31
|
// The duration ends where the header does. Server-Timing goes out with the head, so `total` covers
|
|
39
|
-
// everything up to the moment the answer starts leaving, and
|
|
40
|
-
// the same boundary: a stream built by the write that carries the head is built after this is
|
|
41
|
-
// written. Every stopwatch middleware has that boundary; this one says so.
|
|
32
|
+
// everything up to the moment the answer starts leaving, and `work` has the same boundary.
|
|
42
33
|
|
|
43
34
|
"use strict";
|
|
44
35
|
|
|
45
36
|
const { work, names } = require("./work.js");
|
|
46
37
|
|
|
47
38
|
/**
|
|
48
|
-
* A duration in milliseconds, as Server-Timing writes them: two decimals
|
|
49
|
-
* a millisecond and finer than anything above it is worth.
|
|
39
|
+
* A duration in milliseconds, as Server-Timing writes them: two decimals.
|
|
50
40
|
*
|
|
51
41
|
* @param {bigint} nanoseconds
|
|
52
42
|
* @returns {string}
|
|
@@ -88,9 +78,8 @@ function serverTiming(options) {
|
|
|
88
78
|
const marks = [];
|
|
89
79
|
|
|
90
80
|
/**
|
|
91
|
-
* Adds a mark of the caller's own
|
|
92
|
-
*
|
|
93
|
-
* description is a legal entry and is how a cache hit is usually reported.
|
|
81
|
+
* Adds a mark of the caller's own: the query, the upstream call, the render. The duration
|
|
82
|
+
* is optional, a mark with only a description is a legal entry.
|
|
94
83
|
*
|
|
95
84
|
* @param {string} name a token: letters, digits, dash and underscore
|
|
96
85
|
* @param {number} [duration] milliseconds
|
|
@@ -110,8 +99,8 @@ function serverTiming(options) {
|
|
|
110
99
|
};
|
|
111
100
|
|
|
112
101
|
/**
|
|
113
|
-
* Times a piece of work under a name
|
|
114
|
-
*
|
|
102
|
+
* Times a piece of work under a name. The value comes back, and a promise is timed to
|
|
103
|
+
* where it settles.
|
|
115
104
|
*
|
|
116
105
|
* @param {string} name
|
|
117
106
|
* @param {() => any} work
|
|
@@ -155,8 +144,7 @@ function serverTiming(options) {
|
|
|
155
144
|
written = true;
|
|
156
145
|
const entries = [];
|
|
157
146
|
if (routing) {
|
|
158
|
-
//
|
|
159
|
-
// verdict npx fulmine profile prints for it
|
|
147
|
+
// the same verdict npx fulmine profile prints for this route
|
|
160
148
|
const native = req.route?._native;
|
|
161
149
|
entries.push(`route;desc=${describe(native ? "native" : "router")}`);
|
|
162
150
|
if (native) {
|
|
@@ -167,10 +155,9 @@ function serverTiming(options) {
|
|
|
167
155
|
}
|
|
168
156
|
}
|
|
169
157
|
if (wantsWork) {
|
|
170
|
-
// what this
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// chain and not the body written after it, the same boundary as the total.
|
|
158
|
+
// what this request made the framework build, which the route verdict cannot say:
|
|
159
|
+
// a native route still folds the headers if a middleware reads them. Read at the
|
|
160
|
+
// head, so it covers the chain and not the body, the same boundary as the total.
|
|
174
161
|
const listed = names(work(req, res));
|
|
175
162
|
if (listed.length !== 0) {
|
|
176
163
|
entries.push(`work;desc=${describe(listed.join(", "))}`);
|
package/src/socket.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024 dimden.dev
|
|
3
|
+
Copyright 2026 Nigro Simone
|
|
4
|
+
|
|
5
|
+
This file is derived from Ultimate Express and has been modified.
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** @typedef {import("./response.js")} Response */
|
|
21
|
+
|
|
22
|
+
// events is faster at init, tseep is faster at sending events
|
|
23
|
+
// since we create a ton of objects and dont send a ton of events, its better to use events here
|
|
24
|
+
const { EventEmitter } = require("events");
|
|
25
|
+
const { kShapeMode } = require("./response-utils.js");
|
|
26
|
+
|
|
27
|
+
class Socket extends EventEmitter {
|
|
28
|
+
/**
|
|
29
|
+
* The Socket's error listener, shared across sockets: an error closes the stand-in, which is
|
|
30
|
+
* the close connection trackers wait for. EventEmitter calls it with this = the emitter.
|
|
31
|
+
*
|
|
32
|
+
* @this {any}
|
|
33
|
+
* @param {any} err whatever the response reported, which need not be an Error
|
|
34
|
+
*/
|
|
35
|
+
static _onError(err) {
|
|
36
|
+
this.emit("close");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Enough of a node socket for the middleware that reaches for one. uWS has no socket object to
|
|
41
|
+
* hand over, so this stands in and forwards what it can to the response.
|
|
42
|
+
*
|
|
43
|
+
* @param {Response} response
|
|
44
|
+
*/
|
|
45
|
+
constructor(response) {
|
|
46
|
+
super();
|
|
47
|
+
this.response = response;
|
|
48
|
+
this[kShapeMode] = true;
|
|
49
|
+
// middleware assigns to this one, which is why it is a field rather than a getter: express
|
|
50
|
+
// reads socket.encrypted for req.protocol and a proxy shim writes it
|
|
51
|
+
this.encrypted = response.req.app.ssl;
|
|
52
|
+
this.localPort = response.req.app.port;
|
|
53
|
+
// on-finished reads socket.readable before anything else, and a socket without one reads
|
|
54
|
+
// as a request that is already over
|
|
55
|
+
this.readable = true;
|
|
56
|
+
|
|
57
|
+
// shared, not an arrow: one per process instead of one per materialized socket
|
|
58
|
+
this.on("error", Socket._onError);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Whether anything more can be written, which stops being true once the response is done. */
|
|
62
|
+
get writable() {
|
|
63
|
+
return !this.response.finished;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The peer, as node reports it. Reading it out of uWS is slow, so the request caches it. */
|
|
67
|
+
get remoteAddress() {
|
|
68
|
+
return this.response.req.parsedIp;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A native µWS call almost no caller makes, so it stays behind its getter. */
|
|
72
|
+
get remotePort() {
|
|
73
|
+
return this.response.req._res.getRemotePort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* node's socket carries these three, usually called to take the timeout off. uWS has no per
|
|
78
|
+
* socket timeout reachable from javascript, so they do nothing and return the socket. n8n's
|
|
79
|
+
* chat trigger calls setTimeout on every webhook, and without it the workflow answered 500.
|
|
80
|
+
* @returns {this}
|
|
81
|
+
*/
|
|
82
|
+
setTimeout() {
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @returns {this} */
|
|
87
|
+
setKeepAlive() {
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @returns {this} */
|
|
92
|
+
setNoDelay() {
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Finishes the response through the socket, which is how the middleware that only knows
|
|
98
|
+
* about sockets ends one.
|
|
99
|
+
* @param {any} [body] whatever node's socket.end() would take
|
|
100
|
+
*/
|
|
101
|
+
end(body) {
|
|
102
|
+
this.response.end(body);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* What a server side socket answers about itself. uWS owns the connection, so these follow the
|
|
107
|
+
* response.
|
|
108
|
+
*/
|
|
109
|
+
get destroyed() {
|
|
110
|
+
return this.response.finished === true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @returns {string} "open" until the response is over, as a served socket reads. */
|
|
114
|
+
get readyState() {
|
|
115
|
+
return this.response.finished === true ? "closed" : "open";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @returns {boolean} never: this end was accepted, not dialled. */
|
|
119
|
+
get connecting() {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @returns {boolean} never, for the same reason. */
|
|
124
|
+
get pending() {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The end of the connection node reports here. There is no address to read back from uWS, so
|
|
130
|
+
* this is the port the application bound and the family the peer arrived on.
|
|
131
|
+
* @returns {{address: string|undefined, family: string, port: number|undefined}}
|
|
132
|
+
*/
|
|
133
|
+
address() {
|
|
134
|
+
const remote = this.response.req.parsedIp;
|
|
135
|
+
return {
|
|
136
|
+
address: this.response.req.app._listenHost,
|
|
137
|
+
family: remote?.includes(":") ? "IPv6" : "IPv4",
|
|
138
|
+
port: this.localPort
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Drops the connection. node takes an error and re-emits it, this closes and says so through
|
|
144
|
+
* 'close', since there is no socket underneath to carry an error of its own.
|
|
145
|
+
* @returns {this}
|
|
146
|
+
*/
|
|
147
|
+
destroy() {
|
|
148
|
+
this.close();
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @returns {this} */
|
|
153
|
+
destroySoon() {
|
|
154
|
+
this.close();
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Holds and resumes the body arriving on this connection. The other half of node's pause()
|
|
160
|
+
* means nothing here, the response is written when the application writes it.
|
|
161
|
+
* @returns {this}
|
|
162
|
+
*/
|
|
163
|
+
pause() {
|
|
164
|
+
this.response.req.pause();
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** @returns {this} */
|
|
169
|
+
resume() {
|
|
170
|
+
this.response.req.resume();
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* node writes these bytes straight onto the connection. There is no way past uWS's framing
|
|
176
|
+
* here, so they go through the response instead.
|
|
177
|
+
*
|
|
178
|
+
* @param {any} chunk
|
|
179
|
+
* @param {any} [encoding]
|
|
180
|
+
* @param {any} [callback] node's write signature, which takes all three loosely
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
write(chunk, encoding, callback) {
|
|
184
|
+
return this.response.write(chunk, encoding, callback);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The event loop is µWS's, so there is nothing to hold open or let go. @returns {this} */
|
|
188
|
+
ref() {
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** @returns {this} */
|
|
193
|
+
unref() {
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Closes the connection outright, without finishing a response first. */
|
|
198
|
+
close() {
|
|
199
|
+
if (this.response.finished) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.response.finished = true;
|
|
203
|
+
this.emit("close");
|
|
204
|
+
this.response._res.close();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = Socket;
|
package/src/testing.js
CHANGED
|
@@ -16,25 +16,27 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
// express.testing: what listen() decided about each route, as something a test can assert on.
|
|
18
18
|
//
|
|
19
|
-
// A route is answered by
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// correct, which is why nothing complains. What changes is the throughput, and by the time anyone
|
|
23
|
-
// notices, the commit that did it is three weeks back.
|
|
19
|
+
// A route is answered by uWS itself only while it stays eligible: a `const` in the wrong place, a
|
|
20
|
+
// middleware that reads a header, a new route above an old one, and it falls back to the ordinary
|
|
21
|
+
// router. The answer stays correct, so nothing complains, only the throughput changes.
|
|
24
22
|
//
|
|
25
|
-
// `npx fulmine profile` prints the same verdicts for a human
|
|
26
|
-
//
|
|
27
|
-
// instead of being found in production.
|
|
23
|
+
// `npx fulmine profile` prints the same verdicts for a human. This is the half a test can hold on
|
|
24
|
+
// to, so a pull request that loses the fast path fails in CI.
|
|
28
25
|
|
|
29
26
|
"use strict";
|
|
30
27
|
|
|
31
28
|
const { work, names: workNames } = require("./work.js");
|
|
32
29
|
|
|
30
|
+
/** @typedef {import("./request.js")} Request */
|
|
31
|
+
/** @typedef {import("./response.js")} Response */
|
|
32
|
+
/** @typedef {import("./router.js")} Router */
|
|
33
|
+
/** @typedef {import("./application.js").Application} Application */
|
|
34
|
+
|
|
33
35
|
/**
|
|
34
36
|
* Every route of an application and of the routers mounted under it, each with the path it answers
|
|
35
37
|
* from the outside.
|
|
36
38
|
*
|
|
37
|
-
* @param {
|
|
39
|
+
* @param {Router} router
|
|
38
40
|
* @param {string} prefix
|
|
39
41
|
* @param {any[]} [into]
|
|
40
42
|
* @returns {{route: any, full: string}[]}
|
|
@@ -55,7 +57,7 @@ function collectRoutes(router, prefix, into = []) {
|
|
|
55
57
|
* Compiles the routes, which is what listen() does before it binds, without binding anything. Once
|
|
56
58
|
* per application: a second compilation would register everything with µWS twice.
|
|
57
59
|
*
|
|
58
|
-
* @param {
|
|
60
|
+
* @param {Application} app
|
|
59
61
|
*/
|
|
60
62
|
function compileOnce(app) {
|
|
61
63
|
if (app.listenCalled || app._testingCompiled) {
|
|
@@ -68,11 +70,10 @@ function compileOnce(app) {
|
|
|
68
70
|
/**
|
|
69
71
|
* What compiling the routes decided, one entry per route, in the order they were registered.
|
|
70
72
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* fall back, which ones may read headers, that the one route carrying the traffic is declarative.
|
|
73
|
+
* The primitive the two assertions below are written on. Exported so an application with rules of
|
|
74
|
+
* its own can assert them directly.
|
|
74
75
|
*
|
|
75
|
-
* @param {
|
|
76
|
+
* @param {Application} app an application, listening or not
|
|
76
77
|
* @returns {{method: string, path: string, native: boolean, declarative: boolean, skipHeaders: boolean,
|
|
77
78
|
* skipQuery: boolean, reason: string|undefined}[]}
|
|
78
79
|
*/
|
|
@@ -94,8 +95,8 @@ function routeReport(app) {
|
|
|
94
95
|
/**
|
|
95
96
|
* Whether one of the patterns given names this route.
|
|
96
97
|
*
|
|
97
|
-
* A pattern is a path as
|
|
98
|
-
*
|
|
98
|
+
* A pattern is a path as registered, not a URL: "/api/items/:id", not "/api/items/7". It may carry
|
|
99
|
+
* the method, "GET /health", and may end in "*" for everything under a prefix.
|
|
99
100
|
*
|
|
100
101
|
* @param {{method: string, path: string}} entry
|
|
101
102
|
* @param {string} pattern
|
|
@@ -118,10 +119,10 @@ function names(entry, pattern) {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
/**
|
|
121
|
-
* The routes the patterns name
|
|
122
|
-
*
|
|
122
|
+
* The routes the patterns name. A pattern that names none throws, so a misspelled route fails
|
|
123
|
+
* instead of passing on an empty list.
|
|
123
124
|
*
|
|
124
|
-
* @param {
|
|
125
|
+
* @param {Application} app
|
|
125
126
|
* @param {string|string[]} patterns
|
|
126
127
|
* @param {string} caller the name in the message
|
|
127
128
|
* @returns {ReturnType<typeof routeReport>}
|
|
@@ -152,12 +153,10 @@ function select(app, patterns, caller) {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
/**
|
|
155
|
-
* Throws unless every route named is answered by
|
|
156
|
-
*
|
|
157
|
-
* The message is the point: it names each route that fell back and why, in the same words
|
|
158
|
-
* `npx fulmine profile` uses, so the failure says what to change.
|
|
156
|
+
* Throws unless every route named is answered by uWS itself. The message names each route that
|
|
157
|
+
* fell back and why, in the same words `npx fulmine profile` uses.
|
|
159
158
|
*
|
|
160
|
-
* @param {
|
|
159
|
+
* @param {Application} app
|
|
161
160
|
* @param {string|string[]} patterns paths as they were registered, "GET /path" to pin the method,
|
|
162
161
|
* a trailing "*" for everything under a prefix
|
|
163
162
|
*/
|
|
@@ -174,12 +173,12 @@ function expectNative(app, patterns) {
|
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
/**
|
|
177
|
-
* Why a route
|
|
176
|
+
* Why a route uWS already matches is still not compiled into a response.
|
|
178
177
|
*
|
|
179
178
|
* The handler is the last answer, not the first: three refusals come before it, and blaming the
|
|
180
|
-
* handler
|
|
179
|
+
* handler sends the reader to rewrite something that was already simple enough.
|
|
181
180
|
*
|
|
182
|
-
* @param {
|
|
181
|
+
* @param {Application} app
|
|
183
182
|
* @param {{path: string}} entry
|
|
184
183
|
* @returns {string}
|
|
185
184
|
*/
|
|
@@ -200,10 +199,10 @@ function whyNotCompiled(app, entry) {
|
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
/**
|
|
203
|
-
* Throws unless every route named is answered from a response written at startup
|
|
204
|
-
*
|
|
202
|
+
* Throws unless every route named is answered from a response written at startup. One step past
|
|
203
|
+
* native: uWS answers it without entering javascript.
|
|
205
204
|
*
|
|
206
|
-
* @param {
|
|
205
|
+
* @param {Application} app
|
|
207
206
|
* @param {string|string[]} patterns as in expectNative
|
|
208
207
|
*/
|
|
209
208
|
function expectDeclarative(app, patterns) {
|
|
@@ -228,31 +227,29 @@ function expectDeclarative(app, patterns) {
|
|
|
228
227
|
* What this one request made the framework do, asked from inside a handler or from a `finish`
|
|
229
228
|
* listener. See src/work.js for what each field means and why asking is free.
|
|
230
229
|
*
|
|
231
|
-
* @param {
|
|
232
|
-
* @param {
|
|
230
|
+
* @param {Request} req
|
|
231
|
+
* @param {Response} res
|
|
233
232
|
* @returns {import("./work.js").Work}
|
|
234
233
|
*/
|
|
235
234
|
function workReport(req, res) {
|
|
236
235
|
return work(req, res);
|
|
237
236
|
}
|
|
238
237
|
|
|
239
|
-
// The work a fast request does none of
|
|
240
|
-
//
|
|
238
|
+
// The work a fast request does none of. The route verdict is not here, expectNative and
|
|
239
|
+
// expectDeclarative assert on that.
|
|
241
240
|
const LAZY = ["headers", "query", "body", "requestStream", "responseStream", "socket"];
|
|
242
241
|
|
|
243
242
|
/**
|
|
244
243
|
* Throws if this request built anything it did not have to.
|
|
245
244
|
*
|
|
246
|
-
* The route verdict
|
|
247
|
-
*
|
|
248
|
-
* because a middleware read `req.headers.host` or piped instead of sending: the answer stays
|
|
249
|
-
* correct, the route report stays green, and the throughput does not.
|
|
245
|
+
* The route verdict holds for every request, this holds for one. A native route still slows down
|
|
246
|
+
* request by request if a middleware reads `req.headers.host` or pipes instead of sending.
|
|
250
247
|
*
|
|
251
|
-
* `allow` names what is fine here
|
|
252
|
-
*
|
|
248
|
+
* `allow` names what is fine here: a route that parses a body is asserted as one that parses a
|
|
249
|
+
* body and nothing else.
|
|
253
250
|
*
|
|
254
|
-
* @param {
|
|
255
|
-
* @param {
|
|
251
|
+
* @param {Request} req
|
|
252
|
+
* @param {Response} res
|
|
256
253
|
* @param {object} [options]
|
|
257
254
|
* @param {string[]} [options.allow] fields of the report this route is expected to do anyway
|
|
258
255
|
*/
|