fulmine.js 5.19.2 → 5.19.4
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 +63 -73
- package/src/cli.js +48 -48
- package/src/cluster.js +18 -27
- package/src/compression.js +141 -112
- package/src/declarative.js +611 -540
- package/src/hot-settings.js +80 -0
- package/src/index.js +11 -17
- package/src/lazy-readable.js +131 -0
- package/src/lazy-writable.js +97 -0
- package/src/middlewares.js +153 -130
- package/src/nest.js +22 -36
- package/src/node-shim.js +19 -16
- package/src/optimizer.js +600 -0
- package/src/options.d.ts +9 -4
- package/src/parse-query.js +3 -3
- package/src/request-utils.js +307 -0
- package/src/request.js +147 -548
- package/src/response-utils.js +88 -0
- package/src/response.js +228 -535
- package/src/route.js +7 -8
- package/src/router-utils.js +998 -0
- package/src/router.js +166 -2170
- package/src/server-shape.js +40 -51
- package/src/server-timing.js +32 -33
- package/src/socket.js +208 -0
- package/src/testing.js +43 -45
- package/src/usage.js +25 -25
- package/src/utils.js +165 -78
- package/src/verify.js +22 -31
- package/src/view.js +6 -8
- package/src/walk.js +581 -0
- package/src/websocket.js +34 -26
- package/src/work.js +22 -28
|
@@ -0,0 +1,998 @@
|
|
|
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
|
+
const { getPatternMeta, pathsCanOverlap, regexpGroupKeys, EMPTY_REGEX } = require("./utils.js");
|
|
21
|
+
const Response = require("./response.js");
|
|
22
|
+
const Request = require("./request.js");
|
|
23
|
+
const { METHODS } = require("http");
|
|
24
|
+
|
|
25
|
+
/** @typedef {import("./walk.js")} Walk */
|
|
26
|
+
/** @typedef {import("./router.js")} Router */
|
|
27
|
+
/**
|
|
28
|
+
* One entry of a router's table, as createRoute in router.js builds it. Left loose on purpose: the
|
|
29
|
+
* optimizer hangs half a dozen more fields on it after registration, and writing them all out here
|
|
30
|
+
* would be a second copy of createRoute that nothing keeps in step.
|
|
31
|
+
* @typedef {any} RouteEntry
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* What nativePreset builds for a literal registration: the constants every request to that route
|
|
35
|
+
* shares, read off the preset instead of the URL.
|
|
36
|
+
* @typedef {object} NativePreset
|
|
37
|
+
* @property {string} path
|
|
38
|
+
* @property {string} method
|
|
39
|
+
* @property {boolean} endsWithSlash
|
|
40
|
+
* @property {string} opPath
|
|
41
|
+
* @property {boolean} isOptions
|
|
42
|
+
* @property {boolean} isHead
|
|
43
|
+
* @property {boolean} skipHeaders written at registration, and taken back by a middleware added
|
|
44
|
+
* after listen, see _skipPresets
|
|
45
|
+
* @property {boolean} skipQuery the same, for the query
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Where a granted header skip lives: the preset itself for a literal registration, a holder of its
|
|
49
|
+
* own for a parameterised one, see makeHandler in optimizer.js.
|
|
50
|
+
* @typedef {object} SkipHolder
|
|
51
|
+
* @property {boolean} skipHeaders
|
|
52
|
+
* @property {boolean} skipQuery
|
|
53
|
+
* @property {string|null} [method]
|
|
54
|
+
* @property {boolean} [isOptions]
|
|
55
|
+
* @property {boolean} [isHead]
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* An earlier registration a mount is guarded by, see guardsInside.
|
|
59
|
+
* @typedef {{path: string, use: boolean, method: string, all: boolean}} MountGuard
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* A layer as express shapes it, which is what app.stack and router.stack hand out.
|
|
63
|
+
* @typedef {object} Layer
|
|
64
|
+
* @property {Function} handle
|
|
65
|
+
* @property {string} name
|
|
66
|
+
* @property {undefined} params
|
|
67
|
+
* @property {undefined} path
|
|
68
|
+
* @property {never[]} keys
|
|
69
|
+
* @property {RouteEntry|undefined} route
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* A websocket registration, kept until listen() hands it to µWS.
|
|
73
|
+
* @typedef {{path: string, behavior: Record<string, unknown>, owner: Router}} WsRoute
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
// whether a registered path could be asked for in another case, which is what decides whether the
|
|
77
|
+
// native router can be trusted to prefer it, see _optimizeRoute
|
|
78
|
+
const HAS_LETTER = /[a-zA-Z]/;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether an earlier route would have answered this path had case not mattered. A guard is a
|
|
82
|
+
* folded string when the earlier path is a literal, and an insensitive pattern when it has
|
|
83
|
+
* parameters of its own.
|
|
84
|
+
*
|
|
85
|
+
* The string side is compared character by character rather than through toLowerCase: it sits on
|
|
86
|
+
* the hot path of every parameter route with an earlier literal, and saying no must allocate
|
|
87
|
+
* nothing.
|
|
88
|
+
*
|
|
89
|
+
* @param {(string|RegExp)[]} guards
|
|
90
|
+
* @param {string} path the path as it arrived
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
function anyGuardHits(guards, path) {
|
|
94
|
+
for (let i = 0; i < guards.length; i++) {
|
|
95
|
+
const guard = guards[i];
|
|
96
|
+
if (typeof guard !== "string") {
|
|
97
|
+
if (guard.test(path)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// the guard is a registered path, which under the default routing answers the same path
|
|
103
|
+
// with one trailing slash too: "/x1" registered serves "/x1/", so "/X1/" is as much a case
|
|
104
|
+
// variant of it as "/X1". Missing that answered "/X1/" from the parameter route behind it
|
|
105
|
+
// while express answered from the literal. The regex guards are built non-strict and
|
|
106
|
+
// already accept it. Erring wide costs nothing: a guard that hits only hands the request
|
|
107
|
+
// to the generic router
|
|
108
|
+
const slashed = path.length === guard.length + 1 && path.charCodeAt(guard.length) === 0x2f;
|
|
109
|
+
if (guard.length !== path.length && !slashed) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
let same = true;
|
|
113
|
+
for (let j = 0; j < guard.length; j++) {
|
|
114
|
+
let code = path.charCodeAt(j);
|
|
115
|
+
// A to Z only, which is the fold express's insensitive routing does
|
|
116
|
+
if (code >= 65 && code <= 90) {
|
|
117
|
+
code += 32;
|
|
118
|
+
}
|
|
119
|
+
if (code !== guard.charCodeAt(j)) {
|
|
120
|
+
same = false;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (same) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// every method the declarative compiler can emit: a patched one must disable compilation, or the
|
|
132
|
+
// patch would be honoured everywhere but on compiled routes
|
|
133
|
+
const resCodes = {},
|
|
134
|
+
resDecMethods = ["set", "setHeader", "header", "send", "end", "append", "status", "json", "sendStatus"];
|
|
135
|
+
for (const method of resDecMethods) {
|
|
136
|
+
resCodes[method] = Response.prototype[method].toString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The layer Express makes for one mounted handler. `name` is what a caller matches on: a function's
|
|
141
|
+
* own name, "router" for a mounted router, and "<anonymous>" for the rest, exactly as express reads
|
|
142
|
+
* them off the handle.
|
|
143
|
+
*
|
|
144
|
+
* @param {RouteEntry} route
|
|
145
|
+
* @param {Function & {_routes?: RouteEntry[], _isApplication?: boolean}} callback a handler, or a
|
|
146
|
+
* mounted router, which is callable and carries _routes
|
|
147
|
+
* @returns {Layer} the layer object, which is express's shape and not one of ours
|
|
148
|
+
*/
|
|
149
|
+
function layerFor(route, callback) {
|
|
150
|
+
const layer = {
|
|
151
|
+
handle: callback,
|
|
152
|
+
// express reads the name off the handle, and its own handles are named: a mounted
|
|
153
|
+
// application is "app" and a mounted router "router", whatever this project happens
|
|
154
|
+
// to call the function underneath
|
|
155
|
+
name: Array.isArray(callback._routes)
|
|
156
|
+
? callback._isApplication
|
|
157
|
+
? "app"
|
|
158
|
+
: "router"
|
|
159
|
+
: callback.name || "<anonymous>",
|
|
160
|
+
params: undefined,
|
|
161
|
+
path: undefined,
|
|
162
|
+
keys: [],
|
|
163
|
+
route: undefined
|
|
164
|
+
};
|
|
165
|
+
route._layers.set(callback, layer);
|
|
166
|
+
return layer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The layer Express makes for a route, whose handle runs the route's own handlers one after
|
|
171
|
+
* another. Express calls that handle `handle`, and a caller that looks for a route layer looks for
|
|
172
|
+
* that name.
|
|
173
|
+
*
|
|
174
|
+
* @param {RouteEntry} route
|
|
175
|
+
* @returns {Layer} the layer object, which is express's shape and not one of ours
|
|
176
|
+
*/
|
|
177
|
+
function routeLayer(route) {
|
|
178
|
+
const handle = function handle(req, res, next) {
|
|
179
|
+
let index = 0;
|
|
180
|
+
const step = (err) => {
|
|
181
|
+
const callback = route.callbacks[index++];
|
|
182
|
+
if (callback === undefined) {
|
|
183
|
+
return next(err);
|
|
184
|
+
}
|
|
185
|
+
const isErrorHandler = callback.length === 4;
|
|
186
|
+
if ((err === undefined || err === null) === isErrorHandler) {
|
|
187
|
+
return step(err);
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return isErrorHandler ? callback(err, req, res, step) : callback(req, res, step);
|
|
191
|
+
} catch (thrown) {
|
|
192
|
+
return step(thrown);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
step();
|
|
196
|
+
};
|
|
197
|
+
return { handle, name: "handle", params: undefined, path: undefined, keys: [], route: route.exposed };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The native handler's resolve, invoked as this.resolve(matched) with the walk as receiver. The
|
|
202
|
+
* promise pair _routeRequest allocates exists for callers that await; the uWS handler never did,
|
|
203
|
+
* and on the common path that promise never even settled: an async frame and two promises of
|
|
204
|
+
* floating garbage per request.
|
|
205
|
+
*
|
|
206
|
+
* The 404 epilogue stays on a microtask, where the await used to resume: a middleware that writes
|
|
207
|
+
* after calling next() must still win the headersSent check, as it does in express.
|
|
208
|
+
* @this {Walk}
|
|
209
|
+
*/
|
|
210
|
+
function nativeDone(matched) {
|
|
211
|
+
if (this.settled) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.settled = true;
|
|
215
|
+
if (!matched) {
|
|
216
|
+
queueMicrotask(() => {
|
|
217
|
+
const response = this.res;
|
|
218
|
+
// a 404 after the head is left as it is, as express's final handler leaves it; an error
|
|
219
|
+
// after it goes on to _handleError, which closes the connection as that handler does
|
|
220
|
+
if (response.aborted || (response.headersSent && !this.req._error)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
this.router._endUnmatched(this.req, response);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (response.aborted || response.finished) {
|
|
227
|
+
logError(this.router, err);
|
|
228
|
+
} else {
|
|
229
|
+
this.router._handleError(err, null, this.req, response);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The native handler's reject: answers 500 as express's final handler would, instead of dying as
|
|
238
|
+
* an unhandled rejection. Deferred like the resolve, since every rejection used to reach the
|
|
239
|
+
* handler's catch through an await.
|
|
240
|
+
* @this {Walk}
|
|
241
|
+
*/
|
|
242
|
+
function nativeFail(err) {
|
|
243
|
+
if (this.settled) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.settled = true;
|
|
247
|
+
queueMicrotask(() => {
|
|
248
|
+
const response = this.res;
|
|
249
|
+
if (response.aborted || response.finished) {
|
|
250
|
+
logError(this.router, err);
|
|
251
|
+
} else {
|
|
252
|
+
this.router._handleError(err, null, this.req, response);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* How much of the path a mount takes, which is what its own pattern matched and never more than
|
|
259
|
+
* there is. Exec runs on the same fixed-up path _pathMatches tested: a parent mount that consumed
|
|
260
|
+
* everything leaves "", where the pattern was matched against "/".
|
|
261
|
+
*
|
|
262
|
+
* Counting what each mount took, rather than composing one pattern out of the whole stack, is the
|
|
263
|
+
* difference between a sum and a guess: a mount written as an optional group composes into a
|
|
264
|
+
* pattern the path no longer satisfies, and the prefix stayed on.
|
|
265
|
+
*
|
|
266
|
+
* @param {RouteEntry} route
|
|
267
|
+
* @param {Request} req
|
|
268
|
+
* @returns {number}
|
|
269
|
+
*/
|
|
270
|
+
function mountPrefixLength(route, req) {
|
|
271
|
+
// a use with no path is EMPTY_REGEX, which matches "" at 0 whatever the path is. Answered
|
|
272
|
+
// without the exec, since this runs per hop and most middleware is pathless
|
|
273
|
+
if (route.pattern === EMPTY_REGEX) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
// the registration-time constant of a literal mount, exec-free. See createRoute
|
|
277
|
+
if (route.mountLen !== undefined) {
|
|
278
|
+
return route.mountLen;
|
|
279
|
+
}
|
|
280
|
+
if (typeof route.pattern === "string") {
|
|
281
|
+
return route.pattern.length;
|
|
282
|
+
}
|
|
283
|
+
const path = req._opPath;
|
|
284
|
+
const matched = route.pattern.exec(path === "" ? "/" : path);
|
|
285
|
+
return matched ? Math.min(matched[0].length, path.length) : 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Writes the path the routes below a mount see: the original with what the mounts took off the
|
|
290
|
+
* front. The root reads as "/" rather than as nothing, which is how express hands it over.
|
|
291
|
+
*
|
|
292
|
+
* @param {Request} req
|
|
293
|
+
*/
|
|
294
|
+
function setMountedPath(req) {
|
|
295
|
+
req._opPath = req._consumed === 0 ? req._originalPath : req._originalPath.slice(req._consumed);
|
|
296
|
+
req._opPathLower = null;
|
|
297
|
+
req.url = req._opPath === "" ? "/" + req.urlQuery : req._opPath + req.urlQuery;
|
|
298
|
+
req._path = req._opPath === "" ? "/" : req._opPath;
|
|
299
|
+
req._lastUrl = req.url;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// req.path as the request class declares it, taken off the prototype rather than written out a
|
|
303
|
+
// second time. A request the router adopts is a plain object and gets it defined on itself, see
|
|
304
|
+
// adoptPlainRequest. Enumerable, as express's own is.
|
|
305
|
+
const PATH_PROPERTY = {
|
|
306
|
+
.../** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Request.prototype, "path")),
|
|
307
|
+
enumerable: true
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// and the two the walk calls when a middleware rewrote req.url or req.method, for the same reason:
|
|
311
|
+
// an adopted request has no prototype of ours to find them on, and a rewrite through one of those
|
|
312
|
+
// routers threw instead of being taken over
|
|
313
|
+
const ABSORB_URL = Request.prototype._absorbUrlRewrite;
|
|
314
|
+
const ABSORB_METHOD = Request.prototype._absorbMethodRewrite;
|
|
315
|
+
|
|
316
|
+
const NO_PARAM_NAMES = [];
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The parameter names a route captures with its own pattern.
|
|
320
|
+
*
|
|
321
|
+
* This is the set express runs param callbacks for. A name that reached req.params from a mount
|
|
322
|
+
* above, through mergeParams, belongs to that mount's router: express walks the keys the layer
|
|
323
|
+
* itself matched. Reading req.params instead ran a callback for every inherited name too, and
|
|
324
|
+
* turned a 200 into a 500 when one of them refused the value.
|
|
325
|
+
*
|
|
326
|
+
* Worked out once per route and kept, since it follows from the pattern.
|
|
327
|
+
*
|
|
328
|
+
* @param {RouteEntry} route
|
|
329
|
+
* @returns {string[]}
|
|
330
|
+
*/
|
|
331
|
+
function ownParamNames(route) {
|
|
332
|
+
let names = route._ownParamNames;
|
|
333
|
+
if (names !== undefined) {
|
|
334
|
+
return names;
|
|
335
|
+
}
|
|
336
|
+
if (route.optimizedParams) {
|
|
337
|
+
// µWS matched the pattern and hands the values back by position, under these names
|
|
338
|
+
names = route.optimizedParams;
|
|
339
|
+
} else if (route.pattern instanceof RegExp) {
|
|
340
|
+
const meta = getPatternMeta(route.pattern);
|
|
341
|
+
// outputNames is what _extractParams writes into params; a RegExp the application wrote
|
|
342
|
+
// itself was never compiled here, so its capture groups are the names
|
|
343
|
+
names = meta ? meta.outputNames : regexpGroupKeys(route.pattern);
|
|
344
|
+
} else {
|
|
345
|
+
names = NO_PARAM_NAMES;
|
|
346
|
+
}
|
|
347
|
+
route._ownParamNames = names;
|
|
348
|
+
return names;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Whether this route reads the parameters of the mounts above it, which is its own router asking
|
|
353
|
+
* for them. The stack holds what a mergeParams router captured on the way in, and a plain router
|
|
354
|
+
* mounted inside one must not read it: express asks each router in turn, not the outermost.
|
|
355
|
+
*
|
|
356
|
+
* @param {RouteEntry} route
|
|
357
|
+
* @param {Router} fallback the router dispatching, when the route names no owner
|
|
358
|
+
* @returns {boolean}
|
|
359
|
+
*/
|
|
360
|
+
function mergesParams(route, fallback) {
|
|
361
|
+
const owner = route.owner ?? fallback;
|
|
362
|
+
return Boolean(owner?._settings?.mergeParams);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// shared empty candidate list, so _scanFrom never tests for a missing map entry twice
|
|
366
|
+
const EMPTY_INDICES = /** @type {number[]} */ ([]);
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The generic scan's index over a router's literal routes: route positions by folded pattern, so
|
|
370
|
+
* a scan visits the routes registered for this exact path instead of comparing every one. String
|
|
371
|
+
* patterns are pure literals, everything else, "/*" included, stays in alwaysVisit and is still
|
|
372
|
+
* matched per request by _pathMatches.
|
|
373
|
+
*
|
|
374
|
+
* @param {RouteEntry[]} routes the router's own table
|
|
375
|
+
* @param {boolean} caseFlag the frozen case-sensitivity flag
|
|
376
|
+
* @returns {{map: Map<string, number[]>, alwaysVisit: number[]}}
|
|
377
|
+
*/
|
|
378
|
+
function buildLiteralIndex(routes, caseFlag) {
|
|
379
|
+
const map = new Map();
|
|
380
|
+
const alwaysVisit = [];
|
|
381
|
+
for (let i = 0; i < routes.length; i++) {
|
|
382
|
+
const pattern = routes[i].pattern;
|
|
383
|
+
if (typeof pattern === "string" && pattern !== "/*") {
|
|
384
|
+
const key = caseFlag ? pattern : routes[i].patternLower;
|
|
385
|
+
const list = map.get(key);
|
|
386
|
+
if (list === undefined) {
|
|
387
|
+
map.set(key, [i]);
|
|
388
|
+
} else {
|
|
389
|
+
list.push(i);
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
alwaysVisit.push(i);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { map, alwaysVisit };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The position of the first value >= from in an ascending list, which is list.length when there
|
|
400
|
+
* is none: where a scan resuming at `from` enters a candidate list.
|
|
401
|
+
*
|
|
402
|
+
* @param {number[]} list
|
|
403
|
+
* @param {number} from
|
|
404
|
+
* @returns {number}
|
|
405
|
+
*/
|
|
406
|
+
function firstAtLeast(list, from) {
|
|
407
|
+
let low = 0;
|
|
408
|
+
let high = list.length;
|
|
409
|
+
while (low < high) {
|
|
410
|
+
const mid = (low + high) >> 1;
|
|
411
|
+
if (list[mid] < from) {
|
|
412
|
+
low = mid + 1;
|
|
413
|
+
} else {
|
|
414
|
+
high = mid;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return low;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* The route's own params merged with those of the mounts it sits under, in express's order: an
|
|
422
|
+
* outer mount first, the route's own last. Numbered captures do not overwrite each other, they
|
|
423
|
+
* shift, so a RegExp mount capturing one group leaves the route's own group numbered from one.
|
|
424
|
+
*
|
|
425
|
+
* @param {Record<string, any>} own what this route's own pattern captured
|
|
426
|
+
* @param {Record<string, any>[]} stack the mounts, outermost first
|
|
427
|
+
* @returns {Record<string, any>}
|
|
428
|
+
*/
|
|
429
|
+
function mergeParams(own, stack) {
|
|
430
|
+
const merged = Object.create(null);
|
|
431
|
+
for (const params of stack) {
|
|
432
|
+
Object.assign(merged, params);
|
|
433
|
+
}
|
|
434
|
+
// both sides numbering from zero means the outer ones keep their places and these move up
|
|
435
|
+
if (own[0] !== undefined && merged[0] !== undefined) {
|
|
436
|
+
let count = 0;
|
|
437
|
+
while (merged[count] !== undefined) {
|
|
438
|
+
count++;
|
|
439
|
+
}
|
|
440
|
+
let last = 0;
|
|
441
|
+
while (own[last] !== undefined) {
|
|
442
|
+
last++;
|
|
443
|
+
}
|
|
444
|
+
for (last--; last >= 0; last--) {
|
|
445
|
+
own[last + count] = own[last];
|
|
446
|
+
if (last < count) {
|
|
447
|
+
delete own[last];
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return Object.assign(merged, own);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* The scheme and authority of an absolute request target, or "" for the ordinary kind.
|
|
456
|
+
*
|
|
457
|
+
* A request line may carry the whole URI, and express matches on the path while leaving req.url as
|
|
458
|
+
* it arrived. Same rule it uses: a "://" before any "?" means everything up to the slash after it
|
|
459
|
+
* is not path.
|
|
460
|
+
*
|
|
461
|
+
* @param {string} url
|
|
462
|
+
* @returns {string}
|
|
463
|
+
*/
|
|
464
|
+
function protohostOf(url) {
|
|
465
|
+
if (url.length === 0 || url.charCodeAt(0) === 0x2f) {
|
|
466
|
+
return "";
|
|
467
|
+
}
|
|
468
|
+
const searchIndex = url.indexOf("?");
|
|
469
|
+
const pathLength = searchIndex === -1 ? url.length : searchIndex;
|
|
470
|
+
const fqdnIndex = url.slice(0, pathLength).indexOf("://");
|
|
471
|
+
if (fqdnIndex === -1) {
|
|
472
|
+
return "";
|
|
473
|
+
}
|
|
474
|
+
const slash = url.indexOf("/", fqdnIndex + 3);
|
|
475
|
+
return slash === -1 ? url : url.slice(0, slash);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Fills in what dispatch reads on a request that did not come from uWS.
|
|
480
|
+
*
|
|
481
|
+
* express's router can be driven with a plain object, `router.handle({ url, method }, res, next)`,
|
|
482
|
+
* and its own tests do exactly that. Only ever called for such a request: one of ours arrives with
|
|
483
|
+
* these fields already set.
|
|
484
|
+
*
|
|
485
|
+
* req.url becomes an accessor, so the router goes on writing plain paths to it while a reader sees
|
|
486
|
+
* the absolute URI it arrived as, which keeps the protohost out of the dispatch.
|
|
487
|
+
*
|
|
488
|
+
* @param {any} req the plain object a caller drove the router with, not one of our requests
|
|
489
|
+
* @param {Router} router
|
|
490
|
+
*/
|
|
491
|
+
function adoptPlainRequest(req, router) {
|
|
492
|
+
const arrived = typeof req.url === "string" ? req.url : "";
|
|
493
|
+
const protohost = protohostOf(arrived);
|
|
494
|
+
let raw = arrived.slice(protohost.length);
|
|
495
|
+
if (protohost !== "") {
|
|
496
|
+
Object.defineProperty(req, "url", {
|
|
497
|
+
configurable: true,
|
|
498
|
+
enumerable: true,
|
|
499
|
+
get() {
|
|
500
|
+
return protohost + raw;
|
|
501
|
+
},
|
|
502
|
+
set(value) {
|
|
503
|
+
const written = String(value);
|
|
504
|
+
raw = written.startsWith(protohost) ? written.slice(protohost.length) : written;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const queryIndex = raw.indexOf("?");
|
|
510
|
+
const path = queryIndex === -1 ? raw : raw.slice(0, queryIndex);
|
|
511
|
+
req.urlQuery = queryIndex === -1 ? "" : raw.slice(queryIndex);
|
|
512
|
+
req._rawQuery = req.urlQuery.slice(1);
|
|
513
|
+
req._path = path;
|
|
514
|
+
// an adopted request is a plain object, so it carries no prototype of ours and reads its path
|
|
515
|
+
// off a property of its own. The class's getter itself, so there is one of it
|
|
516
|
+
Object.defineProperty(req, "path", PATH_PROPERTY);
|
|
517
|
+
req._absorbUrlRewrite = ABSORB_URL;
|
|
518
|
+
req._absorbMethodRewrite = ABSORB_METHOD;
|
|
519
|
+
req.originalUrl = req.originalUrl ?? arrived;
|
|
520
|
+
req._originalPath = path;
|
|
521
|
+
req.endsWithSlash = path.charCodeAt(path.length - 1) === 0x2f;
|
|
522
|
+
req._opPath = path;
|
|
523
|
+
req._opPathLower = null;
|
|
524
|
+
req._mayFailDecode = null;
|
|
525
|
+
req._lastUrl = req.url;
|
|
526
|
+
req._lastMethod = req.method;
|
|
527
|
+
req._isOptions = req.method === "OPTIONS";
|
|
528
|
+
req._isHead = req.method === "HEAD";
|
|
529
|
+
req.params = req.params ?? Object.create(null);
|
|
530
|
+
// null, not fresh arrays: the push sites materialize them on the first mount, and most
|
|
531
|
+
// requests never see one, same as the Request constructor
|
|
532
|
+
req._stack = null;
|
|
533
|
+
req._consumed = 0;
|
|
534
|
+
req._mountSlash = false;
|
|
535
|
+
req._paramStack = null;
|
|
536
|
+
req._matchedMethods = req._isOptions ? new Set() : null;
|
|
537
|
+
req.routeCount = 1;
|
|
538
|
+
// read when a mount is left, and there is no application here to read it from
|
|
539
|
+
req.app = req.app ?? router;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* What express's logerror does. Its final handler prints the error it is about to answer with,
|
|
544
|
+
* unless the application runs under `env: "test"`, which is how its own suite stays quiet, and it
|
|
545
|
+
* prints the stack rather than the object. A falsy throw is not printed at all, since finalhandler
|
|
546
|
+
* only calls onerror when there is an error to call it with.
|
|
547
|
+
*
|
|
548
|
+
* @param {Router} router the router whose settings decide it
|
|
549
|
+
* @param {any} err whatever was thrown, which need not be an Error
|
|
550
|
+
* @returns {void}
|
|
551
|
+
*/
|
|
552
|
+
function logError(router, err) {
|
|
553
|
+
if (err && router.get("env") !== "test") {
|
|
554
|
+
console.error(err.stack || err.toString());
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* The uWS onAborted handler, bound to the response: a closure here captured two locals and cost
|
|
560
|
+
* a context plus a function per request, for a path that only ever runs on a client abort.
|
|
561
|
+
* @this {Response} the response, with the request linked as this.req
|
|
562
|
+
*/
|
|
563
|
+
function onNativeAborted() {
|
|
564
|
+
const response = this;
|
|
565
|
+
const request = response.req;
|
|
566
|
+
// node's wording for a client abort, which is what body consumers match on
|
|
567
|
+
/** @type {NodeJS.ErrnoException} */
|
|
568
|
+
const err = new Error("aborted");
|
|
569
|
+
err.code = "ECONNRESET";
|
|
570
|
+
response.aborted = true;
|
|
571
|
+
response.finished = true;
|
|
572
|
+
// node's order on the request: 'aborted', then the stream dies, then 'close'. The
|
|
573
|
+
// error goes only to whoever listens for it, since a destroy(err) with no listener
|
|
574
|
+
// would take down the process
|
|
575
|
+
request.emit("aborted");
|
|
576
|
+
// and the response dies between the two, which is where node puts it. Destroyed rather than
|
|
577
|
+
// told to emit 'close', because being destroyed is the state express is in here and the rest
|
|
578
|
+
// follows from it: 'close' goes out once, a later res.write returns false and calls back with
|
|
579
|
+
// ERR_STREAM_DESTROYED, and no 'error' is emitted.
|
|
580
|
+
//
|
|
581
|
+
// Without this a handler learnt about the abort only from a write failing, so one that had sent
|
|
582
|
+
// its head and gone quiet never learnt at all. `res.on("close")` is where cancellation hangs in
|
|
583
|
+
// every proxy and every streaming endpoint
|
|
584
|
+
response.destroy();
|
|
585
|
+
request.destroy(request.listenerCount("error") > 0 ? err : undefined);
|
|
586
|
+
response.socket?.emit("error", err);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* The per-request constants of a fully literal native registration. µWS matched the URL byte for
|
|
591
|
+
* byte against this exact pattern and dispatches by method, so the request constructor can take
|
|
592
|
+
* these as given instead of asking uWS and recomputing them on every request.
|
|
593
|
+
*
|
|
594
|
+
* @param {string} path the registered pattern, which is what getUrl() would have answered
|
|
595
|
+
* @param {string} method uppercase, fixed by which uWS verb the registration used
|
|
596
|
+
*/
|
|
597
|
+
function nativePreset(path, method) {
|
|
598
|
+
const endsWithSlash = path.charCodeAt(path.length - 1) === 0x2f;
|
|
599
|
+
return {
|
|
600
|
+
path,
|
|
601
|
+
method,
|
|
602
|
+
endsWithSlash,
|
|
603
|
+
opPath: path,
|
|
604
|
+
isOptions: method === "OPTIONS",
|
|
605
|
+
isHead: method === "HEAD",
|
|
606
|
+
// set at registration when the whole chain provably never reads a header, or never
|
|
607
|
+
// reads the query; mutable, because a middleware added after listen takes them back
|
|
608
|
+
skipHeaders: false,
|
|
609
|
+
skipQuery: false
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Whether any error middleware exists anywhere under this router, mounted routers and sub-apps
|
|
615
|
+
* included. The header-skip analysis needs the answer to be no: a throw inside an analyzed
|
|
616
|
+
* handler would hand the request to code nobody analyzed.
|
|
617
|
+
*
|
|
618
|
+
* @param {Router} router
|
|
619
|
+
* @returns {boolean}
|
|
620
|
+
*/
|
|
621
|
+
function hasErrorMiddleware(router) {
|
|
622
|
+
for (const route of router._routes) {
|
|
623
|
+
for (const callback of route.callbacks) {
|
|
624
|
+
// a mounted router or a callable sub-app carries routes of its own; the callable
|
|
625
|
+
// app is also a function, so the routes are looked for first
|
|
626
|
+
if (callback && callback._routes) {
|
|
627
|
+
if (hasErrorMiddleware(callback)) {
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
} else if (typeof callback === "function" && callback.length >= 4) {
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
*
|
|
640
|
+
*/
|
|
641
|
+
function checkHandlers(handlers, emptyMessage = "argument handler is required") {
|
|
642
|
+
if (handlers.length === 0) {
|
|
643
|
+
throw new TypeError(emptyMessage);
|
|
644
|
+
}
|
|
645
|
+
for (const handler of handlers) {
|
|
646
|
+
if (typeof handler !== "function") {
|
|
647
|
+
throw new TypeError("argument handler must be a function");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// what a route's callback is, so that a hop reads a number instead of asking instanceof and length
|
|
653
|
+
const CALLBACK_PLAIN = 0;
|
|
654
|
+
const CALLBACK_ERROR = 1;
|
|
655
|
+
const CALLBACK_ROUTER = 2;
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Reports a parameter that will not decode, unless something is already being reported.
|
|
659
|
+
*
|
|
660
|
+
* Matching a route decodes its parameters, and that happens while the walk is still looking for
|
|
661
|
+
* whoever should answer. Express keeps the first error it has: `layerError = layerError || match`
|
|
662
|
+
* in its router. Overwriting meant express.static's Bad Request on an escape it could not decode
|
|
663
|
+
* was replaced by the decode failure of a route further down that was never going to run. Found by
|
|
664
|
+
* fuzzing route tables against express.
|
|
665
|
+
*
|
|
666
|
+
* @param {Request} req
|
|
667
|
+
* @param {RouteEntry} route
|
|
668
|
+
* @param {unknown} err whatever decoding threw
|
|
669
|
+
*/
|
|
670
|
+
function raiseDecodeFailure(req, route, err) {
|
|
671
|
+
if (req._error) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
req._error = err;
|
|
675
|
+
req._errorKey = route.routeKey;
|
|
676
|
+
req._errorGroup = route.group;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// the verbs a body is read for unless the application says otherwise, which is the parsers' own
|
|
680
|
+
// list. A request with any other verb reaches a parser's method check and leaves through it
|
|
681
|
+
const BODY_METHODS = new Set(["POST", "PUT", "PATCH", "QUERY"]);
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Whether this layer can be stepped over for this request without changing a thing.
|
|
685
|
+
*
|
|
686
|
+
* Only the body parsers are ever asked. Their prologue leaves a request that said nothing about a
|
|
687
|
+
* body alone, whatever content type it carries, which is what `kGetSafe` records. Two conditions
|
|
688
|
+
* on top of that mark, and both are needed:
|
|
689
|
+
*
|
|
690
|
+
* The request must have said nothing about framing at all, a `content-length: 0` included. A parser
|
|
691
|
+
* that can see a length answers about the body it describes even when that body is empty: a zero
|
|
692
|
+
* length with a charset nobody can decode is a 415, in express and here.
|
|
693
|
+
*
|
|
694
|
+
* And the verb must be one no parser reads a body for. With no length and no transfer-encoding a
|
|
695
|
+
* POST still walks into the read, comes back with nothing, and leaves `req.body` as the empty value
|
|
696
|
+
* its parser produces, which a handler can see.
|
|
697
|
+
*
|
|
698
|
+
* What it is worth: a hop measured 367us per thousand requests and the parser prologue it reaches
|
|
699
|
+
* measured 38, ten to one for a layer that had nothing to do.
|
|
700
|
+
*
|
|
701
|
+
* That number is why fusing consecutive layers into one generated function keeps coming up, and why
|
|
702
|
+
* it is not here. Counted over a real front (morgan, helmet, compression, cors, the two body
|
|
703
|
+
* parsers, express-session, one of one's own, express.static): three of the nine can be fused, and
|
|
704
|
+
* the longest run of fusable ones in a row is one, where fusing needs two. The six that fail all
|
|
705
|
+
* call next from inside a callback, reading a body, stat-ing a file, loading a session, so no
|
|
706
|
+
* design that keeps the semantics can fuse them.
|
|
707
|
+
*
|
|
708
|
+
* @param {RouteEntry} route
|
|
709
|
+
* @param {Request} req
|
|
710
|
+
* @returns {boolean}
|
|
711
|
+
*/
|
|
712
|
+
function stepsOver(route, req) {
|
|
713
|
+
if (route.bodyParserOnly !== true || req._hasBodyHeaders === true) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
if (BODY_METHODS.has(req.method)) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
// an application can add its own. Read once and kept, which is what the parser behind this
|
|
720
|
+
// layer does with the same setting: asking on every request measured 17 microseconds per
|
|
721
|
+
// thousand, a third of what stepping over the layer saves
|
|
722
|
+
if (route.bodyMethods === undefined) {
|
|
723
|
+
route.bodyMethods = req.app.get("body methods") ?? null;
|
|
724
|
+
}
|
|
725
|
+
if (route.bodyMethods !== null && route.bodyMethods.includes(req.method)) {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
// The layer is not entered, so it leaves the one mark it would have left: the parser puts
|
|
729
|
+
// `body` on the request before it works out that there is nothing to read. A library asks
|
|
730
|
+
// `"body" in req` to tell "a parser has run" from "none has", and a skip that did not leave
|
|
731
|
+
// it would answer a GET differently from express. See the same seeding in middlewares.js
|
|
732
|
+
if (!("body" in req)) {
|
|
733
|
+
// cast because `body` is deliberately not a field of Request, see the comment there
|
|
734
|
+
/** @type {{body?: unknown}} */ (req).body = undefined;
|
|
735
|
+
}
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Whether a route could answer a request for this path, judged on the pattern it was compiled to.
|
|
741
|
+
* A literal answers only itself; anything with a parameter or a wildcard answers what its regex
|
|
742
|
+
* says. Used where the question is "would this earlier route have had its turn first".
|
|
743
|
+
*
|
|
744
|
+
* @param {RouteEntry} route
|
|
745
|
+
* @param {string} path
|
|
746
|
+
* @returns {boolean}
|
|
747
|
+
*/
|
|
748
|
+
function couldAnswer(route, path) {
|
|
749
|
+
if (route.pattern instanceof RegExp) {
|
|
750
|
+
return route.pattern.test(path);
|
|
751
|
+
}
|
|
752
|
+
return route.pattern === path;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Whether a layer written before a mount could answer a request for one of the paths inside it.
|
|
757
|
+
*
|
|
758
|
+
* A mount covers everything under its path, so this is a question about a subtree and not about the
|
|
759
|
+
* mount point: `/a` and `/:p0/:p1/:p2` match none of each other's text and both answer `/a/x/y`.
|
|
760
|
+
* uWS jumps straight to whichever leaf it registered, so a leaf a layer like this could have
|
|
761
|
+
* answered has to stay on the generic path.
|
|
762
|
+
*
|
|
763
|
+
* Only layers with more segments than the mount path reach this: one with as few already matches
|
|
764
|
+
* the mount point itself. Compared folded whichever way the routers are set, and a wrong yes costs
|
|
765
|
+
* a leaf its native registration and nothing else.
|
|
766
|
+
*
|
|
767
|
+
* @param {{path: string, use: boolean, method: string, all: boolean}} guard
|
|
768
|
+
* @param {string} leafPath the leaf's absolute path, parameters and all
|
|
769
|
+
* @param {RouteEntry} leaf
|
|
770
|
+
* @returns {boolean}
|
|
771
|
+
*/
|
|
772
|
+
function shadowsLeaf(guard, leafPath, leaf) {
|
|
773
|
+
if (!guard.all && guard.method !== leaf.method && !(guard.method === "HEAD" && leaf.method === "GET")) {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
return pathsCanOverlap(guard.path.toLowerCase(), leafPath.toLowerCase(), guard.use);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* The layers before a mount that answer some of what is inside it and not all of it, the one thing
|
|
781
|
+
* neither the chain nor uWS's own choice can say: the chain runs what is in it without matching
|
|
782
|
+
* again, and uWS picks by specificity. They are carried down the walk and asked about every leaf,
|
|
783
|
+
* see shadowsLeaf.
|
|
784
|
+
*
|
|
785
|
+
* @param {Router} router the router the mount belongs to
|
|
786
|
+
* @param {RouteEntry} mount
|
|
787
|
+
* @param {string} pathPrefix what the mounts above this one consumed
|
|
788
|
+
* @param {RouteEntry[]} chain the layers that always run before the mount, which need no guard
|
|
789
|
+
* @param {MountGuard[]} inherited the guards from further out, since a mount two levels down is under
|
|
790
|
+
* everything written before either of them
|
|
791
|
+
* @returns {MountGuard[]|null} null when a path cannot be read segment by segment, which leaves the mount
|
|
792
|
+
* to ordinary dispatch rather than guessing about it
|
|
793
|
+
*/
|
|
794
|
+
function guardsInside(router, mount, pathPrefix, chain, inherited) {
|
|
795
|
+
let guards = inherited;
|
|
796
|
+
for (const r of router._routes) {
|
|
797
|
+
if (r.routeKey > mount.routeKey) {
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
if (r === mount || chain.includes(r)) {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
if (typeof r.path !== "string") {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
if (guards === inherited) {
|
|
807
|
+
guards = [...inherited];
|
|
808
|
+
}
|
|
809
|
+
guards.push({ path: pathPrefix + r.path, use: r.use === true, method: r.method, all: r.all === true });
|
|
810
|
+
}
|
|
811
|
+
return guards;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Notes which application is current before a mounted one is entered, so that exact one comes back.
|
|
816
|
+
*
|
|
817
|
+
* Only an application takes it back. Express restores req.app by putting the request prototype
|
|
818
|
+
* back, and it wraps a mounted application to do that only in Application#use: hang one off a plain
|
|
819
|
+
* Router and nothing restores it. Restoring regardless made a later res.send answer with the outer
|
|
820
|
+
* application's etag setting where express answers with the inner.
|
|
821
|
+
*
|
|
822
|
+
* And what comes back is what was current, not the entered application's parent. Those differ the
|
|
823
|
+
* moment a sub-app is entered from inside another sub-app a plain Router mounted, and reaching for
|
|
824
|
+
* `.parent` skipped a level: a 404 from the top application then carried an ETag under
|
|
825
|
+
* `app.set("etag", false)`. Found by fuzzing three levels of routers.
|
|
826
|
+
*
|
|
827
|
+
* The route is remembered alongside, so the pop can only take back what this same route put there.
|
|
828
|
+
*
|
|
829
|
+
* @param {Walk} walk
|
|
830
|
+
* @param {RouteEntry} route
|
|
831
|
+
* @param {Request} req
|
|
832
|
+
*/
|
|
833
|
+
function rememberApp(walk, route, req) {
|
|
834
|
+
if (walk.router._isApplication && route.callbacks[0]?._isApplication) {
|
|
835
|
+
(req._appStack ??= []).push(route, req.app);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Puts back what rememberApp noted, if this is the route that noted it.
|
|
841
|
+
*
|
|
842
|
+
* @param {RouteEntry} route
|
|
843
|
+
* @param {Request} req
|
|
844
|
+
*/
|
|
845
|
+
function restoreApp(route, req) {
|
|
846
|
+
const stack = req._appStack;
|
|
847
|
+
if (stack !== undefined && stack.length > 0 && stack[stack.length - 2] === route) {
|
|
848
|
+
const app = stack.pop();
|
|
849
|
+
stack.pop();
|
|
850
|
+
useApp(req, app);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* useApp
|
|
856
|
+
* @param {Request} req
|
|
857
|
+
* @param {Router & {request?: object, response?: object}} app the application taking the request
|
|
858
|
+
* over. Typed as a router because the callers hold one, and only an application carries the two
|
|
859
|
+
* prototype layers read below
|
|
860
|
+
*/
|
|
861
|
+
function useApp(req, app) {
|
|
862
|
+
// only an application takes a request over, see rememberApp
|
|
863
|
+
req.app = /** @type {import("./application.js").Application} */ (app);
|
|
864
|
+
if (req.res) {
|
|
865
|
+
req.res.app = app;
|
|
866
|
+
}
|
|
867
|
+
// an app's own request/response extensions apply while it runs: express re-parents both
|
|
868
|
+
// objects on entering a mounted app, and this is the equivalent hop
|
|
869
|
+
if (app.request && Object.getPrototypeOf(req) !== app.request) {
|
|
870
|
+
Object.setPrototypeOf(req, app.request);
|
|
871
|
+
}
|
|
872
|
+
if (app.response && req.res && Object.getPrototypeOf(req.res) !== app.response) {
|
|
873
|
+
Object.setPrototypeOf(req.res, app.response);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Every verb node knows about, which is the list the methods package hands Express, and "all" on
|
|
878
|
+
// top of it. Taken from node rather than written out: the written out one was missing acl, bind,
|
|
879
|
+
// link, rebind, source, unbind, unlink and unlock, and had four of the others twice.
|
|
880
|
+
//
|
|
881
|
+
// GET is left out on purpose. get() is declared in the class, because it doubles as the settings
|
|
882
|
+
// reader, and the loop at the end of this file would replace it.
|
|
883
|
+
const methods = ["all", ...METHODS.filter((method) => method !== "GET").map((method) => method.toLowerCase())];
|
|
884
|
+
const supportedUwsMethods = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"]);
|
|
885
|
+
|
|
886
|
+
// the same name rule patternToRegex reads, so a unicode name is found here too
|
|
887
|
+
const regExParam = /:([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)/gu;
|
|
888
|
+
|
|
889
|
+
// Internals here are _underscore and not #private: a callable router is a function with the
|
|
890
|
+
// router's properties copied onto it, and a # field cannot be copied, so #routes would throw
|
|
891
|
+
// "Cannot read private member" on the first call.
|
|
892
|
+
|
|
893
|
+
// one intermediate prototype per class, built the first time a callable of that class is made
|
|
894
|
+
const callablePrototypes = new WeakMap();
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* The prototype for a callable router or app: the class prototype, with apply and call put back.
|
|
898
|
+
*
|
|
899
|
+
* Setting a function's prototype to a class prototype drops Function.prototype from the chain, and
|
|
900
|
+
* node calls a request listener with handler.apply. An intermediate object, so express.application
|
|
901
|
+
* stays in the chain. constructor and bind are not restored: the code asks constructor.name, and
|
|
902
|
+
* BIND is an HTTP verb, so app.bind registers a route as it does in Express.
|
|
903
|
+
*
|
|
904
|
+
* @param {object} classPrototype
|
|
905
|
+
* @returns {object}
|
|
906
|
+
*/
|
|
907
|
+
function callablePrototypeFor(classPrototype) {
|
|
908
|
+
let prototype = callablePrototypes.get(classPrototype);
|
|
909
|
+
if (prototype) {
|
|
910
|
+
return prototype;
|
|
911
|
+
}
|
|
912
|
+
prototype = Object.create(classPrototype);
|
|
913
|
+
for (const name of ["apply", "call", "toString"]) {
|
|
914
|
+
Object.defineProperty(prototype, name, {
|
|
915
|
+
value: Function.prototype[name],
|
|
916
|
+
writable: true,
|
|
917
|
+
configurable: true,
|
|
918
|
+
enumerable: false
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
callablePrototypes.set(classPrototype, prototype);
|
|
922
|
+
return prototype;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* The default error page, the one Express produces: the stack in a pre, and nothing else. What
|
|
927
|
+
* reaches it has already been redacted when the environment calls for it.
|
|
928
|
+
*
|
|
929
|
+
* The text is escaped, which is not decoration. An error message can carry anything a client sent,
|
|
930
|
+
* and writing it into the page unescaped put that into the markup. The Content-Security-Policy on
|
|
931
|
+
* this response stops a script from running, but a policy is a second line and not the first.
|
|
932
|
+
*
|
|
933
|
+
* @param {any} err whatever was thrown, which need not be an Error
|
|
934
|
+
* @returns {string}
|
|
935
|
+
*/
|
|
936
|
+
function generateErrorPageHtml(err) {
|
|
937
|
+
const text = String(err?.stack ?? err)
|
|
938
|
+
.replace(/&/g, "&")
|
|
939
|
+
.replace(/</g, "<")
|
|
940
|
+
.replace(/>/g, ">")
|
|
941
|
+
.replace(/"/g, """)
|
|
942
|
+
.replace(/'/g, "'")
|
|
943
|
+
.replace(/\n/g, "<br>")
|
|
944
|
+
.replace(/ {2}/g, " ");
|
|
945
|
+
return (
|
|
946
|
+
`<!DOCTYPE html>\n` +
|
|
947
|
+
`<html lang="en">\n` +
|
|
948
|
+
`<head>\n` +
|
|
949
|
+
`<meta charset="utf-8">\n` +
|
|
950
|
+
`<title>Error</title>\n` +
|
|
951
|
+
`</head>\n` +
|
|
952
|
+
`<body>\n` +
|
|
953
|
+
`<pre>${text}</pre>\n` +
|
|
954
|
+
`</body>\n` +
|
|
955
|
+
`</html>\n`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
module.exports = {
|
|
960
|
+
HAS_LETTER,
|
|
961
|
+
anyGuardHits,
|
|
962
|
+
resCodes,
|
|
963
|
+
resDecMethods,
|
|
964
|
+
layerFor,
|
|
965
|
+
routeLayer,
|
|
966
|
+
nativeDone,
|
|
967
|
+
nativeFail,
|
|
968
|
+
mountPrefixLength,
|
|
969
|
+
setMountedPath,
|
|
970
|
+
ownParamNames,
|
|
971
|
+
mergesParams,
|
|
972
|
+
EMPTY_INDICES,
|
|
973
|
+
buildLiteralIndex,
|
|
974
|
+
firstAtLeast,
|
|
975
|
+
mergeParams,
|
|
976
|
+
adoptPlainRequest,
|
|
977
|
+
logError,
|
|
978
|
+
onNativeAborted,
|
|
979
|
+
nativePreset,
|
|
980
|
+
hasErrorMiddleware,
|
|
981
|
+
checkHandlers,
|
|
982
|
+
CALLBACK_PLAIN,
|
|
983
|
+
CALLBACK_ERROR,
|
|
984
|
+
CALLBACK_ROUTER,
|
|
985
|
+
raiseDecodeFailure,
|
|
986
|
+
stepsOver,
|
|
987
|
+
couldAnswer,
|
|
988
|
+
shadowsLeaf,
|
|
989
|
+
guardsInside,
|
|
990
|
+
rememberApp,
|
|
991
|
+
restoreApp,
|
|
992
|
+
useApp,
|
|
993
|
+
methods,
|
|
994
|
+
supportedUwsMethods,
|
|
995
|
+
regExParam,
|
|
996
|
+
callablePrototypeFor,
|
|
997
|
+
generateErrorPageHtml
|
|
998
|
+
};
|