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
package/src/router.js
CHANGED
|
@@ -22,1543 +22,68 @@ const {
|
|
|
22
22
|
getPatternMeta,
|
|
23
23
|
decodeParam,
|
|
24
24
|
needsConversionToRegex,
|
|
25
|
-
canBeOptimized,
|
|
26
25
|
canBeOptimizedWithParams,
|
|
27
26
|
pathsCanOverlap,
|
|
28
|
-
uwsPrefersEarlier,
|
|
29
27
|
regexpGroupKeys,
|
|
30
28
|
NullObject,
|
|
29
|
+
headersSentError,
|
|
31
30
|
EMPTY_REGEX,
|
|
32
31
|
settingsEpoch
|
|
33
32
|
} = require("./utils.js");
|
|
34
33
|
const Response = require("./response.js");
|
|
35
34
|
const Request = require("./request.js");
|
|
36
35
|
const { EventEmitter } = require("tseep");
|
|
37
|
-
const compileDeclarative = require("./declarative.js");
|
|
38
36
|
const statuses = require("statuses");
|
|
39
37
|
const { METHODS } = require("http");
|
|
40
38
|
const { isNodeRequest, serveNodeRequest } = require("./node-shim.js");
|
|
41
|
-
const {
|
|
39
|
+
const { kGetSafe } = require("./usage.js");
|
|
42
40
|
const { checkBehavior } = require("./websocket.js");
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
41
|
+
const { HotSettings, settingsWriteTraps } = require("./hot-settings.js");
|
|
42
|
+
const Walk = require("./walk.js");
|
|
43
|
+
const { useRouterClass, optimizeRoute, compileOptimizedRoutes, registerUwsRoute } = require("./optimizer.js");
|
|
44
|
+
const {
|
|
45
|
+
layerFor,
|
|
46
|
+
routeLayer,
|
|
47
|
+
nativeDone,
|
|
48
|
+
nativeFail,
|
|
49
|
+
ownParamNames,
|
|
50
|
+
mergesParams,
|
|
51
|
+
EMPTY_INDICES,
|
|
52
|
+
buildLiteralIndex,
|
|
53
|
+
firstAtLeast,
|
|
54
|
+
mergeParams,
|
|
55
|
+
adoptPlainRequest,
|
|
56
|
+
logError,
|
|
57
|
+
onNativeAborted,
|
|
58
|
+
checkHandlers,
|
|
59
|
+
CALLBACK_PLAIN,
|
|
60
|
+
CALLBACK_ERROR,
|
|
61
|
+
CALLBACK_ROUTER,
|
|
62
|
+
raiseDecodeFailure,
|
|
63
|
+
stepsOver,
|
|
64
|
+
useApp,
|
|
65
|
+
methods,
|
|
66
|
+
callablePrototypeFor,
|
|
67
|
+
generateErrorPageHtml
|
|
68
|
+
} = require("./router-utils.js");
|
|
69
|
+
|
|
70
|
+
/** @typedef {import("./router-utils.js").RouteEntry} RouteEntry */
|
|
71
|
+
/** @typedef {import("./router-utils.js").SkipHolder} SkipHolder */
|
|
72
|
+
/** @typedef {import("./router-utils.js").NativePreset} NativePreset */
|
|
73
|
+
/** @typedef {import("./router-utils.js").Layer} Layer */
|
|
74
|
+
/** @typedef {import("./router-utils.js").WsRoute} WsRoute */
|
|
75
|
+
/** @typedef {import("uWebSockets.js").HttpRequest} UwsRequest */
|
|
76
|
+
/** @typedef {import("uWebSockets.js").HttpResponse} UwsResponse */
|
|
47
77
|
|
|
48
78
|
// hands out one number per app.route(), so the routes it creates know they belong together
|
|
49
79
|
let routeGroups = 0;
|
|
50
80
|
|
|
51
|
-
// The settings the request and response hot paths read, resolved to plain fields: each read was a
|
|
52
|
-
// variadic get() whose rest array escapes into createRoute, plus a dictionary miss per mount level
|
|
53
|
-
// for the json keys, which have no default. One shape for every router, stale when the epoch moves.
|
|
54
|
-
class HotSettings {
|
|
55
|
-
/** Every field declared up front, one hidden class for every router's copy. */
|
|
56
|
-
constructor() {
|
|
57
|
-
this.epoch = 0;
|
|
58
|
-
this.xPoweredBy = false;
|
|
59
|
-
this.etagFn = undefined;
|
|
60
|
-
// null means every method, which is express's behaviour and the default
|
|
61
|
-
this.etagMethods = null;
|
|
62
|
-
this.queryParserFn = undefined;
|
|
63
|
-
this.trustProxyFn = undefined;
|
|
64
|
-
this.trustProxyProtocol = false;
|
|
65
|
-
this.jsonEscape = undefined;
|
|
66
|
-
this.jsonReplacer = undefined;
|
|
67
|
-
this.jsonSpaces = undefined;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Whether an earlier route would have answered this path had case not mattered. A guard is a
|
|
73
|
-
* folded string when the earlier path is a literal, and an insensitive pattern when it has
|
|
74
|
-
* parameters of its own.
|
|
75
|
-
*
|
|
76
|
-
* The string side is compared character by character rather than through toLowerCase, because it
|
|
77
|
-
* sits on the hot path of every parameter route that has an earlier literal, and saying no must
|
|
78
|
-
* allocate nothing.
|
|
79
|
-
*
|
|
80
|
-
* @param {(string|RegExp)[]} guards
|
|
81
|
-
* @param {string} path the path as it arrived
|
|
82
|
-
* @returns {boolean}
|
|
83
|
-
*/
|
|
84
|
-
function anyGuardHits(guards, path) {
|
|
85
|
-
for (let i = 0; i < guards.length; i++) {
|
|
86
|
-
const guard = guards[i];
|
|
87
|
-
if (typeof guard !== "string") {
|
|
88
|
-
if (guard.test(path)) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
// the guard is a registered path, which under the default routing answers the same path
|
|
94
|
-
// with one trailing slash as well: "/x1" registered is what serves "/x1/", so "/X1/" is
|
|
95
|
-
// just as much a case variant of it as "/X1" is. Missing that answered "/X1/" from the
|
|
96
|
-
// parameter route behind it while express answered from the literal.
|
|
97
|
-
//
|
|
98
|
-
// The regex guards, for earlier paths that carry parameters of their own, are built
|
|
99
|
-
// non-strict and already accept it. Erring wide costs nothing here either: a guard that
|
|
100
|
-
// hits only hands the request to the generic router, which is where express's own order
|
|
101
|
-
// decides anyway
|
|
102
|
-
const slashed = path.length === guard.length + 1 && path.charCodeAt(guard.length) === 0x2f;
|
|
103
|
-
if (guard.length !== path.length && !slashed) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
let same = true;
|
|
107
|
-
for (let j = 0; j < guard.length; j++) {
|
|
108
|
-
let code = path.charCodeAt(j);
|
|
109
|
-
// A to Z only, which is the fold express's insensitive routing does
|
|
110
|
-
if (code >= 65 && code <= 90) {
|
|
111
|
-
code += 32;
|
|
112
|
-
}
|
|
113
|
-
if (code !== guard.charCodeAt(j)) {
|
|
114
|
-
same = false;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (same) {
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// every method the declarative compiler can emit: a patched one must disable compilation, or the
|
|
126
|
-
// patch would be honoured everywhere but on compiled routes
|
|
127
|
-
const resCodes = {},
|
|
128
|
-
resDecMethods = ["set", "setHeader", "header", "send", "end", "append", "status", "json", "sendStatus"];
|
|
129
|
-
for (const method of resDecMethods) {
|
|
130
|
-
resCodes[method] = Response.prototype[method].toString();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
81
|
let routeKey = 0;
|
|
134
82
|
|
|
135
|
-
/**
|
|
136
|
-
* One walk of one router's routes, for one request.
|
|
137
|
-
*
|
|
138
|
-
* next() is made once here instead of once per hop. As a closure per hop it captured eleven
|
|
139
|
-
* bindings, one of them mutable, which is a context on the heap every time a middleware hands over.
|
|
140
|
-
* The hop's own state is three fields on this instead.
|
|
141
|
-
*
|
|
142
|
-
* A nested router gets its own walk, through its own _routeRequest, so req.next belongs to whoever
|
|
143
|
-
* is running the request at that moment.
|
|
144
|
-
*/
|
|
145
|
-
/**
|
|
146
|
-
* The layer Express makes for one mounted handler. `name` is what a caller matches on: a function's
|
|
147
|
-
* own name, "router" for a mounted router, and "<anonymous>" for the rest, exactly as express reads
|
|
148
|
-
* them off the handle.
|
|
149
|
-
*
|
|
150
|
-
* @param {any} route
|
|
151
|
-
* @param {any} callback
|
|
152
|
-
* @returns {any}
|
|
153
|
-
*/
|
|
154
|
-
function layerFor(route, callback) {
|
|
155
|
-
const layer = {
|
|
156
|
-
handle: callback,
|
|
157
|
-
// express reads the name off the handle, and its own handles are named: a mounted
|
|
158
|
-
// application is "app" and a mounted router "router", whatever this project happens
|
|
159
|
-
// to call the function underneath
|
|
160
|
-
name: Array.isArray(callback._routes)
|
|
161
|
-
? callback._isApplication
|
|
162
|
-
? "app"
|
|
163
|
-
: "router"
|
|
164
|
-
: callback.name || "<anonymous>",
|
|
165
|
-
params: undefined,
|
|
166
|
-
path: undefined,
|
|
167
|
-
keys: [],
|
|
168
|
-
route: undefined
|
|
169
|
-
};
|
|
170
|
-
route._layers.set(callback, layer);
|
|
171
|
-
return layer;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* The layer Express makes for a route, whose handle runs the route's own handlers one after
|
|
176
|
-
* another. Express calls that handle `handle`, and a caller that looks for a route layer looks for
|
|
177
|
-
* that name.
|
|
178
|
-
*
|
|
179
|
-
* @param {any} route
|
|
180
|
-
* @returns {any}
|
|
181
|
-
*/
|
|
182
|
-
function routeLayer(route) {
|
|
183
|
-
const handle = function handle(req, res, next) {
|
|
184
|
-
let index = 0;
|
|
185
|
-
const step = (err) => {
|
|
186
|
-
const callback = route.callbacks[index++];
|
|
187
|
-
if (callback === undefined) {
|
|
188
|
-
return next(err);
|
|
189
|
-
}
|
|
190
|
-
const isErrorHandler = callback.length === 4;
|
|
191
|
-
if ((err === undefined || err === null) === isErrorHandler) {
|
|
192
|
-
return step(err);
|
|
193
|
-
}
|
|
194
|
-
try {
|
|
195
|
-
return isErrorHandler ? callback(err, req, res, step) : callback(req, res, step);
|
|
196
|
-
} catch (thrown) {
|
|
197
|
-
return step(thrown);
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
step();
|
|
201
|
-
};
|
|
202
|
-
return { handle, name: "handle", params: undefined, path: undefined, keys: [], route: route.exposed };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
class Walk {
|
|
206
|
-
/**
|
|
207
|
-
* @param {any} router
|
|
208
|
-
* @param {any} req
|
|
209
|
-
* @param {any} res
|
|
210
|
-
* @param {any[]} routes
|
|
211
|
-
* @param {boolean} skipCheck take the route at the index without matching it, which is how an
|
|
212
|
-
* already-decided chain is walked
|
|
213
|
-
* @param {any} skipUntil route to resume after when this chain runs out, or undefined
|
|
214
|
-
* @param {(value: any) => void} resolve
|
|
215
|
-
* @param {(err: any) => void} reject
|
|
216
|
-
*/
|
|
217
|
-
constructor(router, req, res, routes, skipCheck, skipUntil, resolve, reject) {
|
|
218
|
-
this.router = router;
|
|
219
|
-
this.req = req;
|
|
220
|
-
this.res = res;
|
|
221
|
-
this.routes = routes;
|
|
222
|
-
this.skipCheck = skipCheck;
|
|
223
|
-
this.skipUntil = skipUntil;
|
|
224
|
-
this.resolve = resolve;
|
|
225
|
-
this.reject = reject;
|
|
226
|
-
// read only by the native pair below, which has no promise to settle once for it; the
|
|
227
|
-
// promise path leaves it false. Initialized here to keep every walk the same shape
|
|
228
|
-
this.settled = false;
|
|
229
|
-
this.routeIndex = 0;
|
|
230
|
-
this.route = null;
|
|
231
|
-
this.callbackIndex = 0;
|
|
232
|
-
// bound, not wrapped in an arrow: an arrow forwarding into step() is one more call on every
|
|
233
|
-
// hop, and it measured 495 microseconds per thousand requests of nothing else
|
|
234
|
-
this.next = this.step.bind(this);
|
|
235
|
-
// What res.sendFile reports a failure to. Express hands it req.next, which is the router
|
|
236
|
-
// next and not the route one, so a file that cannot be served leaves the route and its
|
|
237
|
-
// error reaches the router error handlers rather than a four argument handler written
|
|
238
|
-
// inside the route. req.next itself is left alone: making it mean this everywhere is what
|
|
239
|
-
// express does, and it breaks express own res.format and app.routes.error tests here, so
|
|
240
|
-
// that stays open rather than half done.
|
|
241
|
-
//
|
|
242
|
-
// Null here and bound on the first route that has more than one callback, which is the
|
|
243
|
-
// only shape that ever reads it: a request that never meets one paid a bind for nothing
|
|
244
|
-
this.leaveRoute = null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Leaves the rest of this route, with the error if there is one, and carries on with the route
|
|
249
|
-
* after it.
|
|
250
|
-
*
|
|
251
|
-
* @param {any} [err]
|
|
252
|
-
*/
|
|
253
|
-
stepOutOfRoute(err) {
|
|
254
|
-
if (err) {
|
|
255
|
-
const req = this.req;
|
|
256
|
-
req._error = err;
|
|
257
|
-
req._errorKey = this.route.routeKey;
|
|
258
|
-
req._errorGroup = this.route.group;
|
|
259
|
-
}
|
|
260
|
-
this.step("route");
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Finds the next route that matches and runs it. next() comes back here for the route after, so
|
|
265
|
-
* a chain of N middlewares costs one promise instead of N nested ones.
|
|
266
|
-
*
|
|
267
|
-
* @param {number} startIndex where to resume the scan
|
|
268
|
-
*/
|
|
269
|
-
dispatch(startIndex) {
|
|
270
|
-
const req = this.req;
|
|
271
|
-
const routes = this.routes;
|
|
272
|
-
const router = this.router;
|
|
273
|
-
// a middleware assigned req.url, which express honours: the rest of the walk matches the
|
|
274
|
-
// new path. One identity compare per hop, since the router writes both sides itself; the
|
|
275
|
-
// handling lives out of line so this function stays small enough to inline
|
|
276
|
-
if (req.url !== req._lastUrl && this.takeUrlRewrite(startIndex)) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
// and the same for req.method, which method-override assigns: the compiled chain was
|
|
280
|
-
// picked by the verb the request arrived with, so it no longer stands for this one
|
|
281
|
-
if (req.method !== req._lastMethod && this.takeMethodRewrite(startIndex)) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
let routeIndex = startIndex;
|
|
285
|
-
// a compiled chain runs what is in it without matching again, so this is where a layer that
|
|
286
|
-
// provably has nothing to do for this request is stepped over rather than entered
|
|
287
|
-
if (this.skipCheck) {
|
|
288
|
-
while (routeIndex < routes.length && routes[routeIndex].bodyParserOnly === true) {
|
|
289
|
-
if (!stepsOver(routes[routeIndex], req)) {
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
routeIndex++;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!this.skipCheck) {
|
|
296
|
-
// express matches a layer's path before it looks at the method, and decodes the
|
|
297
|
-
// parameters there, so a malformed escape answers 400 even when no route of this
|
|
298
|
-
// method exists. Only a path carrying a percent can produce one, and that check keeps
|
|
299
|
-
// every other request from matching routes it could never run. Scanned once per
|
|
300
|
-
// rewrite and kept on the request: a middleware-heavy chain scanned it per hop
|
|
301
|
-
const mayFailDecode = (req._mayFailDecode ??= req._originalPath.indexOf("%") !== -1);
|
|
302
|
-
// frozen here, once per scan: _pathMatches reads the two flags as bare fields, and
|
|
303
|
-
// calling this per route measured 0.45us of a scan of four hundred
|
|
304
|
-
router._freezeRoutingFlags();
|
|
305
|
-
if (routes === router._routes) {
|
|
306
|
-
// the router's own table has an index over its literal routes, so the scan visits
|
|
307
|
-
// the handful that could match instead of every one, see _scanFrom
|
|
308
|
-
routeIndex = router._scanFrom(req, routeIndex, mayFailDecode);
|
|
309
|
-
} else {
|
|
310
|
-
// a compiled chain's own array, always short: the linear scan stays.
|
|
311
|
-
// Written out rather than through a predicate handed to findIndexStartingFrom,
|
|
312
|
-
// which was one closure per hop of every request not on a compiled chain
|
|
313
|
-
const method = req.method;
|
|
314
|
-
const length = routes.length;
|
|
315
|
-
for (; routeIndex < length; routeIndex++) {
|
|
316
|
-
const r = routes[routeIndex];
|
|
317
|
-
// A HEAD request enters a route whose path matched even when its verb cannot
|
|
318
|
-
// serve one: express exempts HEAD from the method check ("if (!hasMethod &&
|
|
319
|
-
// method !== 'HEAD')" in router/index.js), so the layer's parameters are
|
|
320
|
-
// captured and its param() callbacks run before the route is dropped. Only
|
|
321
|
-
// asked when the router has callbacks to run, since entering a route to step
|
|
322
|
-
// straight back out of it is otherwise pure cost. runRoute steps over it.
|
|
323
|
-
if (!(
|
|
324
|
-
r.all ||
|
|
325
|
-
r.method === method ||
|
|
326
|
-
req._isOptions ||
|
|
327
|
-
(req._isHead && (r.gettable || r.paramCallbacks.size > 0))
|
|
328
|
-
)) {
|
|
329
|
-
// taken only to fail: _preprocessRequest decodes again and turns it into
|
|
330
|
-
// the error, so the handlers of a route this request cannot run never see it
|
|
331
|
-
if (mayFailDecode && router._pathMatches(r, req) && router._paramsFailToDecode(r, req)) {
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (router._pathMatches(r, req)) {
|
|
337
|
-
// matched, and then stepped over: a body parser this request gets nothing
|
|
338
|
-
// out of costs a hop and answers with next() at the end of it
|
|
339
|
-
if (r.bodyParserOnly === true && stepsOver(r, req)) {
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const route = routes[routeIndex];
|
|
348
|
-
if (!route) {
|
|
349
|
-
if (!this.skipCheck) {
|
|
350
|
-
// on normal unoptimized routes, if theres no match then there is no route
|
|
351
|
-
return this.resolve(false);
|
|
352
|
-
}
|
|
353
|
-
// the chain ran out, so ordinary routing takes over from the top and skips what has
|
|
354
|
-
// already run
|
|
355
|
-
useApp(req, router);
|
|
356
|
-
// a chain that went into a mount never left it, since keepMount stops the pop, so the
|
|
357
|
-
// path is still relative to it. /alone/skip must not be offered to the app as /skip
|
|
358
|
-
if (req._stack !== null && req._stack.length > 0) {
|
|
359
|
-
req._stack.length = 0;
|
|
360
|
-
req._consumed = 0;
|
|
361
|
-
setMountedPath(req);
|
|
362
|
-
}
|
|
363
|
-
// an error out of a mount is attributed to the mount, so error handlers declared before
|
|
364
|
-
// it do not catch it, as in ordinary dispatch
|
|
365
|
-
if (req._error && this.skipUntil && this.skipUntil.keepMount && this.skipUntil.routeKey > req._errorKey) {
|
|
366
|
-
req._errorKey = this.skipUntil.routeKey;
|
|
367
|
-
req._errorGroup = this.skipUntil.group;
|
|
368
|
-
}
|
|
369
|
-
this.routes = router._routes;
|
|
370
|
-
this.skipCheck = false;
|
|
371
|
-
return this.dispatch(0);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
this.routeIndex = routeIndex;
|
|
375
|
-
this.route = route;
|
|
376
|
-
this.callbackIndex = 0;
|
|
377
|
-
|
|
378
|
-
// _preprocessRequest returns a promise only when param callbacks will really run, so the
|
|
379
|
-
// common case stays synchronous even in an app that uses app.param. A microtask every 300
|
|
380
|
-
// routes resets the stack, which a long chain would otherwise blow
|
|
381
|
-
const continueRoute = router._preprocessRequest(req, this.res, route);
|
|
382
|
-
if (continueRoute instanceof Promise || req.routeCount % 300 === 0) {
|
|
383
|
-
// .catch and not a rejection argument: a throw inside runRoute itself must reject
|
|
384
|
-
// the walk instead of becoming an unhandled rejection
|
|
385
|
-
Promise.resolve(continueRoute)
|
|
386
|
-
.then((resumed) => this.runRoute(resumed))
|
|
387
|
-
// wrapped so the native pair keeps the walk as receiver; a promise's reject
|
|
388
|
-
// would not have cared
|
|
389
|
-
.catch((err) => this.reject(err));
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
return this.runRoute(continueRoute);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Takes over a req.url a middleware assigned. On an ordinary walk the scan simply continues
|
|
397
|
-
* against the new path; a compiled chain was computed for the old one, so ordinary routing
|
|
398
|
-
* takes over, skipping only what has already run.
|
|
399
|
-
*
|
|
400
|
-
* @param {number} startIndex where dispatch was about to resume
|
|
401
|
-
* @returns {boolean} whether this rerouted the walk itself
|
|
402
|
-
*/
|
|
403
|
-
takeUrlRewrite(startIndex) {
|
|
404
|
-
const req = this.req;
|
|
405
|
-
const router = this.router;
|
|
406
|
-
req._absorbUrlRewrite();
|
|
407
|
-
if (!this.skipCheck) {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
this.skipUntil = startIndex > 0 ? this.routes[startIndex - 1] : undefined;
|
|
411
|
-
if (req._stack !== null && req._stack.length > 0) {
|
|
412
|
-
req._stack.length = 0;
|
|
413
|
-
req._consumed = 0;
|
|
414
|
-
setMountedPath(req);
|
|
415
|
-
}
|
|
416
|
-
this.routes = router._routes;
|
|
417
|
-
this.skipCheck = false;
|
|
418
|
-
this.dispatch(0);
|
|
419
|
-
return true;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Takes over a req.method a middleware assigned, which method-override is written to do. The
|
|
424
|
-
* ordinary scan reads req.method per route and is right from the next hop on; a compiled chain
|
|
425
|
-
* was chosen by the method µWS dispatched on, so ordinary routing takes over from the top the
|
|
426
|
-
* way a url rewrite does.
|
|
427
|
-
*
|
|
428
|
-
* @param {number} startIndex where dispatch was about to resume
|
|
429
|
-
* @returns {boolean} whether this rerouted the walk itself
|
|
430
|
-
*/
|
|
431
|
-
takeMethodRewrite(startIndex) {
|
|
432
|
-
const req = this.req;
|
|
433
|
-
const router = this.router;
|
|
434
|
-
req._absorbMethodRewrite();
|
|
435
|
-
if (!this.skipCheck) {
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
this.skipUntil = startIndex > 0 ? this.routes[startIndex - 1] : undefined;
|
|
439
|
-
if (req._stack !== null && req._stack.length > 0) {
|
|
440
|
-
req._stack.length = 0;
|
|
441
|
-
req._consumed = 0;
|
|
442
|
-
setMountedPath(req);
|
|
443
|
-
}
|
|
444
|
-
this.routes = router._routes;
|
|
445
|
-
this.skipCheck = false;
|
|
446
|
-
this.dispatch(0);
|
|
447
|
-
return true;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Enters the route the walk is on: a mount adjusts req.url, req.path and the mount stack on the
|
|
452
|
-
* way in, and then the route's callbacks run one after another through next().
|
|
453
|
-
*
|
|
454
|
-
* @param {any} continueRoute what _preprocessRequest decided: true to run, "route" to skip
|
|
455
|
-
*/
|
|
456
|
-
runRoute(continueRoute) {
|
|
457
|
-
const req = this.req;
|
|
458
|
-
const route = this.route;
|
|
459
|
-
// A compiled chain walks into a mount rather than entering it, so the rule above needs
|
|
460
|
-
// saying here as well: everything after this marker is inside the mount, and a mount is
|
|
461
|
-
// stepped over while an error is in flight. Leaving the chain is what running out of it
|
|
462
|
-
// already means, and ordinary routing takes over after the mount.
|
|
463
|
-
if (route.keepMount === true && req._error) {
|
|
464
|
-
return this.dispatch(this.routes.length);
|
|
465
|
-
}
|
|
466
|
-
if (route.use) {
|
|
467
|
-
if (route.mountApp) {
|
|
468
|
-
// optimized chain: normal dispatch swaps req.app when it enters a mounted
|
|
469
|
-
// Application, but the compiled mount route has no callback to do it
|
|
470
|
-
rememberApp(this, route, req);
|
|
471
|
-
useApp(req, route.mountApp);
|
|
472
|
-
}
|
|
473
|
-
const taken = mountPrefixLength(route, req);
|
|
474
|
-
// pushed negative when this mount consumes the whole remaining path: express invents
|
|
475
|
-
// the "/" the routes below see, and the pop has to know, see leaveHop and issue #17
|
|
476
|
-
(req._stack ??= []).push(
|
|
477
|
-
taken !== 0 && req._consumed + taken === req._originalPath.length ? -taken : taken
|
|
478
|
-
);
|
|
479
|
-
// a use with no path consumes nothing, so everything below would work out the values
|
|
480
|
-
// that are already there. Only skipped without a trailing slash, where the rules about
|
|
481
|
-
// one cannot bite. An application is mostly pathless middleware, and this is per hop
|
|
482
|
-
if (taken !== 0 || req.endsWithSlash) {
|
|
483
|
-
req._consumed += taken;
|
|
484
|
-
// a mount that took a trailing slash: req.baseUrl then has to join the pieces
|
|
485
|
-
// rather than slice the path, which is the slower half of its getter
|
|
486
|
-
if (taken !== 0 && req._originalPath.charCodeAt(req._consumed - 1) === 0x2f) {
|
|
487
|
-
req._mountSlash = true;
|
|
488
|
-
}
|
|
489
|
-
setMountedPath(req);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
req.next = this.next;
|
|
493
|
-
// the same step when the route has one callback, and then it has to be the same object:
|
|
494
|
-
// express hands res.format's handlers the next its own layer received, and its test asserts
|
|
495
|
-
// that identity. With more than one callback the two differ for real, and what express
|
|
496
|
-
// hands over is the one that leaves the route
|
|
497
|
-
req._leaveRoute = route.callbacks.length > 1 ? (this.leaveRoute ??= this.stepOutOfRoute.bind(this)) : this.next;
|
|
498
|
-
if (continueRoute === "route") {
|
|
499
|
-
this.step("route");
|
|
500
|
-
} else if (continueRoute) {
|
|
501
|
-
this.step(undefined);
|
|
502
|
-
} else {
|
|
503
|
-
this.resolve(true);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* A hop while the request carries an error, or over an error handler it cannot run: the
|
|
509
|
-
* handler is invoked when the error is its to catch, everything else is skipped.
|
|
510
|
-
*
|
|
511
|
-
* @param {number} kind what the callback is, one of the CALLBACK_ constants
|
|
512
|
-
* @param {Function} callback
|
|
513
|
-
*/
|
|
514
|
-
errorHop(kind, callback) {
|
|
515
|
-
const req = this.req;
|
|
516
|
-
const route = this.route;
|
|
517
|
-
// A four argument handler written inside a route only ever sees what that route raised:
|
|
518
|
-
// express skips a route layer entirely while an error is in flight, so an error from a
|
|
519
|
-
// middleware before it, or out of a mount, walks past to the router's own error handlers.
|
|
520
|
-
// Middleware error handlers keep the ordinary rule, which is that they catch what was
|
|
521
|
-
// raised before them.
|
|
522
|
-
const reachable = route.use
|
|
523
|
-
? route.routeKey >= req._errorKey
|
|
524
|
-
: route.routeKey === req._errorKey || (route.group !== undefined && route.group === req._errorGroup);
|
|
525
|
-
if (req._error && kind === CALLBACK_ERROR && reachable) {
|
|
526
|
-
const out = this.router._handleError(req._error, callback, req, this.res);
|
|
527
|
-
if (out instanceof Promise) {
|
|
528
|
-
// an error handler's rejected promise moves on to the next error handler, and
|
|
529
|
-
// a bare rejection gets the error express invents for it
|
|
530
|
-
out.catch((err) => {
|
|
531
|
-
req._error = err || new Error("Rejected promise");
|
|
532
|
-
req._errorKey = route.routeKey;
|
|
533
|
-
req._errorGroup = route.group;
|
|
534
|
-
return this.step(undefined);
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
return this.step(undefined);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Leaves the route the walk is on: the mount pop, the router hand-back, and the hop to the
|
|
544
|
-
* route after. Out of step so the commonest hop, callbacks exhausted by a plain next(), goes
|
|
545
|
-
* here without re-running step's prologue and compares.
|
|
546
|
-
*
|
|
547
|
-
* @param {boolean} isRouter next("router") rather than next("route")
|
|
548
|
-
*/
|
|
549
|
-
leaveHop(isRouter) {
|
|
550
|
-
const req = this.req;
|
|
551
|
-
const route = this.route;
|
|
552
|
-
if (route.use && !route.keepMount) {
|
|
553
|
-
const pushed = req._stack.pop();
|
|
554
|
-
const taken = pushed < 0 ? -pushed : pushed;
|
|
555
|
-
// a rewrite done inside this middleware is taken now: the pop below recomputes
|
|
556
|
-
// req.url from the original path and would silently revert it. The slashAdded
|
|
557
|
-
// mangle belongs to the mount that consumed a prefix, not to a pathless use
|
|
558
|
-
if (req.url !== req._lastUrl) {
|
|
559
|
-
req._absorbUrlRewrite(taken !== 0);
|
|
560
|
-
req._consumed -= taken;
|
|
561
|
-
setMountedPath(req);
|
|
562
|
-
} else {
|
|
563
|
-
if (pushed < 0 && req._originalPath.length > req._consumed) {
|
|
564
|
-
// a rewrite below this mount left a remainder where entry had none: express
|
|
565
|
-
// strips the first character of it when it rejoins, see issue #17
|
|
566
|
-
req._originalPath =
|
|
567
|
-
req._originalPath.slice(0, req._consumed) + req._originalPath.slice(req._consumed + 1);
|
|
568
|
-
req._mayFailDecode = null;
|
|
569
|
-
}
|
|
570
|
-
if (taken !== 0) {
|
|
571
|
-
// a pathless use consumed nothing and rewrote nothing, so the recompute would
|
|
572
|
-
// write back the very values it reads
|
|
573
|
-
req._consumed -= taken;
|
|
574
|
-
setMountedPath(req);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
restoreApp(route, req);
|
|
578
|
-
}
|
|
579
|
-
if (isRouter) {
|
|
580
|
-
if (this.skipCheck) {
|
|
581
|
-
// on a compiled chain, leaving the router is what running out of chain
|
|
582
|
-
// already means: ordinary routing takes over after the mount. With no
|
|
583
|
-
// mount in the chain the router being left is the app's own, and nothing
|
|
584
|
-
// of it may run afterwards, not even a middleware registered later
|
|
585
|
-
if (this.skipUntil?.keepMount) {
|
|
586
|
-
return this.dispatch(this.routes.length);
|
|
587
|
-
}
|
|
588
|
-
return this.resolve(false);
|
|
589
|
-
}
|
|
590
|
-
// out of this router entirely, so whoever mounted it carries on after the
|
|
591
|
-
// mount. The app's own walk has nobody after it, and answers 404
|
|
592
|
-
return this.resolve(false);
|
|
593
|
-
}
|
|
594
|
-
req.routeCount++;
|
|
595
|
-
// dispatch is a plain call, so a synchronous throw would escape here instead of
|
|
596
|
-
// rejecting, as it used to when this recursed through the async _routeRequest
|
|
597
|
-
try {
|
|
598
|
-
return this.dispatch(this.routeIndex + 1);
|
|
599
|
-
} catch (err) {
|
|
600
|
-
return this.reject(err);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* One hop, which is what next() does: with nothing, run the route's next callback; with "route",
|
|
606
|
-
* leave the route; with anything else, remember it as the error and carry on.
|
|
607
|
-
*
|
|
608
|
-
* @param {any} thingamabob
|
|
609
|
-
*/
|
|
610
|
-
step(thingamabob) {
|
|
611
|
-
const req = this.req;
|
|
612
|
-
const res = this.res;
|
|
613
|
-
const route = this.route;
|
|
614
|
-
const router = this.router;
|
|
615
|
-
if (thingamabob) {
|
|
616
|
-
if (thingamabob === "route" || thingamabob === "router") {
|
|
617
|
-
return this.leaveHop(thingamabob === "router");
|
|
618
|
-
} else {
|
|
619
|
-
req._error = thingamabob;
|
|
620
|
-
req._errorKey = route.routeKey;
|
|
621
|
-
req._errorGroup = route.group;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const kind = route.callbackKinds[this.callbackIndex];
|
|
625
|
-
const callback = route.callbacks[this.callbackIndex++];
|
|
626
|
-
if (!callback) {
|
|
627
|
-
return this.leaveHop(false);
|
|
628
|
-
}
|
|
629
|
-
// skipping routes we already went through via optimized path. Before the Router branch
|
|
630
|
-
// below and not after it: a mount whose chain was compiled has already run, and running it
|
|
631
|
-
// again would answer from inside the router a request that had just left it
|
|
632
|
-
if (!this.skipCheck && this.skipUntil && this.skipUntil.routeKey >= route.routeKey) {
|
|
633
|
-
return this.step(undefined);
|
|
634
|
-
}
|
|
635
|
-
// A mounted router or application is stepped over while an error is in flight. Its handle
|
|
636
|
-
// takes three arguments, so express's Layer#handleError hands the error straight on without
|
|
637
|
-
// entering it: what a mount catches is what it raised itself. Entering it ran the error
|
|
638
|
-
// handlers written inside the mount, and left req.app pointing at a mounted application,
|
|
639
|
-
// whose settings then answered. A 500 carried an ETag under app.set("etag", false).
|
|
640
|
-
if (kind === CALLBACK_ROUTER && !req._error) {
|
|
641
|
-
if (callback._isApplication) {
|
|
642
|
-
rememberApp(this, route, req);
|
|
643
|
-
useApp(req, callback);
|
|
644
|
-
}
|
|
645
|
-
const pushedParams = callback._settings.mergeParams;
|
|
646
|
-
if (pushedParams) {
|
|
647
|
-
(req._paramStack ??= []).push(req.params);
|
|
648
|
-
}
|
|
649
|
-
// express restores req.params when a router hands back, so what runs after the mount
|
|
650
|
-
// sees the params it had before it
|
|
651
|
-
const parentParams = req.params;
|
|
652
|
-
// each router answers OPTIONS with the verbs it knows itself, so the one being entered
|
|
653
|
-
// starts its own list: express keeps that list per router, and a router that hands back
|
|
654
|
-
// without answering leaves the outer one's untouched
|
|
655
|
-
const parentMethods = req._matchedMethods;
|
|
656
|
-
if (parentMethods !== null) {
|
|
657
|
-
req._matchedMethods = new Set();
|
|
658
|
-
}
|
|
659
|
-
callback
|
|
660
|
-
._routeRequest(req, res, 0)
|
|
661
|
-
.then((routed) => {
|
|
662
|
-
// the child's params are scoped to it, and must not leak into the routes after
|
|
663
|
-
if (pushedParams) {
|
|
664
|
-
req._paramStack.pop();
|
|
665
|
-
}
|
|
666
|
-
req.params = parentParams;
|
|
667
|
-
if (req._error) {
|
|
668
|
-
req._errorKey = route.routeKey;
|
|
669
|
-
req._errorGroup = route.group;
|
|
670
|
-
}
|
|
671
|
-
if (routed) {
|
|
672
|
-
if (parentMethods !== null) {
|
|
673
|
-
req._matchedMethods = parentMethods;
|
|
674
|
-
}
|
|
675
|
-
return this.resolve(true);
|
|
676
|
-
}
|
|
677
|
-
const childMethods = req._matchedMethods;
|
|
678
|
-
if (parentMethods !== null) {
|
|
679
|
-
req._matchedMethods = parentMethods;
|
|
680
|
-
}
|
|
681
|
-
if (req._isOptions && childMethods.size && !req._error) {
|
|
682
|
-
// OPTIONS routing is different, it stops in the router if matched.
|
|
683
|
-
// Express answers as the router hands back, so a throw while answering,
|
|
684
|
-
// a head already written being the way, walks on to later error handlers
|
|
685
|
-
try {
|
|
686
|
-
router._sendOptionsReply(req, res, childMethods);
|
|
687
|
-
return this.resolve(true);
|
|
688
|
-
} catch (err) {
|
|
689
|
-
return this.step(err);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
// An error carried out of the mount is not answered by the automatic reply, and
|
|
693
|
-
// stopping here handed it to the default page: express walks on to the error
|
|
694
|
-
// handlers written after the mount, for OPTIONS as for any other method.
|
|
695
|
-
this.step(undefined);
|
|
696
|
-
})
|
|
697
|
-
// a rejection out of the nested walk, or a throw above, must reject this one
|
|
698
|
-
// instead of dying as an unhandled rejection; wrapped for the native pair's
|
|
699
|
-
// receiver
|
|
700
|
-
.catch((err) => this.reject(err));
|
|
701
|
-
} else {
|
|
702
|
-
// errors and error handlers live out of line: this is the cold path, and its size
|
|
703
|
-
// was pushing step past the inlining threshold
|
|
704
|
-
if (req._error || kind === CALLBACK_ERROR) {
|
|
705
|
-
return this.errorHop(kind, callback);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
try {
|
|
709
|
-
// handling OPTIONS method
|
|
710
|
-
if (req._isOptions && !route.all && route.method !== "OPTIONS") {
|
|
711
|
-
req._matchedMethods.add(route.method);
|
|
712
|
-
if (route.gettable) {
|
|
713
|
-
req._matchedMethods.add("HEAD");
|
|
714
|
-
}
|
|
715
|
-
return this.step(undefined);
|
|
716
|
-
}
|
|
717
|
-
// entered only so its param callbacks could run, see the scan in dispatch: the verb
|
|
718
|
-
// cannot serve a HEAD, so nothing here answers it
|
|
719
|
-
if (req._isHead && !route.all && !route.gettable && route.method !== "HEAD") {
|
|
720
|
-
return this.step(undefined);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const out = callback(req, res, this.next);
|
|
724
|
-
if (out instanceof Promise) {
|
|
725
|
-
// Express 5 forwards a rejected handler promise to the error middleware on its
|
|
726
|
-
// own, so there is nothing left for the "catch async errors" setting or for
|
|
727
|
-
// express-async-errors to opt into. A bare rejection carries no error, and
|
|
728
|
-
// express invents this one for it
|
|
729
|
-
out.catch((err) => {
|
|
730
|
-
req._error = err || new Error("Rejected promise");
|
|
731
|
-
req._errorKey = route.routeKey;
|
|
732
|
-
req._errorGroup = route.group;
|
|
733
|
-
return this.step(undefined);
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
} catch (err) {
|
|
737
|
-
req._error = err;
|
|
738
|
-
req._errorKey = route.routeKey;
|
|
739
|
-
req._errorGroup = route.group;
|
|
740
|
-
return this.step(undefined);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* The native handler's resolve, invoked as this.resolve(matched) with the walk as receiver. The
|
|
748
|
-
* promise pair _routeRequest allocates exists for callers that await; the uWS handler never did,
|
|
749
|
-
* and on the common path, where the handler answers and next() is never called, that promise
|
|
750
|
-
* never even settled: an async frame and two promises of floating garbage per request.
|
|
751
|
-
*
|
|
752
|
-
* The 404 epilogue stays on a microtask, exactly where the await used to resume: a middleware
|
|
753
|
-
* that writes after calling next() must still win the headersSent check, as it does in express.
|
|
754
|
-
* @this {Walk}
|
|
755
|
-
*/
|
|
756
|
-
function nativeDone(matched) {
|
|
757
|
-
if (this.settled) {
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
this.settled = true;
|
|
761
|
-
if (!matched) {
|
|
762
|
-
queueMicrotask(() => {
|
|
763
|
-
const response = this.res;
|
|
764
|
-
if (response.headersSent || response.aborted) {
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
try {
|
|
768
|
-
this.router._endUnmatched(this.req, response);
|
|
769
|
-
} catch (err) {
|
|
770
|
-
if (response.aborted || response.finished) {
|
|
771
|
-
logError(this.router, err);
|
|
772
|
-
} else {
|
|
773
|
-
this.router._handleError(err, null, this.req, response);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/**
|
|
781
|
-
* The native handler's reject: answers 500 as express's final handler would, instead of dying as
|
|
782
|
-
* an unhandled rejection. Deferred like the resolve, since every rejection used to reach the
|
|
783
|
-
* handler's catch through an await.
|
|
784
|
-
* @this {Walk}
|
|
785
|
-
*/
|
|
786
|
-
function nativeFail(err) {
|
|
787
|
-
if (this.settled) {
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
this.settled = true;
|
|
791
|
-
queueMicrotask(() => {
|
|
792
|
-
const response = this.res;
|
|
793
|
-
if (response.aborted || response.finished) {
|
|
794
|
-
logError(this.router, err);
|
|
795
|
-
} else {
|
|
796
|
-
this.router._handleError(err, null, this.req, response);
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* How much of the path a mount takes, which is what its own pattern matched and never more than
|
|
803
|
-
* there is. Exec runs on the same fixed-up path _pathMatches tested: a parent mount that consumed
|
|
804
|
-
* everything leaves "", where the pattern was matched against "/".
|
|
805
|
-
*
|
|
806
|
-
* Counting what each mount took, rather than rebuilding one pattern out of the whole stack and
|
|
807
|
-
* matching that against the original path, is the difference between a sum and a guess: a mount
|
|
808
|
-
* written as an optional group composes into a pattern the path no longer satisfies, and the
|
|
809
|
-
* prefix stayed on.
|
|
810
|
-
*
|
|
811
|
-
* @param {any} route
|
|
812
|
-
* @param {any} req
|
|
813
|
-
* @returns {number}
|
|
814
|
-
*/
|
|
815
|
-
function mountPrefixLength(route, req) {
|
|
816
|
-
// a use with no path is EMPTY_REGEX, which matches "" at 0 whatever the path is. Answered
|
|
817
|
-
// without the exec, since this runs per hop and most middleware is pathless
|
|
818
|
-
if (route.pattern === EMPTY_REGEX) {
|
|
819
|
-
return 0;
|
|
820
|
-
}
|
|
821
|
-
// the registration-time constant of a literal mount, exec-free. See createRoute
|
|
822
|
-
if (route.mountLen !== undefined) {
|
|
823
|
-
return route.mountLen;
|
|
824
|
-
}
|
|
825
|
-
if (typeof route.pattern === "string") {
|
|
826
|
-
return route.pattern.length;
|
|
827
|
-
}
|
|
828
|
-
const path = req._opPath;
|
|
829
|
-
const matched = route.pattern.exec(path === "" ? "/" : path);
|
|
830
|
-
return matched ? Math.min(matched[0].length, path.length) : 0;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* Writes the path the routes below a mount see: the original with what the mounts took off the
|
|
835
|
-
* front. The root reads as "/" rather than as nothing, which is how express hands it over.
|
|
836
|
-
*
|
|
837
|
-
* @param {any} req
|
|
838
|
-
*/
|
|
839
|
-
function setMountedPath(req) {
|
|
840
|
-
req._opPath = req._consumed === 0 ? req._originalPath : req._originalPath.slice(req._consumed);
|
|
841
|
-
req._opPathLower = null;
|
|
842
|
-
req.url = req._opPath === "" ? "/" + req.urlQuery : req._opPath + req.urlQuery;
|
|
843
|
-
req._path = req._opPath === "" ? "/" : req._opPath;
|
|
844
|
-
req._lastUrl = req.url;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// req.path as the request class declares it, taken off the prototype rather than written out a
|
|
848
|
-
// second time. A request the router adopts is a plain object and gets it defined on itself, see
|
|
849
|
-
// adoptPlainRequest. Enumerable, as express's own is.
|
|
850
|
-
const PATH_PROPERTY = {
|
|
851
|
-
.../** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(Request.prototype, "path")),
|
|
852
|
-
enumerable: true
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
// and the two the walk calls when a middleware rewrote req.url or req.method, for the same reason:
|
|
856
|
-
// an adopted request has no prototype of ours to find them on, and a rewrite through one of those
|
|
857
|
-
// routers threw instead of being taken over
|
|
858
|
-
const ABSORB_URL = Request.prototype._absorbUrlRewrite;
|
|
859
|
-
const ABSORB_METHOD = Request.prototype._absorbMethodRewrite;
|
|
860
|
-
|
|
861
|
-
const NO_PARAM_NAMES = [];
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* The parameter names a route captures with its own pattern.
|
|
865
|
-
*
|
|
866
|
-
* This is the set express runs param callbacks for. A name that reached req.params from a mount
|
|
867
|
-
* above, through mergeParams, belongs to that mount's router and not to this one, and express does
|
|
868
|
-
* not call this router's param() for it: it walks the keys the layer itself matched. Reading
|
|
869
|
-
* req.params instead ran a callback for every inherited name too, which is visible whenever such a
|
|
870
|
-
* callback does anything, and turned a 200 into a 500 when one of them refused the value.
|
|
871
|
-
*
|
|
872
|
-
* Worked out once per route and kept, since it follows from the pattern and never changes.
|
|
873
|
-
*
|
|
874
|
-
* @param {any} route
|
|
875
|
-
* @returns {string[]}
|
|
876
|
-
*/
|
|
877
|
-
function ownParamNames(route) {
|
|
878
|
-
let names = route._ownParamNames;
|
|
879
|
-
if (names !== undefined) {
|
|
880
|
-
return names;
|
|
881
|
-
}
|
|
882
|
-
if (route.optimizedParams) {
|
|
883
|
-
// µWS matched the pattern and hands the values back by position, under these names
|
|
884
|
-
names = route.optimizedParams;
|
|
885
|
-
} else if (route.pattern instanceof RegExp) {
|
|
886
|
-
const meta = getPatternMeta(route.pattern);
|
|
887
|
-
// outputNames is what _extractParams writes into params; a RegExp the application wrote
|
|
888
|
-
// itself was never compiled here, so its capture groups are the names
|
|
889
|
-
names = meta ? meta.outputNames : regexpGroupKeys(route.pattern);
|
|
890
|
-
} else {
|
|
891
|
-
names = NO_PARAM_NAMES;
|
|
892
|
-
}
|
|
893
|
-
route._ownParamNames = names;
|
|
894
|
-
return names;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
/**
|
|
898
|
-
* Whether this route reads the parameters of the mounts above it, which is its own router asking
|
|
899
|
-
* for them. The stack holds what a mergeParams router captured on the way in, and a plain router
|
|
900
|
-
* mounted inside one must not read it: express asks each router in turn, not the outermost.
|
|
901
|
-
*
|
|
902
|
-
* @param {any} route
|
|
903
|
-
* @param {any} fallback the router dispatching, when the route names no owner
|
|
904
|
-
* @returns {boolean}
|
|
905
|
-
*/
|
|
906
|
-
function mergesParams(route, fallback) {
|
|
907
|
-
const owner = route.owner ?? fallback;
|
|
908
|
-
return Boolean(owner?._settings?.mergeParams);
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// shared empty candidate list, so _scanFrom never tests for a missing map entry twice
|
|
912
|
-
const EMPTY_INDICES = /** @type {number[]} */ ([]);
|
|
913
|
-
|
|
914
|
-
/**
|
|
915
|
-
* The generic scan's index over a router's literal routes: route positions by folded pattern, so
|
|
916
|
-
* a scan visits the routes registered for this exact path instead of comparing every one. String
|
|
917
|
-
* patterns are pure literals, everything else, "/*" included, stays in alwaysVisit and is still
|
|
918
|
-
* matched per request by _pathMatches.
|
|
919
|
-
*
|
|
920
|
-
* @param {any[]} routes the router's own table
|
|
921
|
-
* @param {boolean} caseFlag the frozen case-sensitivity flag
|
|
922
|
-
* @returns {{map: Map<string, number[]>, alwaysVisit: number[]}}
|
|
923
|
-
*/
|
|
924
|
-
function buildLiteralIndex(routes, caseFlag) {
|
|
925
|
-
const map = new Map();
|
|
926
|
-
const alwaysVisit = [];
|
|
927
|
-
for (let i = 0; i < routes.length; i++) {
|
|
928
|
-
const pattern = routes[i].pattern;
|
|
929
|
-
if (typeof pattern === "string" && pattern !== "/*") {
|
|
930
|
-
const key = caseFlag ? pattern : routes[i].patternLower;
|
|
931
|
-
const list = map.get(key);
|
|
932
|
-
if (list === undefined) {
|
|
933
|
-
map.set(key, [i]);
|
|
934
|
-
} else {
|
|
935
|
-
list.push(i);
|
|
936
|
-
}
|
|
937
|
-
} else {
|
|
938
|
-
alwaysVisit.push(i);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
return { map, alwaysVisit };
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* The position of the first value >= from in an ascending list, which is list.length when there
|
|
946
|
-
* is none: where a scan resuming at `from` enters a candidate list.
|
|
947
|
-
*
|
|
948
|
-
* @param {number[]} list
|
|
949
|
-
* @param {number} from
|
|
950
|
-
* @returns {number}
|
|
951
|
-
*/
|
|
952
|
-
function firstAtLeast(list, from) {
|
|
953
|
-
let low = 0;
|
|
954
|
-
let high = list.length;
|
|
955
|
-
while (low < high) {
|
|
956
|
-
const mid = (low + high) >> 1;
|
|
957
|
-
if (list[mid] < from) {
|
|
958
|
-
low = mid + 1;
|
|
959
|
-
} else {
|
|
960
|
-
high = mid;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
return low;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
/**
|
|
967
|
-
* The route's own params merged with those of the mounts it sits under, in express's order: an
|
|
968
|
-
* outer mount first, the route's own last. Numbered captures do not overwrite each other, they
|
|
969
|
-
* shift, so a RegExp mount capturing one group leaves the route's own group numbered from one.
|
|
970
|
-
*
|
|
971
|
-
* @param {Record<string, any>} own what this route's own pattern captured
|
|
972
|
-
* @param {Record<string, any>[]} stack the mounts, outermost first
|
|
973
|
-
* @returns {Record<string, any>}
|
|
974
|
-
*/
|
|
975
|
-
function mergeParams(own, stack) {
|
|
976
|
-
const merged = Object.create(null);
|
|
977
|
-
for (const params of stack) {
|
|
978
|
-
Object.assign(merged, params);
|
|
979
|
-
}
|
|
980
|
-
// both sides numbering from zero means the outer ones keep their places and these move up
|
|
981
|
-
if (own[0] !== undefined && merged[0] !== undefined) {
|
|
982
|
-
let count = 0;
|
|
983
|
-
while (merged[count] !== undefined) {
|
|
984
|
-
count++;
|
|
985
|
-
}
|
|
986
|
-
let last = 0;
|
|
987
|
-
while (own[last] !== undefined) {
|
|
988
|
-
last++;
|
|
989
|
-
}
|
|
990
|
-
for (last--; last >= 0; last--) {
|
|
991
|
-
own[last + count] = own[last];
|
|
992
|
-
if (last < count) {
|
|
993
|
-
delete own[last];
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
return Object.assign(merged, own);
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* The scheme and authority of an absolute request target, or "" for the ordinary kind.
|
|
1002
|
-
*
|
|
1003
|
-
* A request line may carry the whole URI, and express matches on the path while leaving req.url as
|
|
1004
|
-
* it arrived. Same rule it uses: a "://" before any "?" means everything up to the slash after it
|
|
1005
|
-
* is not path.
|
|
1006
|
-
*
|
|
1007
|
-
* @param {string} url
|
|
1008
|
-
* @returns {string}
|
|
1009
|
-
*/
|
|
1010
|
-
function protohostOf(url) {
|
|
1011
|
-
if (url.length === 0 || url.charCodeAt(0) === 0x2f) {
|
|
1012
|
-
return "";
|
|
1013
|
-
}
|
|
1014
|
-
const searchIndex = url.indexOf("?");
|
|
1015
|
-
const pathLength = searchIndex === -1 ? url.length : searchIndex;
|
|
1016
|
-
const fqdnIndex = url.slice(0, pathLength).indexOf("://");
|
|
1017
|
-
if (fqdnIndex === -1) {
|
|
1018
|
-
return "";
|
|
1019
|
-
}
|
|
1020
|
-
const slash = url.indexOf("/", fqdnIndex + 3);
|
|
1021
|
-
return slash === -1 ? url : url.slice(0, slash);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* Fills in what dispatch reads on a request that did not come from µWS.
|
|
1026
|
-
*
|
|
1027
|
-
* express's router can be driven with a plain object, `router.handle({ url, method }, res, next)`,
|
|
1028
|
-
* and its own tests do exactly that; so does anything mounting a router on a server of its own.
|
|
1029
|
-
* Only ever called for such a request: one of ours arrives with these fields already set.
|
|
1030
|
-
*
|
|
1031
|
-
* req.url becomes an accessor, so the router goes on writing plain paths to it while a reader sees
|
|
1032
|
-
* the absolute URI it arrived as. That keeps the protohost out of the dispatch itself.
|
|
1033
|
-
*
|
|
1034
|
-
* @param {any} req
|
|
1035
|
-
* @param {any} router
|
|
1036
|
-
*/
|
|
1037
|
-
function adoptPlainRequest(req, router) {
|
|
1038
|
-
const arrived = typeof req.url === "string" ? req.url : "";
|
|
1039
|
-
const protohost = protohostOf(arrived);
|
|
1040
|
-
let raw = arrived.slice(protohost.length);
|
|
1041
|
-
if (protohost !== "") {
|
|
1042
|
-
Object.defineProperty(req, "url", {
|
|
1043
|
-
configurable: true,
|
|
1044
|
-
enumerable: true,
|
|
1045
|
-
get() {
|
|
1046
|
-
return protohost + raw;
|
|
1047
|
-
},
|
|
1048
|
-
set(value) {
|
|
1049
|
-
const written = String(value);
|
|
1050
|
-
raw = written.startsWith(protohost) ? written.slice(protohost.length) : written;
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
const queryIndex = raw.indexOf("?");
|
|
1056
|
-
const path = queryIndex === -1 ? raw : raw.slice(0, queryIndex);
|
|
1057
|
-
req.urlQuery = queryIndex === -1 ? "" : raw.slice(queryIndex);
|
|
1058
|
-
req._rawQuery = req.urlQuery.slice(1);
|
|
1059
|
-
req._path = path;
|
|
1060
|
-
// an adopted request is a plain object, so it carries no prototype of ours and reads its path
|
|
1061
|
-
// off a property of its own. The class's getter itself, so there is one of it
|
|
1062
|
-
Object.defineProperty(req, "path", PATH_PROPERTY);
|
|
1063
|
-
req._absorbUrlRewrite = ABSORB_URL;
|
|
1064
|
-
req._absorbMethodRewrite = ABSORB_METHOD;
|
|
1065
|
-
req.originalUrl = req.originalUrl ?? arrived;
|
|
1066
|
-
req._originalPath = path;
|
|
1067
|
-
req.endsWithSlash = path.charCodeAt(path.length - 1) === 0x2f;
|
|
1068
|
-
req._opPath = path;
|
|
1069
|
-
req._opPathLower = null;
|
|
1070
|
-
req._mayFailDecode = null;
|
|
1071
|
-
req._lastUrl = req.url;
|
|
1072
|
-
req._lastMethod = req.method;
|
|
1073
|
-
req._isOptions = req.method === "OPTIONS";
|
|
1074
|
-
req._isHead = req.method === "HEAD";
|
|
1075
|
-
req.params = req.params ?? Object.create(null);
|
|
1076
|
-
// null, not fresh arrays: the push sites materialize them on the first mount, and most
|
|
1077
|
-
// requests never see one, same as the Request constructor
|
|
1078
|
-
req._stack = null;
|
|
1079
|
-
req._consumed = 0;
|
|
1080
|
-
req._mountSlash = false;
|
|
1081
|
-
req._paramStack = null;
|
|
1082
|
-
req._matchedMethods = req._isOptions ? new Set() : null;
|
|
1083
|
-
req.routeCount = 1;
|
|
1084
|
-
// read when a mount is left, and there is no application here to read it from
|
|
1085
|
-
req.app = req.app ?? router;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
/**
|
|
1089
|
-
* What express's logerror does. Its final handler prints the error it is about to answer with,
|
|
1090
|
-
* unless the application runs under `env: "test"`, which is how its own suite stays quiet, and it
|
|
1091
|
-
* prints the stack rather than the object. A falsy throw is not printed at all, since finalhandler
|
|
1092
|
-
* only calls onerror when there is an error to call it with.
|
|
1093
|
-
*
|
|
1094
|
-
* @param {any} router the router whose settings decide it
|
|
1095
|
-
* @param {any} err
|
|
1096
|
-
* @returns {void}
|
|
1097
|
-
*/
|
|
1098
|
-
function logError(router, err) {
|
|
1099
|
-
if (err && router.get("env") !== "test") {
|
|
1100
|
-
console.error(err.stack || err.toString());
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* The uWS onAborted handler, bound to the response: a closure here captured two locals and cost
|
|
1106
|
-
* a context plus a function per request, for a path that only ever runs on a client abort.
|
|
1107
|
-
* @this {any} the response, with the request linked as this.req
|
|
1108
|
-
*/
|
|
1109
|
-
function onNativeAborted() {
|
|
1110
|
-
const response = this;
|
|
1111
|
-
const request = response.req;
|
|
1112
|
-
// node's wording for a client abort, which is what body consumers match on
|
|
1113
|
-
/** @type {NodeJS.ErrnoException} */
|
|
1114
|
-
const err = new Error("aborted");
|
|
1115
|
-
err.code = "ECONNRESET";
|
|
1116
|
-
response.aborted = true;
|
|
1117
|
-
response.finished = true;
|
|
1118
|
-
// node's order on the request: 'aborted', then the stream dies, then 'close'. The
|
|
1119
|
-
// error goes only to whoever listens for it, since a destroy(err) with no listener
|
|
1120
|
-
// would take down the process
|
|
1121
|
-
request.emit("aborted");
|
|
1122
|
-
// and the response dies between the two, which is where node puts it. Destroyed rather than
|
|
1123
|
-
// told to emit 'close', because being destroyed is the state express is in here and everything
|
|
1124
|
-
// after it follows from that state rather than having to be reproduced: 'close' goes out once,
|
|
1125
|
-
// a later res.write returns false and calls its callback with ERR_STREAM_DESTROYED, and no
|
|
1126
|
-
// 'error' is emitted, which a destroy(err) here would.
|
|
1127
|
-
//
|
|
1128
|
-
// Without this a handler learnt about the abort only from a write failing, so one that had sent
|
|
1129
|
-
// its head and gone quiet never learnt at all. `res.on("close")` is where cancellation hangs in
|
|
1130
|
-
// every proxy and every streaming endpoint, so it never ran for exactly the shape that needs it.
|
|
1131
|
-
response.destroy();
|
|
1132
|
-
request.destroy(request.listenerCount("error") > 0 ? err : undefined);
|
|
1133
|
-
response.socket?.emit("error", err);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
/**
|
|
1137
|
-
* What app.settings is wrapped in, so a write that never went through set() still tells the hot
|
|
1138
|
-
* copies they are out of date. Only writes are trapped: a missing trap is the plain operation on
|
|
1139
|
-
* the object itself, so reads through here behave exactly as they did.
|
|
1140
|
-
*
|
|
1141
|
-
* defineProperty is here for the trust proxy default marker, which set() writes that way, and for
|
|
1142
|
-
* anything else reaching for Object.defineProperty rather than an assignment.
|
|
1143
|
-
*/
|
|
1144
|
-
const settingsWriteTraps = {
|
|
1145
|
-
/**
|
|
1146
|
-
* @param {any} target
|
|
1147
|
-
* @param {string|symbol} key
|
|
1148
|
-
* @param {any} value
|
|
1149
|
-
*/
|
|
1150
|
-
set(target, key, value) {
|
|
1151
|
-
target[key] = value;
|
|
1152
|
-
settingsEpoch.n++;
|
|
1153
|
-
return true;
|
|
1154
|
-
},
|
|
1155
|
-
/**
|
|
1156
|
-
* @param {any} target
|
|
1157
|
-
* @param {string|symbol} key
|
|
1158
|
-
*/
|
|
1159
|
-
deleteProperty(target, key) {
|
|
1160
|
-
delete target[key];
|
|
1161
|
-
settingsEpoch.n++;
|
|
1162
|
-
return true;
|
|
1163
|
-
},
|
|
1164
|
-
/**
|
|
1165
|
-
* @param {any} target
|
|
1166
|
-
* @param {string|symbol} key
|
|
1167
|
-
* @param {any} descriptor
|
|
1168
|
-
*/
|
|
1169
|
-
defineProperty(target, key, descriptor) {
|
|
1170
|
-
Object.defineProperty(target, key, descriptor);
|
|
1171
|
-
settingsEpoch.n++;
|
|
1172
|
-
return true;
|
|
1173
|
-
}
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
* The per-request constants of a fully literal native registration. µWS matched the URL byte for
|
|
1178
|
-
* byte against this exact pattern and dispatches by method, so the request constructor can take
|
|
1179
|
-
* these as given instead of asking uWS and recomputing them on every request.
|
|
1180
|
-
*
|
|
1181
|
-
* @param {string} path the registered pattern, which is what getUrl() would have answered
|
|
1182
|
-
* @param {string} method uppercase, fixed by which uWS verb the registration used
|
|
1183
|
-
*/
|
|
1184
|
-
function nativePreset(path, method) {
|
|
1185
|
-
const endsWithSlash = path.charCodeAt(path.length - 1) === 0x2f;
|
|
1186
|
-
return {
|
|
1187
|
-
path,
|
|
1188
|
-
method,
|
|
1189
|
-
endsWithSlash,
|
|
1190
|
-
opPath: path,
|
|
1191
|
-
isOptions: method === "OPTIONS",
|
|
1192
|
-
isHead: method === "HEAD",
|
|
1193
|
-
// set at registration when the whole chain provably never reads a header, or never
|
|
1194
|
-
// reads the query; mutable, because a middleware added after listen takes them back
|
|
1195
|
-
skipHeaders: false,
|
|
1196
|
-
skipQuery: false
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
/**
|
|
1201
|
-
* Whether any error middleware exists anywhere under this router, mounted routers and sub-apps
|
|
1202
|
-
* included. The header-skip analysis needs the answer to be no: a throw inside an analyzed
|
|
1203
|
-
* handler would hand the request to code nobody analyzed.
|
|
1204
|
-
*
|
|
1205
|
-
* @param {any} router
|
|
1206
|
-
* @returns {boolean}
|
|
1207
|
-
*/
|
|
1208
|
-
function hasErrorMiddleware(router) {
|
|
1209
|
-
for (const route of router._routes) {
|
|
1210
|
-
for (const callback of route.callbacks) {
|
|
1211
|
-
// a mounted router or a callable sub-app carries routes of its own; the callable
|
|
1212
|
-
// app is also a function, so the routes are looked for first
|
|
1213
|
-
if (callback && callback._routes) {
|
|
1214
|
-
if (hasErrorMiddleware(callback)) {
|
|
1215
|
-
return true;
|
|
1216
|
-
}
|
|
1217
|
-
} else if (typeof callback === "function" && callback.length >= 4) {
|
|
1218
|
-
return true;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
return false;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
/**
|
|
1226
|
-
*
|
|
1227
|
-
*/
|
|
1228
|
-
function checkHandlers(handlers, emptyMessage = "argument handler is required") {
|
|
1229
|
-
if (handlers.length === 0) {
|
|
1230
|
-
throw new TypeError(emptyMessage);
|
|
1231
|
-
}
|
|
1232
|
-
for (const handler of handlers) {
|
|
1233
|
-
if (typeof handler !== "function") {
|
|
1234
|
-
throw new TypeError("argument handler must be a function");
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// what a route's callback is, so that a hop reads a number instead of asking instanceof and length
|
|
1240
|
-
const CALLBACK_PLAIN = 0;
|
|
1241
|
-
const CALLBACK_ERROR = 1;
|
|
1242
|
-
const CALLBACK_ROUTER = 2;
|
|
1243
|
-
|
|
1244
|
-
/**
|
|
1245
|
-
* Reports a parameter that will not decode, unless something is already being reported.
|
|
1246
|
-
*
|
|
1247
|
-
* Matching a route decodes its parameters, and that happens while the walk is still looking for
|
|
1248
|
-
* whoever should answer, including when it is looking for an error handler. Express does the same
|
|
1249
|
-
* and keeps the first error it has: `layerError = layerError || match` in its router. Overwriting
|
|
1250
|
-
* meant a middleware that had already refused the path, express.static answering Bad Request on an
|
|
1251
|
-
* escape it could not decode, had its answer replaced by the decode failure of a route further down
|
|
1252
|
-
* that was never going to run. Same status, different message, and only when a later route happens
|
|
1253
|
-
* to match the same path. Found by fuzzing route tables against express.
|
|
1254
|
-
*
|
|
1255
|
-
* @param {any} req
|
|
1256
|
-
* @param {any} route
|
|
1257
|
-
* @param {any} err
|
|
1258
|
-
*/
|
|
1259
|
-
function raiseDecodeFailure(req, route, err) {
|
|
1260
|
-
if (req._error) {
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
req._error = err;
|
|
1264
|
-
req._errorKey = route.routeKey;
|
|
1265
|
-
req._errorGroup = route.group;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
// the verbs a body is read for unless the application says otherwise, which is the parsers' own
|
|
1269
|
-
// list. A request with any other verb reaches a parser's method check and leaves through it
|
|
1270
|
-
const BODY_METHODS = new Set(["POST", "PUT", "PATCH", "QUERY"]);
|
|
1271
|
-
|
|
1272
|
-
/**
|
|
1273
|
-
* Whether this layer can be stepped over for this request without changing a thing.
|
|
1274
|
-
*
|
|
1275
|
-
* Only the body parsers are ever asked. Their prologue leaves a request that said nothing about a
|
|
1276
|
-
* body alone, whatever content type it carries, which is what `kGetSafe` already records for the
|
|
1277
|
-
* header-skip analysis. Two conditions on top of that mark, and both are needed:
|
|
1278
|
-
*
|
|
1279
|
-
* The request must have said nothing about framing at all, a `content-length: 0` included. A parser
|
|
1280
|
-
* that can see a length answers about the body it describes even when that body is empty: a zero
|
|
1281
|
-
* length with a charset nobody can decode is a 415, in express and here.
|
|
1282
|
-
*
|
|
1283
|
-
* And the verb must be one no parser reads a body for. With no length and no transfer-encoding a
|
|
1284
|
-
* POST still walks into the read, comes back with nothing, and leaves `req.body` as the empty value
|
|
1285
|
-
* its parser produces, which is a thing a handler can see.
|
|
1286
|
-
*
|
|
1287
|
-
* What this is worth: a hop measured 367 microseconds per thousand requests on the machine this was
|
|
1288
|
-
* written on, and the parser prologue it reaches measured 38. Ten to one, for a layer that had
|
|
1289
|
-
* nothing to do.
|
|
1290
|
-
*
|
|
1291
|
-
* That number is also why fusing consecutive layers into one generated function keeps coming up,
|
|
1292
|
-
* and why it is not here. Counted over a real front, morgan, helmet, compression, cors, the two body
|
|
1293
|
-
* parsers, express-session, a middleware of one's own and express.static: three of the nine can be
|
|
1294
|
-
* fused, and the longest run of fusable ones in a row is one. Fusing needs two. The rule was relaxed
|
|
1295
|
-
* from "calls next once, unconditionally" to merely "calls next synchronously" and the answer did
|
|
1296
|
-
* not move, because the six that fail all call next from inside a callback: they are asynchronous by
|
|
1297
|
-
* nature, reading a body, stat-ing a file, loading a session. A layer that has not decided by the
|
|
1298
|
-
* time it returns cannot be fused by any design that keeps the semantics. What fuses is a run of
|
|
1299
|
-
* trivial middlewares, which is a benchmark shape rather than an application's.
|
|
1300
|
-
*
|
|
1301
|
-
* @param {any} route
|
|
1302
|
-
* @param {any} req
|
|
1303
|
-
* @returns {boolean}
|
|
1304
|
-
*/
|
|
1305
|
-
function stepsOver(route, req) {
|
|
1306
|
-
if (route.bodyParserOnly !== true || req._hasBodyHeaders === true) {
|
|
1307
|
-
return false;
|
|
1308
|
-
}
|
|
1309
|
-
if (BODY_METHODS.has(req.method)) {
|
|
1310
|
-
return false;
|
|
1311
|
-
}
|
|
1312
|
-
// an application can add its own. Read once and kept, which is what the parser behind this
|
|
1313
|
-
// layer does with the same setting: asking on every request measured 17 microseconds per
|
|
1314
|
-
// thousand, a third of what stepping over the layer saves
|
|
1315
|
-
if (route.bodyMethods === undefined) {
|
|
1316
|
-
route.bodyMethods = req.app.get("body methods") ?? null;
|
|
1317
|
-
}
|
|
1318
|
-
if (route.bodyMethods !== null && route.bodyMethods.includes(req.method)) {
|
|
1319
|
-
return false;
|
|
1320
|
-
}
|
|
1321
|
-
// The layer is not entered, so it leaves the one mark it would have left: the parser puts
|
|
1322
|
-
// `body` on the request before it works out that there is nothing to read. A library asks
|
|
1323
|
-
// `"body" in req` to tell "a parser has run" from "none has", and a skip that did not leave
|
|
1324
|
-
// it would answer a GET differently from express. See the same seeding in middlewares.js
|
|
1325
|
-
if (!("body" in req)) {
|
|
1326
|
-
req.body = undefined;
|
|
1327
|
-
}
|
|
1328
|
-
return true;
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
/**
|
|
1332
|
-
* Whether a route could answer a request for this path, judged on the pattern it was compiled to.
|
|
1333
|
-
* A literal answers only itself; anything with a parameter or a wildcard answers what its regex
|
|
1334
|
-
* says. Used where the question is "would this earlier route have had its turn first".
|
|
1335
|
-
*
|
|
1336
|
-
* @param {any} route
|
|
1337
|
-
* @param {string} path
|
|
1338
|
-
* @returns {boolean}
|
|
1339
|
-
*/
|
|
1340
|
-
function couldAnswer(route, path) {
|
|
1341
|
-
if (route.pattern instanceof RegExp) {
|
|
1342
|
-
return route.pattern.test(path);
|
|
1343
|
-
}
|
|
1344
|
-
return route.pattern === path;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
/**
|
|
1348
|
-
* Whether a layer written before a mount could answer a request for one of the paths inside it.
|
|
1349
|
-
*
|
|
1350
|
-
* A mount covers everything under its path, so this is a question about a subtree rather than about
|
|
1351
|
-
* the mount point, and the two answers differ: `/a` and `/:p0/:p1/:p2` match none of each other's
|
|
1352
|
-
* text, and both answer `/a/x/y`. µWS jumps straight to whichever leaf it registered, so a leaf a
|
|
1353
|
-
* layer like this could have answered has to stay on the generic path, which is the only place
|
|
1354
|
-
* express's registration order decides.
|
|
1355
|
-
*
|
|
1356
|
-
* Only layers with more segments than the mount path reach this: one with as few already matches
|
|
1357
|
-
* the mount point itself, and _optimizeRoute has refused the mount before the walk gets here.
|
|
1358
|
-
*
|
|
1359
|
-
* Compared folded whichever way the routers are set. A wrong yes costs a leaf its native
|
|
1360
|
-
* registration and nothing else.
|
|
1361
|
-
*
|
|
1362
|
-
* @param {{path: string, use: boolean, method: string, all: boolean}} guard
|
|
1363
|
-
* @param {string} leafPath the leaf's absolute path, parameters and all
|
|
1364
|
-
* @param {any} leaf
|
|
1365
|
-
* @returns {boolean}
|
|
1366
|
-
*/
|
|
1367
|
-
function shadowsLeaf(guard, leafPath, leaf) {
|
|
1368
|
-
if (!guard.all && guard.method !== leaf.method && !(guard.method === "HEAD" && leaf.method === "GET")) {
|
|
1369
|
-
return false;
|
|
1370
|
-
}
|
|
1371
|
-
return pathsCanOverlap(guard.path.toLowerCase(), leafPath.toLowerCase(), guard.use);
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
/**
|
|
1375
|
-
* The layers before a mount that answer some of what is inside it and not all of it, which is the
|
|
1376
|
-
* one thing neither the chain nor µWS's own choice can say: the chain runs what is in it without
|
|
1377
|
-
* matching again, and µWS picks by specificity. They are carried down the walk instead and asked
|
|
1378
|
-
* about every leaf, see shadowsLeaf.
|
|
1379
|
-
*
|
|
1380
|
-
* @param {any} router the router the mount belongs to
|
|
1381
|
-
* @param {any} mount
|
|
1382
|
-
* @param {string} pathPrefix what the mounts above this one consumed
|
|
1383
|
-
* @param {any[]} chain the layers that always run before the mount, which need no guard
|
|
1384
|
-
* @param {any[]} inherited the guards from further out, since a mount two levels down is under
|
|
1385
|
-
* everything written before either of them
|
|
1386
|
-
* @returns {any[]|null} null when a path cannot be read segment by segment, which leaves the mount
|
|
1387
|
-
* to ordinary dispatch rather than guessing about it
|
|
1388
|
-
*/
|
|
1389
|
-
function guardsInside(router, mount, pathPrefix, chain, inherited) {
|
|
1390
|
-
let guards = inherited;
|
|
1391
|
-
for (const r of router._routes) {
|
|
1392
|
-
if (r.routeKey > mount.routeKey) {
|
|
1393
|
-
break;
|
|
1394
|
-
}
|
|
1395
|
-
if (r === mount || chain.includes(r)) {
|
|
1396
|
-
continue;
|
|
1397
|
-
}
|
|
1398
|
-
if (typeof r.path !== "string") {
|
|
1399
|
-
return null;
|
|
1400
|
-
}
|
|
1401
|
-
if (guards === inherited) {
|
|
1402
|
-
guards = [...inherited];
|
|
1403
|
-
}
|
|
1404
|
-
guards.push({ path: pathPrefix + r.path, use: r.use === true, method: r.method, all: r.all === true });
|
|
1405
|
-
}
|
|
1406
|
-
return guards;
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
/**
|
|
1410
|
-
* Notes which application is current before a mounted one is entered, so that exact one comes back
|
|
1411
|
-
* when it hands over.
|
|
1412
|
-
*
|
|
1413
|
-
* Only an application takes it back. Express restores req.app by putting the request prototype
|
|
1414
|
-
* back, and it wraps a mounted application to do that only in Application#use: hang one off a plain
|
|
1415
|
-
* Router and nothing restores it, so whatever runs afterwards still reads the settings of the
|
|
1416
|
-
* application that was entered. Restoring regardless made a later res.send answer with the outer
|
|
1417
|
-
* application's etag setting where express answers with the inner.
|
|
1418
|
-
*
|
|
1419
|
-
* And what comes back is what was current, not the entered application's parent. Those differ the
|
|
1420
|
-
* moment a sub-app is entered from inside another sub-app that a plain Router mounted: the outer
|
|
1421
|
-
* one is still current, express puts that one back, and reaching for `.parent` skipped a level.
|
|
1422
|
-
* A 404 from the top application then carried an ETag under `app.set("etag", false)`, because the
|
|
1423
|
-
* settings answering were the inner application's. Found by fuzzing three levels of routers.
|
|
1424
|
-
*
|
|
1425
|
-
* The route is remembered alongside, so the pop can only ever take back what this same route put
|
|
1426
|
-
* there: a mounted application that answers instead of handing over leaves its entry behind, and
|
|
1427
|
-
* the request is over by then.
|
|
1428
|
-
*
|
|
1429
|
-
* @param {any} walk
|
|
1430
|
-
* @param {any} route
|
|
1431
|
-
* @param {any} req
|
|
1432
|
-
*/
|
|
1433
|
-
function rememberApp(walk, route, req) {
|
|
1434
|
-
if (walk.router._isApplication && route.callbacks[0]?._isApplication) {
|
|
1435
|
-
(req._appStack ??= []).push(route, req.app);
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
* Puts back what rememberApp noted, if this is the route that noted it.
|
|
1441
|
-
*
|
|
1442
|
-
* @param {any} route
|
|
1443
|
-
* @param {any} req
|
|
1444
|
-
*/
|
|
1445
|
-
function restoreApp(route, req) {
|
|
1446
|
-
const stack = req._appStack;
|
|
1447
|
-
if (stack !== undefined && stack.length > 0 && stack[stack.length - 2] === route) {
|
|
1448
|
-
const app = stack.pop();
|
|
1449
|
-
stack.pop();
|
|
1450
|
-
useApp(req, app);
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
/**
|
|
1455
|
-
* useApp
|
|
1456
|
-
* @param {any} req
|
|
1457
|
-
* @param {any} app
|
|
1458
|
-
*/
|
|
1459
|
-
function useApp(req, app) {
|
|
1460
|
-
req.app = app;
|
|
1461
|
-
if (req.res) {
|
|
1462
|
-
req.res.app = app;
|
|
1463
|
-
}
|
|
1464
|
-
// an app's own request/response extensions apply while it runs: express re-parents both
|
|
1465
|
-
// objects on entering a mounted app, and this is the equivalent hop
|
|
1466
|
-
if (app.request && Object.getPrototypeOf(req) !== app.request) {
|
|
1467
|
-
Object.setPrototypeOf(req, app.request);
|
|
1468
|
-
}
|
|
1469
|
-
if (app.response && req.res && Object.getPrototypeOf(req.res) !== app.response) {
|
|
1470
|
-
Object.setPrototypeOf(req.res, app.response);
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
// Every verb node knows about, which is the list the methods package hands Express, and "all" on
|
|
1475
|
-
// top of it. Taken from node rather than written out: the written out one was missing acl, bind,
|
|
1476
|
-
// link, rebind, source, unbind, unlink and unlock, and had four of the others twice.
|
|
1477
|
-
//
|
|
1478
|
-
// GET is left out on purpose. get() is declared in the class, because it doubles as the settings
|
|
1479
|
-
// reader, and the loop at the end of this file would replace it.
|
|
1480
|
-
const methods = ["all", ...METHODS.filter((method) => method !== "GET").map((method) => method.toLowerCase())];
|
|
1481
|
-
const supportedUwsMethods = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"]);
|
|
1482
|
-
|
|
1483
|
-
// the same name rule patternToRegex reads, so a unicode name is found here too
|
|
1484
|
-
const regExParam = /:([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)/gu;
|
|
1485
|
-
|
|
1486
|
-
// Internals here are _underscore and not #private: a callable router is a function with the
|
|
1487
|
-
// router's properties copied onto it, and a # field cannot be copied, so #routes would throw
|
|
1488
|
-
// "Cannot read private member" on the first call.
|
|
1489
|
-
|
|
1490
|
-
// one intermediate prototype per class, built the first time a callable of that class is made
|
|
1491
|
-
const callablePrototypes = new WeakMap();
|
|
1492
|
-
|
|
1493
|
-
/**
|
|
1494
|
-
* The prototype for a callable router or app: the class prototype, with apply and call put back.
|
|
1495
|
-
*
|
|
1496
|
-
* Setting a function's prototype to a class prototype drops Function.prototype from the chain, and
|
|
1497
|
-
* node calls a request listener with handler.apply. An intermediate object, so express.application
|
|
1498
|
-
* stays in the chain. constructor and bind are not restored: the code asks constructor.name, and
|
|
1499
|
-
* BIND is an HTTP verb, so app.bind registers a route as it does in Express.
|
|
1500
|
-
*
|
|
1501
|
-
* @param {object} classPrototype
|
|
1502
|
-
* @returns {object}
|
|
1503
|
-
*/
|
|
1504
|
-
function callablePrototypeFor(classPrototype) {
|
|
1505
|
-
let prototype = callablePrototypes.get(classPrototype);
|
|
1506
|
-
if (prototype) {
|
|
1507
|
-
return prototype;
|
|
1508
|
-
}
|
|
1509
|
-
prototype = Object.create(classPrototype);
|
|
1510
|
-
for (const name of ["apply", "call", "toString"]) {
|
|
1511
|
-
Object.defineProperty(prototype, name, {
|
|
1512
|
-
value: /** @type {any} */ (Function.prototype)[name],
|
|
1513
|
-
writable: true,
|
|
1514
|
-
configurable: true,
|
|
1515
|
-
enumerable: false
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
callablePrototypes.set(classPrototype, prototype);
|
|
1519
|
-
return prototype;
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
/**
|
|
1523
|
-
* The default error page, which is the one Express produces: the stack in a pre, and nothing else.
|
|
1524
|
-
* What reaches it has already been redacted when the environment calls for it.
|
|
1525
|
-
*
|
|
1526
|
-
* The text is escaped, which is not decoration. An error message can carry anything a client sent,
|
|
1527
|
-
* a path or a header among them, and writing it into the page unescaped put whatever it held into
|
|
1528
|
-
* the markup. The Content-Security-Policy on this response stops a script there from running, but
|
|
1529
|
-
* a policy is a second line and not the first. finalhandler escapes and then puts the line breaks
|
|
1530
|
-
* and the indentation back as markup, and this reads the same as what it produces.
|
|
1531
|
-
*
|
|
1532
|
-
* @param {any} err
|
|
1533
|
-
* @returns {string}
|
|
1534
|
-
*/
|
|
1535
|
-
function generateErrorPageHtml(err) {
|
|
1536
|
-
const text = String(err?.stack ?? err)
|
|
1537
|
-
.replace(/&/g, "&")
|
|
1538
|
-
.replace(/</g, "<")
|
|
1539
|
-
.replace(/>/g, ">")
|
|
1540
|
-
.replace(/"/g, """)
|
|
1541
|
-
.replace(/'/g, "'")
|
|
1542
|
-
.replace(/\n/g, "<br>")
|
|
1543
|
-
.replace(/ {2}/g, " ");
|
|
1544
|
-
return (
|
|
1545
|
-
`<!DOCTYPE html>\n` +
|
|
1546
|
-
`<html lang="en">\n` +
|
|
1547
|
-
`<head>\n` +
|
|
1548
|
-
`<meta charset="utf-8">\n` +
|
|
1549
|
-
`<title>Error</title>\n` +
|
|
1550
|
-
`</head>\n` +
|
|
1551
|
-
`<body>\n` +
|
|
1552
|
-
`<pre>${text}</pre>\n` +
|
|
1553
|
-
`</body>\n` +
|
|
1554
|
-
`</html>\n`
|
|
1555
|
-
);
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
83
|
module.exports = class Router extends EventEmitter {
|
|
1559
84
|
/**
|
|
1560
85
|
* The router or application this one is mounted on, undefined until it is.
|
|
1561
|
-
* @type {
|
|
86
|
+
* @type {Router|undefined}
|
|
1562
87
|
*/
|
|
1563
88
|
parent;
|
|
1564
89
|
|
|
@@ -1647,11 +172,11 @@ module.exports = class Router extends EventEmitter {
|
|
|
1647
172
|
this._routes = [];
|
|
1648
173
|
// websocket routes, kept apart from the HTTP ones: µWS serves them itself and listen()
|
|
1649
174
|
// hands them over whole, mount paths and all
|
|
1650
|
-
/** @type {
|
|
175
|
+
/** @type {WsRoute[]|null} */
|
|
1651
176
|
this._wsRoutes = null;
|
|
1652
177
|
// the native presets allowed to skip the header copy, so a late middleware or an etag
|
|
1653
178
|
// arriving after listen can take the permission back; null until one is granted
|
|
1654
|
-
/** @type {Set<
|
|
179
|
+
/** @type {Set<SkipHolder>|null} */
|
|
1655
180
|
this._skipPresets = null;
|
|
1656
181
|
/** @type {boolean|undefined} */
|
|
1657
182
|
this._hasErrMwCache = undefined;
|
|
@@ -1660,15 +185,17 @@ module.exports = class Router extends EventEmitter {
|
|
|
1660
185
|
this.mountpath = "/";
|
|
1661
186
|
// The settings twice: the plain object everything inside here reads, and the Proxy the
|
|
1662
187
|
// outside gets. Express lets an application write app.settings["x"] straight, which set()
|
|
1663
|
-
// never sees, so the hot copies in _hot()
|
|
1664
|
-
//
|
|
1665
|
-
// bumps it on the spot. Reading through a Proxy costs about 20ns, which is why the inside
|
|
1666
|
-
// never does: _settings is the same object without the wrapper.
|
|
188
|
+
// never sees, so the hot copies in _hot() kept answering the old value. The trap bumps the
|
|
189
|
+
// epoch on the spot. Reading through a Proxy costs about 20ns, so the inside never does
|
|
1667
190
|
this._settings = settings;
|
|
1668
191
|
this.settings = new Proxy(settings, settingsWriteTraps);
|
|
1669
192
|
// the base classes; an Application replaces these with its own per-app subclasses, and a
|
|
1670
193
|
// plain router has no request/response prototype layer, as in Express
|
|
194
|
+
// Typed loosely because an Application replaces both with per-app subclasses of its own,
|
|
195
|
+
// and a field declared as the base class would not accept one under strictFunctionTypes
|
|
196
|
+
/** @type {any} */
|
|
1671
197
|
this._request = Request;
|
|
198
|
+
/** @type {any} */
|
|
1672
199
|
this._response = Response;
|
|
1673
200
|
|
|
1674
201
|
if (typeof settings.caseSensitive !== "undefined") {
|
|
@@ -1704,22 +231,20 @@ module.exports = class Router extends EventEmitter {
|
|
|
1704
231
|
* Routes a request through this router, as Express's app.handle and router.handle do. next() is
|
|
1705
232
|
* called when nothing answered, so an unmatched request goes back to whoever is running this.
|
|
1706
233
|
*
|
|
1707
|
-
* @param {any} req
|
|
1708
|
-
* @param {any} res
|
|
1709
|
-
* @param {(err?:
|
|
234
|
+
* @param {any} req a Request, or the plain object express's own router tests drive it with
|
|
235
|
+
* @param {any} res a Response, or whatever the caller is serving with
|
|
236
|
+
* @param {(err?: unknown) => void} [next]
|
|
1710
237
|
* @returns {Promise<void>}
|
|
1711
238
|
*/
|
|
1712
239
|
async handle(req, res, next) {
|
|
1713
240
|
// a request from node's own server, which is what http.createServer(app) delivers
|
|
1714
241
|
if (isNodeRequest(req)) {
|
|
1715
|
-
return serveNodeRequest(this, req,
|
|
242
|
+
return serveNodeRequest(this, req, res, next);
|
|
1716
243
|
}
|
|
1717
244
|
// an app taking over a request becomes that request's app, as it does when mounted, so
|
|
1718
|
-
// req.app.get("view engine") inside a sub-app reads the sub-app's settings
|
|
1719
|
-
//
|
|
1720
|
-
//
|
|
1721
|
-
// a plain object, which is how express's router can be driven and how its own tests drive
|
|
1722
|
-
// it. One of ours arrives with these set, so this costs a property read
|
|
245
|
+
// req.app.get("view engine") inside a sub-app reads the sub-app's settings. A plain router
|
|
246
|
+
// is not an app and leaves it alone, as Express's router.handle does.
|
|
247
|
+
// a plain object, which is how express's router can be driven and how its own tests drive it
|
|
1723
248
|
if (req._opPath === undefined) {
|
|
1724
249
|
if (typeof req.url !== "string" || req.url === "") {
|
|
1725
250
|
// express reads the path with parseurl, which answers nothing for these, and it
|
|
@@ -1854,15 +379,15 @@ module.exports = class Router extends EventEmitter {
|
|
|
1854
379
|
* what a nested router strips off the path before matching against it. Cached per stack, since
|
|
1855
380
|
* the same mount chain is walked by every request that reaches it.
|
|
1856
381
|
*
|
|
1857
|
-
* @param {
|
|
382
|
+
* @param {Request} req
|
|
1858
383
|
* @returns {RegExp}
|
|
1859
384
|
*/
|
|
1860
385
|
getFullMountpath(req) {
|
|
1861
386
|
// path-less app.use() pushes "", so a stack of only those joins to "" no matter how deep it is.
|
|
1862
387
|
// patternToRegex("", true) is EMPTY_REGEX, so this returns exactly what the join path would,
|
|
1863
|
-
//
|
|
1864
|
-
//
|
|
1865
|
-
if (req.
|
|
388
|
+
// The null first: _stack stays null until a mount is entered, and this is reachable from
|
|
389
|
+
// an unmounted request. It used to read a counter that no longer exists, so it threw
|
|
390
|
+
if (req._stack === null || req._stack.length === 0) {
|
|
1866
391
|
return EMPTY_REGEX;
|
|
1867
392
|
}
|
|
1868
393
|
const fullStack = req._stack.join("");
|
|
@@ -1893,14 +418,12 @@ module.exports = class Router extends EventEmitter {
|
|
|
1893
418
|
/**
|
|
1894
419
|
* The generic scan over this router's own table, driven by the literal index: only the routes
|
|
1895
420
|
* registered for this exact path, plus every non-literal route, are visited, in registration
|
|
1896
|
-
* order, and each
|
|
1897
|
-
*
|
|
1898
|
-
* fails, so the first index this answers is the one the plain loop found.
|
|
421
|
+
* order, and each one still answers through the same method gate and _pathMatches the plain
|
|
422
|
+
* loop used. The routes skipped are exactly the literals whose string compare provably fails.
|
|
1899
423
|
*
|
|
1900
|
-
* Runs after _freezeRoutingFlags, which is what makes _caseFlag and _strictFlag readable here
|
|
1901
|
-
* and the lazily built index stable.
|
|
424
|
+
* Runs after _freezeRoutingFlags, which is what makes _caseFlag and _strictFlag readable here.
|
|
1902
425
|
*
|
|
1903
|
-
* @param {
|
|
426
|
+
* @param {Request} req
|
|
1904
427
|
* @param {number} startIndex where to resume the scan
|
|
1905
428
|
* @param {boolean} mayFailDecode whether the path carries a percent escape
|
|
1906
429
|
* @returns {number} the index of the route to enter, or the table length for none
|
|
@@ -1979,8 +502,8 @@ module.exports = class Router extends EventEmitter {
|
|
|
1979
502
|
* makes a route eligible for the native router; anything carrying a parameter or a wildcard was
|
|
1980
503
|
* turned into a regular expression when it was registered.
|
|
1981
504
|
*
|
|
1982
|
-
* @param {
|
|
1983
|
-
* @param {
|
|
505
|
+
* @param {RouteEntry} route
|
|
506
|
+
* @param {Request} req
|
|
1984
507
|
* @returns {boolean}
|
|
1985
508
|
*/
|
|
1986
509
|
_pathMatches(route, req) {
|
|
@@ -2038,16 +561,15 @@ module.exports = class Router extends EventEmitter {
|
|
|
2038
561
|
|
|
2039
562
|
/**
|
|
2040
563
|
* The layers Express keeps on a router, in Express's own shape: one per middleware, one per
|
|
2041
|
-
* route, and the route's own handlers under `route.stack`. Libraries that list an
|
|
2042
|
-
*
|
|
2043
|
-
*
|
|
564
|
+
* route, and the route's own handlers under `route.stack`. Libraries that list an application's
|
|
565
|
+
* endpoints walk this, and so do tests that reach in for a handler by name, which is how
|
|
566
|
+
* LibreChat pulls one middleware out of its router.
|
|
2044
567
|
*
|
|
2045
|
-
* A view,
|
|
2046
|
-
*
|
|
2047
|
-
*
|
|
2048
|
-
* compares identities across two reads gets the same answer Express gives.
|
|
568
|
+
* A view, rebuilt on every read, not the router's own storage: pushing a layer onto it or
|
|
569
|
+
* splicing one out moves nothing. The layer objects are kept, so identities compare across two
|
|
570
|
+
* reads the way they do in Express.
|
|
2049
571
|
*
|
|
2050
|
-
* @returns {
|
|
572
|
+
* @returns {Layer[]}
|
|
2051
573
|
*/
|
|
2052
574
|
get stack() {
|
|
2053
575
|
const layers = [];
|
|
@@ -2070,21 +592,21 @@ module.exports = class Router extends EventEmitter {
|
|
|
2070
592
|
* anything not comparable as a string is compiled to a regular expression and marked complex.
|
|
2071
593
|
*
|
|
2072
594
|
* @param {string} method HTTP method, or USE for a mount
|
|
2073
|
-
* @param {
|
|
2074
|
-
* @param {any} [parent] what to return, so chaining lands on the app rather than the router
|
|
2075
|
-
*
|
|
595
|
+
* @param {string|RegExp|(string|RegExp)[]} path one path or several
|
|
596
|
+
* @param {any} [parent] what to return, so chaining lands on the app rather than the router.
|
|
597
|
+
* Loose because it is also the builder app.route() hands back
|
|
598
|
+
* @param {...any} callbacks handlers, or arrays of them at any depth, flattened below. Loose
|
|
599
|
+
* because a parameter keeps its declared type through that reassignment
|
|
2076
600
|
* @returns {any} parent
|
|
2077
601
|
*/
|
|
2078
602
|
createRoute(method, path, parent = this, ...callbacks) {
|
|
2079
603
|
method = method.toUpperCase();
|
|
2080
604
|
callbacks = callbacks.flat(Infinity);
|
|
2081
605
|
checkHandlers(callbacks);
|
|
2082
|
-
// What express hangs off req.route as its methods, and the three registrations
|
|
2083
|
-
//
|
|
2084
|
-
//
|
|
2085
|
-
//
|
|
2086
|
-
// Built in node's own order, which is the order the methods package hands express, so
|
|
2087
|
-
// the map reads back key for key as express's does.
|
|
606
|
+
// What express hangs off req.route as its methods, and the three registrations disagree:
|
|
607
|
+
// app.all() registers every verb one at a time, so the map names all of them; router.all()
|
|
608
|
+
// and app.route().all() mark the route _all instead; everything hung off one app.route()
|
|
609
|
+
// shares one map. Built in node's own order, so it reads back key for key as express's does
|
|
2088
610
|
let methodMap;
|
|
2089
611
|
let stack;
|
|
2090
612
|
if (method !== "USE") {
|
|
@@ -2193,7 +715,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
2193
715
|
stack,
|
|
2194
716
|
// the route as a request sees it, which is the route itself unless the path was
|
|
2195
717
|
// normalised. Written into the literal so every route keeps one shape
|
|
2196
|
-
exposed: /** @type {
|
|
718
|
+
exposed: /** @type {RouteEntry|undefined} */ (undefined),
|
|
2197
719
|
routeKey: routeKey++,
|
|
2198
720
|
// which app.route() this came from, when it came from one, so the routes it built
|
|
2199
721
|
// count as one route where an error is concerned. undefined for every other route
|
|
@@ -2251,327 +773,23 @@ module.exports = class Router extends EventEmitter {
|
|
|
2251
773
|
}
|
|
2252
774
|
|
|
2253
775
|
/**
|
|
2254
|
-
* The chain a request would walk to reach this route,
|
|
2255
|
-
*
|
|
2256
|
-
* could also match has to be in the chain, in order.
|
|
776
|
+
* The chain a request would walk to reach this route, see optimizeRoute in optimizer.js. Kept
|
|
777
|
+
* as a method because a mounted router is asked for its own through it.
|
|
2257
778
|
*
|
|
2258
|
-
* @param {
|
|
2259
|
-
* @param {
|
|
2260
|
-
* @returns {
|
|
779
|
+
* @param {RouteEntry} route
|
|
780
|
+
* @param {RouteEntry[]} routes every route of this router, in registration order
|
|
781
|
+
* @returns {RouteEntry[]|false} the chain, ending in the route itself
|
|
2261
782
|
*/
|
|
2262
783
|
_optimizeRoute(route, routes) {
|
|
2263
|
-
|
|
2264
|
-
// a route with a parameter matches paths its own text does not, so what an earlier route
|
|
2265
|
-
// could answer is compared shape against shape and not against that text
|
|
2266
|
-
const withParams = typeof route.path === "string" && route.path.includes(":");
|
|
2267
|
-
// under insensitive routing two paths that differ only in case answer the same requests,
|
|
2268
|
-
// so the text comparisons below run on the folded form. µWS itself still matches bytes:
|
|
2269
|
-
// a request in the registered case takes the chain, any other case takes the fallback,
|
|
2270
|
-
// and both answer as express would as long as the chain agrees with registration order
|
|
2271
|
-
const caseSensitive = this._caseSensitive();
|
|
2272
|
-
const routePathFolded = caseSensitive || typeof route.path !== "string" ? route.path : route.path.toLowerCase();
|
|
2273
|
-
// whether this route answers only the path as written, or the one with a trailing slash too
|
|
2274
|
-
const strictHere = (route.owner ?? this)._strictRouting();
|
|
2275
|
-
/** @type {string[]|null} earlier literals a case variant could smuggle a request past */
|
|
2276
|
-
let caseGuards = null;
|
|
2277
|
-
|
|
2278
|
-
for (let i = 0; i < routes.length; i++) {
|
|
2279
|
-
const r = routes[i];
|
|
2280
|
-
if (r.routeKey > route.routeKey) {
|
|
2281
|
-
break;
|
|
2282
|
-
}
|
|
2283
|
-
if (r === route) {
|
|
2284
|
-
continue;
|
|
2285
|
-
}
|
|
2286
|
-
// if the methods are not the same, and its not an all method, skip it
|
|
2287
|
-
if (!r.all && r.method !== route.method) {
|
|
2288
|
-
// check if the methods are compatible (GET and HEAD)
|
|
2289
|
-
if (!(r.method === "HEAD" && route.method === "GET")) {
|
|
2290
|
-
// A mount is registered ALL, because what lives under it can answer any
|
|
2291
|
-
// method, and this chain is computed once for all of them. So an earlier
|
|
2292
|
-
// route of some other method is not irrelevant here the way it is for a
|
|
2293
|
-
// plain route: it belongs in the chain of the leaves that share its method
|
|
2294
|
-
// and in no other, and one chain cannot say that. µWS would then jump
|
|
2295
|
-
// straight to a leaf and answer as though the earlier route did not exist,
|
|
2296
|
-
// which is what let a literal route inside a mounted router beat a parameter
|
|
2297
|
-
// route written before the mount. Leave the mount to ordinary dispatch,
|
|
2298
|
-
// where express's own order is what decides.
|
|
2299
|
-
if (route.use && typeof route.path === "string" && couldAnswer(r, route.path)) {
|
|
2300
|
-
return false;
|
|
2301
|
-
}
|
|
2302
|
-
continue;
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
// The same rule as the one just above, which a route of another method reaches by
|
|
2307
|
-
// another road. A mount's chain is inherited by every path under it, and a route that
|
|
2308
|
-
// is not itself a mount answers the mount point rather than the subtree: in the chain
|
|
2309
|
-
// it ran for the whole of it, so router.all("/:p1") answered the /posts/a-b that
|
|
2310
|
-
// belongs to the router mounted at /posts. guardsInside is written for this: only
|
|
2311
|
-
// layers with more segments than the mount path are asked about a leaf.
|
|
2312
|
-
if (route.use && !r.use && typeof route.path === "string" && couldAnswer(r, route.path)) {
|
|
2313
|
-
return false;
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
// a RegExp mount runs only where its match starts the path and breaks on a separator,
|
|
2317
|
-
// which is decidable here against a literal path and not against one with a parameter
|
|
2318
|
-
if (r.regexMount) {
|
|
2319
|
-
const matched = typeof route.path === "string" ? r.pattern.exec(route.path) : null;
|
|
2320
|
-
const runsAlways =
|
|
2321
|
-
matched !== null &&
|
|
2322
|
-
!matched[0].includes(":") &&
|
|
2323
|
-
route.path.slice(0, matched[0].length) === matched[0] &&
|
|
2324
|
-
(route.path.length === matched[0].length || route.path[matched[0].length] === "/");
|
|
2325
|
-
if (runsAlways) {
|
|
2326
|
-
if (r.callbacks.some((c) => c instanceof Router)) {
|
|
2327
|
-
return false;
|
|
2328
|
-
}
|
|
2329
|
-
optimizedPath.push(r);
|
|
2330
|
-
continue;
|
|
2331
|
-
}
|
|
2332
|
-
// it may still answer some of the paths this route matches, and the chain has no
|
|
2333
|
-
// way to say "only sometimes"
|
|
2334
|
-
if (matched !== null || withParams) {
|
|
2335
|
-
return false;
|
|
2336
|
-
}
|
|
2337
|
-
continue;
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
// check if the paths match. A route with parameters is excluded from the text test:
|
|
2341
|
-
// its literal ":name" text would let an earlier regex in on requests it never matches.
|
|
2342
|
-
const regexCanMatch = r.pattern instanceof RegExp && (!withParams || r.use);
|
|
2343
|
-
if (
|
|
2344
|
-
(regexCanMatch && r.pattern.test(route.path)) ||
|
|
2345
|
-
(typeof r.pattern === "string" &&
|
|
2346
|
-
(r.pattern === route.path ||
|
|
2347
|
-
(!caseSensitive && r.pattern.toLowerCase() === routePathFolded) ||
|
|
2348
|
-
r.pattern === "/*"))
|
|
2349
|
-
) {
|
|
2350
|
-
if (r.callbacks.some((c) => c instanceof Router)) {
|
|
2351
|
-
return false; // cant optimize nested routers with matches
|
|
2352
|
-
}
|
|
2353
|
-
optimizedPath.push(r);
|
|
2354
|
-
continue;
|
|
2355
|
-
}
|
|
2356
|
-
// Without strict routing this registration answers "/x/" as well as "/x". An earlier
|
|
2357
|
-
// pattern matching only the second answers part of what the registration takes and not
|
|
2358
|
-
// the rest, which the chain has no way to say: it runs what is in it without matching
|
|
2359
|
-
// again. Both spellings used to put the route in whole, so app.all("/:p0/{:o1}/{:o2}")
|
|
2360
|
-
// answered a GET /list/Mixed that belonged to the route written after it. An ordinary
|
|
2361
|
-
// pattern answers both spellings, so only an optional group or a wildcard reaches here.
|
|
2362
|
-
if (regexCanMatch && !strictHere && r.pattern.test(route.path + "/")) {
|
|
2363
|
-
return false;
|
|
2364
|
-
}
|
|
2365
|
-
if (!withParams) {
|
|
2366
|
-
continue;
|
|
2367
|
-
}
|
|
2368
|
-
// an earlier route that answers only some of the paths this one matches cannot go in
|
|
2369
|
-
// the chain, which runs what is in it without matching again
|
|
2370
|
-
if (typeof r.path !== "string" || !canBeOptimizedWithParams(r.path)) {
|
|
2371
|
-
return false;
|
|
2372
|
-
}
|
|
2373
|
-
const rPathFolded = caseSensitive ? r.path : r.path.toLowerCase();
|
|
2374
|
-
if (!pathsCanOverlap(rPathFolded, routePathFolded, r.use)) {
|
|
2375
|
-
continue;
|
|
2376
|
-
}
|
|
2377
|
-
if (r.use) {
|
|
2378
|
-
return false;
|
|
2379
|
-
}
|
|
2380
|
-
// the same path lands on the same µWS registration, so the earlier route runs first
|
|
2381
|
-
// from inside the chain, under its own parameter names; a case variant of it lands on
|
|
2382
|
-
// its own registration, whose chain was computed the same way, or on the fallback
|
|
2383
|
-
if (rPathFolded === routePathFolded) {
|
|
2384
|
-
if (r.callbacks.some((c) => c instanceof Router)) {
|
|
2385
|
-
return false;
|
|
2386
|
-
}
|
|
2387
|
-
optimizedPath.push(r);
|
|
2388
|
-
continue;
|
|
2389
|
-
}
|
|
2390
|
-
// otherwise the two overlap only where µWS itself hands the request to the earlier,
|
|
2391
|
-
// more specific registration, so this chain never sees those paths
|
|
2392
|
-
if (
|
|
2393
|
-
!r.optimizedPath ||
|
|
2394
|
-
!uwsPrefersEarlier(r.path, route.path) ||
|
|
2395
|
-
(!caseSensitive && route.path !== routePathFolded)
|
|
2396
|
-
) {
|
|
2397
|
-
return false;
|
|
2398
|
-
}
|
|
2399
|
-
// that argument is about bytes. Under insensitive routing "/POSTS" byte-matches no
|
|
2400
|
-
// registration of "/posts", so µWS hands it here instead, where this chain would
|
|
2401
|
-
// answer as if the earlier route did not exist. The literal is remembered so the
|
|
2402
|
-
// registration can send those requests to the generic router, which is the only place
|
|
2403
|
-
// express's own order can decide; a path with no letter in it has no other case to
|
|
2404
|
-
// arrive in and needs no guard
|
|
2405
|
-
if (!caseSensitive && HAS_LETTER.test(r.path)) {
|
|
2406
|
-
(caseGuards ??= []).push(r.path);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
optimizedPath.push(route);
|
|
2410
|
-
route._caseGuards = caseGuards;
|
|
2411
|
-
|
|
2412
|
-
return optimizedPath;
|
|
784
|
+
return optimizeRoute(this, route, routes);
|
|
2413
785
|
}
|
|
2414
786
|
|
|
2415
787
|
/**
|
|
2416
|
-
* Hands every route reachable by path alone to the native uWS router,
|
|
2417
|
-
*
|
|
2418
|
-
* needs every route to have been registered first.
|
|
788
|
+
* Hands every route reachable by path alone to the native uWS router, see
|
|
789
|
+
* compileOptimizedRoutes in optimizer.js. Runs once, when the app starts listening.
|
|
2419
790
|
*/
|
|
2420
791
|
_compileOptimizedRoutes() {
|
|
2421
|
-
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
// Everything below is what makes this framework fast, and every one of its decisions is a
|
|
2425
|
-
// claim that µWS answering by itself is the same answer the chain would have given. Turned
|
|
2426
|
-
// off, the claim is not made and the chain answers everything. Serving one application both
|
|
2427
|
-
// ways and comparing the answers is what tests those claims: `npm run fuzz -- --self`.
|
|
2428
|
-
if (this.get("native routes") === false) {
|
|
2429
|
-
return;
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
// pathPrefix/chainPrefix accumulate across nested sole-callback mounts, and outerGuards
|
|
2433
|
-
// carries what was written before them and answers only part of what is under them
|
|
2434
|
-
const walk = (router, pathPrefix, chainPrefix, outerGuards) => {
|
|
2435
|
-
for (const route of router._routes) {
|
|
2436
|
-
if (route.use) {
|
|
2437
|
-
// only sole-callback mounts. Case rules do not gate the walk: each level's
|
|
2438
|
-
// _optimizeRoute guards its own routes under its own setting, and a request
|
|
2439
|
-
// in any other case than the registered one takes the fallback, which
|
|
2440
|
-
// honours the child's setting on its own
|
|
2441
|
-
if (
|
|
2442
|
-
!route.complex &&
|
|
2443
|
-
canBeOptimized(route.path) &&
|
|
2444
|
-
route.path !== "/*" &&
|
|
2445
|
-
route.callbacks.length === 1 &&
|
|
2446
|
-
route.callbacks[0] instanceof Router
|
|
2447
|
-
) {
|
|
2448
|
-
let pathToMount = router._optimizeRoute(route, router._routes);
|
|
2449
|
-
if (!pathToMount) {
|
|
2450
|
-
route._whyGeneric = "something before it in the same router overlaps its paths";
|
|
2451
|
-
continue;
|
|
2452
|
-
}
|
|
2453
|
-
pathToMount = pathToMount.slice(0, -1);
|
|
2454
|
-
const guards = guardsInside(router, route, pathPrefix, pathToMount, outerGuards);
|
|
2455
|
-
if (guards === null) {
|
|
2456
|
-
route._whyGeneric = "a path written before it cannot be read segment by segment";
|
|
2457
|
-
continue;
|
|
2458
|
-
}
|
|
2459
|
-
route._walkedInto = true;
|
|
2460
|
-
walk(
|
|
2461
|
-
route.callbacks[0],
|
|
2462
|
-
pathPrefix + route.path,
|
|
2463
|
-
[
|
|
2464
|
-
...chainPrefix,
|
|
2465
|
-
...pathToMount,
|
|
2466
|
-
{
|
|
2467
|
-
...route,
|
|
2468
|
-
callbacks: [],
|
|
2469
|
-
callbackKinds: [],
|
|
2470
|
-
keepMount: true,
|
|
2471
|
-
// mounted sub-apps become req.app during their dispatch, like express
|
|
2472
|
-
mountApp:
|
|
2473
|
-
route.callbacks[0].constructor.name === "Application"
|
|
2474
|
-
? route.callbacks[0]
|
|
2475
|
-
: undefined
|
|
2476
|
-
}
|
|
2477
|
-
],
|
|
2478
|
-
guards
|
|
2479
|
-
);
|
|
2480
|
-
} else {
|
|
2481
|
-
// said once here rather than at each condition above: a mount is walked into
|
|
2482
|
-
// only when µWS can match its path on its own and it carries exactly one
|
|
2483
|
-
// router, and those are the two things worth telling anyone about
|
|
2484
|
-
route._whyGeneric = !(route.callbacks.length === 1 && route.callbacks[0] instanceof Router)
|
|
2485
|
-
? "it is middleware rather than a single mounted router"
|
|
2486
|
-
: "µWS cannot match this mount path on its own";
|
|
2487
|
-
}
|
|
2488
|
-
// µWS picks by specificity and Express by registration order, so the chain
|
|
2489
|
-
// computed for whichever route µWS lands on runs everything that could have
|
|
2490
|
-
// matched before it
|
|
2491
|
-
} else if (
|
|
2492
|
-
// parameters that are whole segments are matched by µWS the same way
|
|
2493
|
-
(canBeOptimized(route.path) || canBeOptimizedWithParams(route.path)) &&
|
|
2494
|
-
// Inside a mounted router, only when nothing after it could answer the same
|
|
2495
|
-
// path. This used to be asked of parameter routes alone, and a literal one
|
|
2496
|
-
// needs it just as much: µWS picks by specificity where Express picks by
|
|
2497
|
-
// registration order, and a chain carries only what runs in front of its
|
|
2498
|
-
// route, so `router.get("/a", (req, res, next) => next())` followed by
|
|
2499
|
-
// `router.get("/:x", ...)` left the mount instead of reaching the second
|
|
2500
|
-
// route, and answered 404 where Express answers it. Found by the fuzzer,
|
|
2501
|
-
// replay with --seed 221940161 --rounds 1.
|
|
2502
|
-
(!pathPrefix || !router._isFollowedByAnOverlap(route, router._routes)) &&
|
|
2503
|
-
supportedUwsMethods.has(route.method)
|
|
2504
|
-
) {
|
|
2505
|
-
// something outside this router, written before the mount it is in, that could
|
|
2506
|
-
// answer this exact path. µWS would jump here and never give it its turn
|
|
2507
|
-
if (outerGuards.length > 0 && typeof route.path === "string") {
|
|
2508
|
-
const absolute = pathPrefix + route.path;
|
|
2509
|
-
const guard = outerGuards.find((g) => shadowsLeaf(g, absolute, route));
|
|
2510
|
-
if (guard) {
|
|
2511
|
-
route._whyGeneric = `${guard.path} is written before the mount it is in and answers the same paths`;
|
|
2512
|
-
continue;
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
const leafPath = router._optimizeRoute(route, router._routes);
|
|
2516
|
-
if (!leafPath) {
|
|
2517
|
-
route._whyGeneric = "something before it in the same router overlaps its paths";
|
|
2518
|
-
continue;
|
|
2519
|
-
}
|
|
2520
|
-
// param route earlier in the same router would steal this static path
|
|
2521
|
-
if (leafPath.length > 1) {
|
|
2522
|
-
const shadow = leafPath[leafPath.length - 2];
|
|
2523
|
-
if (
|
|
2524
|
-
shadow &&
|
|
2525
|
-
!shadow.use &&
|
|
2526
|
-
shadow.method === route.method &&
|
|
2527
|
-
shadow.path !== route.path &&
|
|
2528
|
-
shadow.pattern instanceof RegExp
|
|
2529
|
-
) {
|
|
2530
|
-
route._whyGeneric = `the parameter route ${shadow.path} is written before it`;
|
|
2531
|
-
continue;
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
// the prefix goes in whether or not the mount had a path: a pathless mount
|
|
2535
|
-
// adds nothing to the path and everything to the chain, the middlewares in
|
|
2536
|
-
// front of it and the mount entry that says where to resume
|
|
2537
|
-
const chain = chainPrefix.length > 0 ? [...chainPrefix, ...leafPath] : leafPath;
|
|
2538
|
-
if (pathPrefix) {
|
|
2539
|
-
const registered = {
|
|
2540
|
-
...route,
|
|
2541
|
-
path: pathPrefix + route.path,
|
|
2542
|
-
pattern: pathPrefix + route.path,
|
|
2543
|
-
optimizedRouter: true
|
|
2544
|
-
};
|
|
2545
|
-
if (route._caseGuards) {
|
|
2546
|
-
// compared against the whole path µWS matched, so they carry the mount
|
|
2547
|
-
// prefix, folded along with the rest of it
|
|
2548
|
-
registered._caseGuards = route._caseGuards.map((p) => pathPrefix + p);
|
|
2549
|
-
}
|
|
2550
|
-
this._registerUwsRoute(registered, chain);
|
|
2551
|
-
// the chain holds the original object, so the request-time guard has to
|
|
2552
|
-
// find the computed fields there, or a mounted param route extracts its
|
|
2553
|
-
// params twice. The names match: the prefix is static, so the copy's path
|
|
2554
|
-
// adds no parameter of its own
|
|
2555
|
-
route.optimizedParams = registered.optimizedParams;
|
|
2556
|
-
route.optimizedPath = registered.optimizedPath;
|
|
2557
|
-
// and what was decided about it, for the same reason: the copy is thrown
|
|
2558
|
-
// away and the profile reads the route the application actually holds
|
|
2559
|
-
route._native = registered._native;
|
|
2560
|
-
} else {
|
|
2561
|
-
this._registerUwsRoute(route, chain);
|
|
2562
|
-
}
|
|
2563
|
-
} else if (!supportedUwsMethods.has(route.method)) {
|
|
2564
|
-
route._whyGeneric = `µWS does not serve ${route.method}`;
|
|
2565
|
-
} else if (canBeOptimized(route.path) || canBeOptimizedWithParams(route.path)) {
|
|
2566
|
-
// eligible but for the overlap test, which only applies inside a mount
|
|
2567
|
-
route._whyGeneric = "a route after it in the same mounted router could answer the same paths";
|
|
2568
|
-
} else {
|
|
2569
|
-
route._whyGeneric = "µWS cannot match this path on its own";
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
};
|
|
2573
|
-
|
|
2574
|
-
walk(this, "", [], []);
|
|
792
|
+
compileOptimizedRoutes(this);
|
|
2575
793
|
}
|
|
2576
794
|
|
|
2577
795
|
/**
|
|
@@ -2579,12 +797,12 @@ module.exports = class Router extends EventEmitter {
|
|
|
2579
797
|
* request does whichever path serves it. The response rides back as request.res: returning
|
|
2580
798
|
* a `{ request, response }` pair was one throwaway object per request.
|
|
2581
799
|
*
|
|
2582
|
-
* @param {
|
|
2583
|
-
* @param {
|
|
2584
|
-
* @param {
|
|
2585
|
-
* @param {
|
|
800
|
+
* @param {UwsResponse} res uWS response
|
|
801
|
+
* @param {UwsRequest} req uWS request, readable only during this call
|
|
802
|
+
* @param {NativePreset} [preset] a literal registration's constants, see nativePreset
|
|
803
|
+
* @param {SkipHolder} [skipHolder] the object a granted header skip lives on: the preset itself
|
|
2586
804
|
* for a literal registration, a holder of its own for a parameterised one
|
|
2587
|
-
* @returns {
|
|
805
|
+
* @returns {Request} the request, with the response reachable as request.res
|
|
2588
806
|
*/
|
|
2589
807
|
handleRequest(res, req, preset, skipHolder) {
|
|
2590
808
|
const request = new this._request(req, res, this, preset, skipHolder);
|
|
@@ -2598,19 +816,17 @@ module.exports = class Router extends EventEmitter {
|
|
|
2598
816
|
* Refuses a request whose framing cannot be trusted and hangs up without answering. No route
|
|
2599
817
|
* runs, so nothing downstream can be reached by one.
|
|
2600
818
|
*
|
|
2601
|
-
* Hanging up is the
|
|
2602
|
-
*
|
|
819
|
+
* Hanging up is the point: uWS has already read what followed the body it believed in as a
|
|
820
|
+
* second, pipelined request, and it dispatches that one unless the socket goes. Node answers
|
|
2603
821
|
* 400 and then closes, and this cannot do both: uWS only skips the queued request when the
|
|
2604
|
-
* response is closed rather than completed, and
|
|
2605
|
-
*
|
|
2606
|
-
* request through, so the close wins
|
|
2607
|
-
* content-lengths, so there is no well-behaved client to explain it to.
|
|
822
|
+
* response is closed rather than completed, and writeStatus, end and endWithoutBody all
|
|
823
|
+
* complete it. Every combination was measured and delivering the 400 always let the smuggled
|
|
824
|
+
* request through, so the close wins.
|
|
2608
825
|
*
|
|
2609
826
|
* Called once handleRequest has fully returned, never from inside it: an Application links the
|
|
2610
|
-
* response into its pending list after the base call, and the 'close' emitted here
|
|
2611
|
-
* takes it back out again.
|
|
827
|
+
* response into its pending list after the base call, and the 'close' emitted here takes it out.
|
|
2612
828
|
*
|
|
2613
|
-
* @param {
|
|
829
|
+
* @param {Response} response
|
|
2614
830
|
*/
|
|
2615
831
|
_refuseRequest(response) {
|
|
2616
832
|
response.finished = true;
|
|
@@ -2623,8 +839,8 @@ module.exports = class Router extends EventEmitter {
|
|
|
2623
839
|
* for a response that outlives its handler callback: the native handler arms it in its
|
|
2624
840
|
* finally when the answer is still pending, which on a synchronous route it never is.
|
|
2625
841
|
*
|
|
2626
|
-
* @param {
|
|
2627
|
-
* @param {
|
|
842
|
+
* @param {UwsResponse} res uWS response
|
|
843
|
+
* @param {Response} response
|
|
2628
844
|
*/
|
|
2629
845
|
_armAbort(res, response) {
|
|
2630
846
|
res.onAborted(onNativeAborted.bind(response));
|
|
@@ -2638,8 +854,8 @@ module.exports = class Router extends EventEmitter {
|
|
|
2638
854
|
* mount or a pattern of an unknown shape counts as an overlap; two paths µWS could match itself
|
|
2639
855
|
* are compared segment by segment.
|
|
2640
856
|
*
|
|
2641
|
-
* @param {
|
|
2642
|
-
* @param {
|
|
857
|
+
* @param {RouteEntry} route
|
|
858
|
+
* @param {RouteEntry[]} routes every route of the router this one belongs to
|
|
2643
859
|
* @returns {boolean}
|
|
2644
860
|
*/
|
|
2645
861
|
_isFollowedByAnOverlap(route, routes) {
|
|
@@ -2670,244 +886,14 @@ module.exports = class Router extends EventEmitter {
|
|
|
2670
886
|
}
|
|
2671
887
|
|
|
2672
888
|
/**
|
|
2673
|
-
*
|
|
2674
|
-
*
|
|
889
|
+
* Registers one route with uWS, see registerUwsRoute in optimizer.js. Kept as a method because
|
|
890
|
+
* the optimizer tests replace it to see which routes went native.
|
|
2675
891
|
*
|
|
2676
|
-
* @param {
|
|
2677
|
-
* @param {
|
|
892
|
+
* @param {RouteEntry} route
|
|
893
|
+
* @param {RouteEntry[]} optimizedPath the chain the route was optimized with
|
|
2678
894
|
*/
|
|
2679
895
|
_registerUwsRoute(route, optimizedPath) {
|
|
2680
|
-
|
|
2681
|
-
if (method === "all") {
|
|
2682
|
-
method = "any";
|
|
2683
|
-
} else if (method === "delete") {
|
|
2684
|
-
method = "del";
|
|
2685
|
-
}
|
|
2686
|
-
if (route.path.includes(":")) {
|
|
2687
|
-
route.optimizedParams = route.path.match(regExParam).map((p) => p.slice(1));
|
|
2688
|
-
}
|
|
2689
|
-
// null for almost every route: only a parameter route with an earlier literal that a case
|
|
2690
|
-
// variant could slip past carries one, see _optimizeRoute. Built once here, and matched
|
|
2691
|
-
// insensitively, since that is the folding the guard exists for
|
|
2692
|
-
const caseGuards = route._caseGuards
|
|
2693
|
-
? route._caseGuards.map((p) =>
|
|
2694
|
-
needsConversionToRegex(p) ? patternToRegex(p, false, false) : p.toLowerCase()
|
|
2695
|
-
)
|
|
2696
|
-
: null;
|
|
2697
|
-
const makeHandler = (chain, preset, skips, wireMethod) => {
|
|
2698
|
-
// the mutable object a granted skip lives on, so a middleware arriving after
|
|
2699
|
-
// listen can take it back: a literal registration's preset doubles as it, and a
|
|
2700
|
-
// parameterised one, which has no preset, gets a holder of its own. It also carries
|
|
2701
|
-
// the registration's method, so the constructor settles it with one compare
|
|
2702
|
-
let skipHolder = preset;
|
|
2703
|
-
if (skipHolder === undefined && (skips.skipHeaders || skips.skipQuery || wireMethod !== null)) {
|
|
2704
|
-
skipHolder = {
|
|
2705
|
-
skipHeaders: skips.skipHeaders,
|
|
2706
|
-
skipQuery: skips.skipQuery,
|
|
2707
|
-
method: wireMethod,
|
|
2708
|
-
isOptions: wireMethod === "OPTIONS",
|
|
2709
|
-
isHead: wireMethod === "HEAD"
|
|
2710
|
-
};
|
|
2711
|
-
if (skips.skipHeaders || skips.skipQuery) {
|
|
2712
|
-
(this._skipPresets ??= new Set()).add(skipHolder);
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
// all three are registration-time constants: computing them in the handler was a
|
|
2716
|
-
// closure and a scan of the chain on every native request.
|
|
2717
|
-
// Falling back resumes after the mount, not after the router's leaf: the leaf can have
|
|
2718
|
-
// a lower routeKey than the parent's middlewares, and an error handler declared before
|
|
2719
|
-
// the mount must not catch what the router threw
|
|
2720
|
-
const mount = chain.find((r) => r.keepMount);
|
|
2721
|
-
const skipUntil = mount ?? chain[chain.length - 1];
|
|
2722
|
-
const optimizedParams = route.optimizedParams;
|
|
2723
|
-
// not async, and no _routeRequest: its promise pair exists for callers that await,
|
|
2724
|
-
// and this one never did. nativeDone and nativeFail defer their epilogues to a
|
|
2725
|
-
// microtask, which is where the await used to resume, so the visible order holds
|
|
2726
|
-
return (res, req) => {
|
|
2727
|
-
// a request that is an earlier literal in another case: express answers it with
|
|
2728
|
-
// that route, and the chain here does not contain it, so the generic router takes
|
|
2729
|
-
// this one
|
|
2730
|
-
if (caseGuards !== null && anyGuardHits(caseGuards, req.getUrl())) {
|
|
2731
|
-
// an application is what registers native routes, and only it serves
|
|
2732
|
-
return /** @type {any} */ (this)._serveGeneric(res, req);
|
|
2733
|
-
}
|
|
2734
|
-
const request = this.handleRequest(res, req, preset, skipHolder);
|
|
2735
|
-
const response = request.res;
|
|
2736
|
-
if (request._mustRefuse === true) {
|
|
2737
|
-
return this._refuseRequest(response);
|
|
2738
|
-
}
|
|
2739
|
-
if (optimizedParams) {
|
|
2740
|
-
// slicing these out of the already-fetched path instead measured a wash:
|
|
2741
|
-
// the segment scan costs what the crossing costs
|
|
2742
|
-
request.optimizedParams = new NullObject();
|
|
2743
|
-
for (let i = 0; i < optimizedParams.length; i++) {
|
|
2744
|
-
request.optimizedParams[optimizedParams[i]] = req.getParameter(i);
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
const walk = new Walk(this, request, response, chain, true, skipUntil, nativeDone, nativeFail);
|
|
2748
|
-
try {
|
|
2749
|
-
walk.dispatch(0);
|
|
2750
|
-
} catch (err) {
|
|
2751
|
-
// what a throw inside a promise executor did: reject, once
|
|
2752
|
-
nativeFail.call(walk, err);
|
|
2753
|
-
} finally {
|
|
2754
|
-
// whatever runs after this line is outside the cork uWS held for this
|
|
2755
|
-
// callback, so later writes have to open their own
|
|
2756
|
-
response._corkNeeded = true;
|
|
2757
|
-
// an abort can only arrive after this callback returns, so a response that
|
|
2758
|
-
// already finished inside it never needs uWS told at all
|
|
2759
|
-
if (!response.finished) {
|
|
2760
|
-
this._armAbort(res, response);
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
};
|
|
2764
|
-
};
|
|
2765
|
-
// a HEAD route may sit in a GET route's chain so the head registration runs it, but a
|
|
2766
|
-
// chain runs without re-matching the method, so the get registration must not see it
|
|
2767
|
-
const getChain =
|
|
2768
|
-
route.method === "GET" ? optimizedPath.filter((r) => r.all || r.method !== "HEAD") : optimizedPath;
|
|
2769
|
-
route.optimizedPath = optimizedPath;
|
|
2770
|
-
const headChain = getChain.length === optimizedPath.length ? getChain : optimizedPath;
|
|
2771
|
-
|
|
2772
|
-
// A fully literal registration knows path and method here, so each registration site
|
|
2773
|
-
// hands the request constructor its own constants. An "any" registration serves every
|
|
2774
|
-
// verb and a parameterised one matches paths it cannot spell, so both stay dynamic
|
|
2775
|
-
const canPreset = !route.optimizedParams && method !== "any";
|
|
2776
|
-
// the route's own router decides, not the app running the registration: a router created
|
|
2777
|
-
// with { strict: true } and mounted on an app without it does not answer /things/, and
|
|
2778
|
-
// registering that path here is the only way it could
|
|
2779
|
-
const strictHere = (route.owner ?? this)._strictRouting();
|
|
2780
|
-
|
|
2781
|
-
// Whether requests served by this registration may skip the header copy: GET and its
|
|
2782
|
-
// HEAD twins only, no error middleware may exist anywhere (a throw hands the request to
|
|
2783
|
-
// code the analysis never saw), and every callback in the chain has to pass the source
|
|
2784
|
-
// analysis in usage.js, whose default answer is no.
|
|
2785
|
-
//
|
|
2786
|
-
// The etag setting is not one of the conditions. It used to be, on the grounds that send
|
|
2787
|
-
// consults freshness, but the skip branch reads if-none-match and if-modified-since by
|
|
2788
|
-
// name whatever the setting, see the comment at request.js:527, and
|
|
2789
|
-
// req.fresh reads nothing else off the request. Requiring etag off as well cost the copy
|
|
2790
|
-
// to every application that left it on, which is every application that did not go
|
|
2791
|
-
// looking for the setting.
|
|
2792
|
-
const NO_SKIPS = { skipHeaders: false, skipQuery: false };
|
|
2793
|
-
let getSkips = NO_SKIPS;
|
|
2794
|
-
let headSkips = NO_SKIPS;
|
|
2795
|
-
if (route.method === "GET") {
|
|
2796
|
-
let hasErr = this._hasErrMwCache;
|
|
2797
|
-
if (hasErr === undefined) {
|
|
2798
|
-
hasErr = this._hasErrMwCache = hasErrorMiddleware(this);
|
|
2799
|
-
}
|
|
2800
|
-
if (!hasErr) {
|
|
2801
|
-
// a terminal next() may only fall into the framework's own 404, so no later
|
|
2802
|
-
// route may be able to catch the same path
|
|
2803
|
-
const owner = route.owner ?? this;
|
|
2804
|
-
const noLaterMatch = !owner._isFollowedByAnOverlap.call(owner, route, owner._routes);
|
|
2805
|
-
getSkips = chainUsage(getChain, noLaterMatch);
|
|
2806
|
-
headSkips = headChain === getChain ? getSkips : chainUsage(headChain, noLaterMatch);
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
// remembered so a middleware or setting arriving after listen can take the skips back
|
|
2810
|
-
const makePreset = (path, method, skips) => {
|
|
2811
|
-
const preset = nativePreset(path, method);
|
|
2812
|
-
if (skips.skipHeaders || skips.skipQuery) {
|
|
2813
|
-
preset.skipHeaders = skips.skipHeaders;
|
|
2814
|
-
preset.skipQuery = skips.skipQuery;
|
|
2815
|
-
(this._skipPresets ??= new Set()).add(preset);
|
|
2816
|
-
}
|
|
2817
|
-
return preset;
|
|
2818
|
-
};
|
|
2819
|
-
|
|
2820
|
-
// the wire token this registration answers; "any" serves every verb and stays dynamic
|
|
2821
|
-
const wireMethod = method === "any" ? null : route.method;
|
|
2822
|
-
let fn = makeHandler(
|
|
2823
|
-
getChain,
|
|
2824
|
-
canPreset ? makePreset(route.path, route.method, getSkips) : undefined,
|
|
2825
|
-
getSkips,
|
|
2826
|
-
wireMethod
|
|
2827
|
-
);
|
|
2828
|
-
const jsFn = fn;
|
|
2829
|
-
|
|
2830
|
-
let replacedPath = route.path;
|
|
2831
|
-
|
|
2832
|
-
// the response prototype the route will really run under: its own app's, which sees a
|
|
2833
|
-
// method patched there or inherited from a parent app, falling back to the registering app
|
|
2834
|
-
const responseProto = /** @type {any} */ (route.owner)?.response ?? /** @type {any} */ (this).response;
|
|
2835
|
-
// check if route is declarative
|
|
2836
|
-
if (
|
|
2837
|
-
optimizedPath.length === 1 && // must not have middlewares
|
|
2838
|
-
route.callbacks.length === 1 && // must not have multiple callbacks
|
|
2839
|
-
typeof route.callbacks[0] === "function" && // must be a function
|
|
2840
|
-
route.paramCallbacks.size === 0 && // a param callback has to run, and this answers without running anything
|
|
2841
|
-
// a captured value is decoded when the route runs, and one that cannot be decoded is a
|
|
2842
|
-
// 400 in express and on the ordinary path here. Nothing runs to raise it on a
|
|
2843
|
-
// declarative response, so GET /a-b%5Ec@d%e came back 200 from app.get("/:p12")
|
|
2844
|
-
route.optimizedParams === undefined &&
|
|
2845
|
-
// a declarative response is answered by µWS itself, so no javascript runs and the case
|
|
2846
|
-
// guard could not: a route that needs one has to stay an ordinary handler
|
|
2847
|
-
caseGuards === null &&
|
|
2848
|
-
!resDecMethods.some((method) => resCodes[method] !== responseProto[method].toString()) && // must not have injected methods
|
|
2849
|
-
this.get("declarative responses") // must have declarative responses enabled
|
|
2850
|
-
) {
|
|
2851
|
-
const decRes = compileDeclarative(route.callbacks[0], this);
|
|
2852
|
-
if (decRes) {
|
|
2853
|
-
fn = decRes;
|
|
2854
|
-
}
|
|
2855
|
-
} else {
|
|
2856
|
-
replacedPath = route.path.replace(regExParam, ":x");
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
// what listen() settled about this route, kept so `npx fulmine profile` can print it rather
|
|
2860
|
-
// than making anyone read the source or instrument it. Written once, during compilation,
|
|
2861
|
-
// so no request pays for it
|
|
2862
|
-
route._native = {
|
|
2863
|
-
path: replacedPath,
|
|
2864
|
-
declarative: fn !== jsFn,
|
|
2865
|
-
skipHeaders: getSkips.skipHeaders === true,
|
|
2866
|
-
skipQuery: getSkips.skipQuery === true,
|
|
2867
|
-
ahead: optimizedPath.length - 1,
|
|
2868
|
-
guards: caseGuards ? caseGuards.length : 0
|
|
2869
|
-
};
|
|
2870
|
-
|
|
2871
|
-
this.uwsApp[method](replacedPath, fn);
|
|
2872
|
-
if (!strictHere && route.path[route.path.length - 1] !== "/") {
|
|
2873
|
-
// a declarative response answers the twin as itself; a preset handler cannot be
|
|
2874
|
-
// shared, since the twin's path is its own constant
|
|
2875
|
-
const slashFn =
|
|
2876
|
-
fn !== jsFn
|
|
2877
|
-
? fn
|
|
2878
|
-
: canPreset
|
|
2879
|
-
? makeHandler(
|
|
2880
|
-
getChain,
|
|
2881
|
-
makePreset(route.path + "/", route.method, getSkips),
|
|
2882
|
-
getSkips,
|
|
2883
|
-
wireMethod
|
|
2884
|
-
)
|
|
2885
|
-
: fn;
|
|
2886
|
-
this.uwsApp[method](replacedPath + "/", slashFn);
|
|
2887
|
-
if (method === "get") {
|
|
2888
|
-
this.uwsApp.head(
|
|
2889
|
-
replacedPath + "/",
|
|
2890
|
-
makeHandler(
|
|
2891
|
-
headChain,
|
|
2892
|
-
canPreset ? makePreset(route.path + "/", "HEAD", headSkips) : undefined,
|
|
2893
|
-
headSkips,
|
|
2894
|
-
"HEAD"
|
|
2895
|
-
)
|
|
2896
|
-
);
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
if (method === "get") {
|
|
2900
|
-
// its own handler always: the shared one would carry the GET registration's method
|
|
2901
|
-
this.uwsApp.head(
|
|
2902
|
-
replacedPath,
|
|
2903
|
-
makeHandler(
|
|
2904
|
-
headChain,
|
|
2905
|
-
canPreset ? makePreset(route.path, "HEAD", headSkips) : undefined,
|
|
2906
|
-
headSkips,
|
|
2907
|
-
"HEAD"
|
|
2908
|
-
)
|
|
2909
|
-
);
|
|
2910
|
-
}
|
|
896
|
+
registerUwsRoute(this, route, optimizedPath);
|
|
2911
897
|
}
|
|
2912
898
|
|
|
2913
899
|
/**
|
|
@@ -2915,10 +901,10 @@ module.exports = class Router extends EventEmitter {
|
|
|
2915
901
|
* Passing something to next() from an error handler clears the error and resumes routing,
|
|
2916
902
|
* which is how Express lets a handler decide the error was not fatal.
|
|
2917
903
|
*
|
|
2918
|
-
* @param {any} err
|
|
904
|
+
* @param {any} err whatever was thrown, which need not be an Error
|
|
2919
905
|
* @param {Function|null} handler the four-argument handler to call, or null for the default
|
|
2920
|
-
* @param {
|
|
2921
|
-
* @param {
|
|
906
|
+
* @param {Request} request
|
|
907
|
+
* @param {Response} response
|
|
2922
908
|
*/
|
|
2923
909
|
_handleError(err, handler, request, response) {
|
|
2924
910
|
if (handler) {
|
|
@@ -2936,6 +922,14 @@ module.exports = class Router extends EventEmitter {
|
|
|
2936
922
|
}
|
|
2937
923
|
}
|
|
2938
924
|
logError(this, err);
|
|
925
|
+
// no error page can follow a head that is out, so express's final handler closes the
|
|
926
|
+
// connection instead, and leaves a response that was already ended as it is
|
|
927
|
+
if (response.headersSent) {
|
|
928
|
+
if (!response.finished) {
|
|
929
|
+
response.destroy();
|
|
930
|
+
}
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
2939
933
|
if (response.statusCode === 200) {
|
|
2940
934
|
// the status the error carries, as express's own final handler reads it: a body that
|
|
2941
935
|
// was too large or a request cut short is the client's 4xx, not a 500 from here
|
|
@@ -2949,7 +943,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
2949
943
|
* The HTML for an error, which in production says only what the status means rather than what
|
|
2950
944
|
* went wrong, so a stack trace does not reach the client.
|
|
2951
945
|
*
|
|
2952
|
-
* @param {
|
|
946
|
+
* @param {unknown} err whatever was thrown, which need not be an Error
|
|
2953
947
|
* @param {number} statusCode
|
|
2954
948
|
* @param {boolean} [checkEnv] whether production should redact it
|
|
2955
949
|
* @returns {string}
|
|
@@ -3026,10 +1020,10 @@ module.exports = class Router extends EventEmitter {
|
|
|
3026
1020
|
* and from any mergeParams parents, and the app.param callbacks for the parameters this route
|
|
3027
1021
|
* matched that this request has not already seen.
|
|
3028
1022
|
*
|
|
3029
|
-
* @param {
|
|
3030
|
-
* @param {
|
|
3031
|
-
* @param {
|
|
3032
|
-
* @returns {
|
|
1023
|
+
* @param {Request} req
|
|
1024
|
+
* @param {Response} res
|
|
1025
|
+
* @param {RouteEntry} route
|
|
1026
|
+
* @returns {Promise<true|"route">|true|"route"} a promise only when a param callback is involved
|
|
3033
1027
|
*/
|
|
3034
1028
|
_preprocessRequest(req, res, route) {
|
|
3035
1029
|
// express sets this inside Route#dispatch, so only a route ever writes one: a middleware
|
|
@@ -3081,11 +1075,10 @@ module.exports = class Router extends EventEmitter {
|
|
|
3081
1075
|
// the route's own router's callbacks: an optimized chain is walked by the app even when it
|
|
3082
1076
|
// ends in a mounted router's route
|
|
3083
1077
|
//
|
|
3084
|
-
// A route an OPTIONS request reaches only to have its verb counted
|
|
3085
|
-
//
|
|
3086
|
-
//
|
|
3087
|
-
//
|
|
3088
|
-
// matched whatever its method is, which is what answers 400 for a malformed escape.
|
|
1078
|
+
// A route an OPTIONS request reaches only to have its verb counted is not a route this
|
|
1079
|
+
// request runs, and express does not run its app.param() callbacks for it. Same condition
|
|
1080
|
+
// as the OPTIONS branch in runRoute. The decoding above happens either way, because express
|
|
1081
|
+
// decodes a layer whose path matched whatever its method is
|
|
3089
1082
|
const paramCallbacks = route.paramCallbacks;
|
|
3090
1083
|
if (paramCallbacks.size > 0 && !(req._isOptions && !route.all && route.method !== "OPTIONS")) {
|
|
3091
1084
|
return this._runParamCallbacks(req, res, route, paramCallbacks);
|
|
@@ -3098,8 +1091,8 @@ module.exports = class Router extends EventEmitter {
|
|
|
3098
1091
|
* route whose path matched and whose method did not, which express still decodes: the 400 it
|
|
3099
1092
|
* answers there is what this reproduces.
|
|
3100
1093
|
*
|
|
3101
|
-
* @param {
|
|
3102
|
-
* @param {
|
|
1094
|
+
* @param {RouteEntry} route
|
|
1095
|
+
* @param {Request} req
|
|
3103
1096
|
* @returns {boolean}
|
|
3104
1097
|
*/
|
|
3105
1098
|
_paramsFailToDecode(route, req) {
|
|
@@ -3118,13 +1111,13 @@ module.exports = class Router extends EventEmitter {
|
|
|
3118
1111
|
* Runs the app.param() callbacks for the parameters this route matched, and says whether the
|
|
3119
1112
|
* route may run.
|
|
3120
1113
|
*
|
|
3121
|
-
* Express calls one once per value and not once per request: the same name
|
|
3122
|
-
*
|
|
3123
|
-
*
|
|
1114
|
+
* Express calls one once per value and not once per request: the same name with a different
|
|
1115
|
+
* value calls it again, and a value already seen restores what that call left in req.params,
|
|
1116
|
+
* its deferral or its error included, without running anything.
|
|
3124
1117
|
*
|
|
3125
|
-
* @param {
|
|
3126
|
-
* @param {
|
|
3127
|
-
* @param {
|
|
1118
|
+
* @param {Request} req
|
|
1119
|
+
* @param {Response} res
|
|
1120
|
+
* @param {RouteEntry} route
|
|
3128
1121
|
* @param {Map<string, Function[]>} paramCallbacks the owning router's, which is also the key of
|
|
3129
1122
|
* its own cache: two routers that declare the same parameter each call their own
|
|
3130
1123
|
* @returns {Promise<true|"route">|true}
|
|
@@ -3247,7 +1240,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
3247
1240
|
|
|
3248
1241
|
/**
|
|
3249
1242
|
* Resolves with the route that answered, or false when nothing matched.
|
|
3250
|
-
* @returns {Promise<
|
|
1243
|
+
* @returns {Promise<RouteEntry|false>}
|
|
3251
1244
|
*/
|
|
3252
1245
|
_routeRequest(req, res, startIndex = 0, routes = this._routes, skipCheck = false, skipUntil) {
|
|
3253
1246
|
return new Promise((resolve, reject) => {
|
|
@@ -3260,8 +1253,8 @@ module.exports = class Router extends EventEmitter {
|
|
|
3260
1253
|
* and nativeFail defer their epilogues to the microtask the await used to resume on, so the
|
|
3261
1254
|
* visible order holds.
|
|
3262
1255
|
*
|
|
3263
|
-
* @param {
|
|
3264
|
-
* @param {
|
|
1256
|
+
* @param {Request} req
|
|
1257
|
+
* @param {Response} res
|
|
3265
1258
|
*/
|
|
3266
1259
|
_routeRequestDirect(req, res) {
|
|
3267
1260
|
const walk = new Walk(this, req, res, this._routes, false, undefined, nativeDone, nativeFail);
|
|
@@ -3313,21 +1306,20 @@ module.exports = class Router extends EventEmitter {
|
|
|
3313
1306
|
settingsEpoch.n++;
|
|
3314
1307
|
}
|
|
3315
1308
|
}
|
|
3316
|
-
|
|
1309
|
+
// a handler in the path position was moved to the callbacks above
|
|
1310
|
+
this.createRoute("USE", /** @type {string|RegExp|(string|RegExp)[]} */ (path), this, ...callbacks);
|
|
3317
1311
|
return this;
|
|
3318
1312
|
}
|
|
3319
1313
|
|
|
3320
1314
|
/**
|
|
3321
|
-
* Registers a websocket route, which
|
|
1315
|
+
* Registers a websocket route, which uWS serves itself.
|
|
3322
1316
|
*
|
|
3323
|
-
* The behavior is
|
|
3324
|
-
* `upgrade(req, res)` of this project's own shape, which runs before the handshake with a
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
1317
|
+
* The behavior is uWS's, settings and socket handlers alike, plus one addition: an
|
|
1318
|
+
* `upgrade(req, res)` of this project's own shape, which runs before the handshake with a real
|
|
1319
|
+
* request and response. Answering with the response declines the socket; returning a promise
|
|
1320
|
+
* holds the handshake until it settles.
|
|
3327
1321
|
*
|
|
3328
|
-
* The request lives as long as the socket and reaches every handler as `ws.req
|
|
3329
|
-
* the upgrade learned about the client, and anything it hangs on the request, is there when
|
|
3330
|
-
* a message arrives.
|
|
1322
|
+
* The request lives as long as the socket and reaches every handler as `ws.req`.
|
|
3331
1323
|
*
|
|
3332
1324
|
* @example
|
|
3333
1325
|
* app.ws("/room/:id", {
|
|
@@ -3340,7 +1332,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
3340
1332
|
* });
|
|
3341
1333
|
*
|
|
3342
1334
|
* @param {string} path a literal path, or one whose parameters are whole segments
|
|
3343
|
-
* @param {
|
|
1335
|
+
* @param {Record<string, unknown>} behavior uWS's WebSocketBehavior, plus the optional `upgrade` above
|
|
3344
1336
|
* @returns {this}
|
|
3345
1337
|
*/
|
|
3346
1338
|
ws(path, behavior) {
|
|
@@ -3376,7 +1368,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
3376
1368
|
this._pendingGroupMethods = groupMethods;
|
|
3377
1369
|
this._pendingGroupStack = groupStack;
|
|
3378
1370
|
try {
|
|
3379
|
-
return this.createRoute(method, path,
|
|
1371
|
+
return this.createRoute(method, path, fns, ...callbacks);
|
|
3380
1372
|
} finally {
|
|
3381
1373
|
this._pendingGroup = undefined;
|
|
3382
1374
|
this._pendingGroupMethods = undefined;
|
|
@@ -3394,9 +1386,9 @@ module.exports = class Router extends EventEmitter {
|
|
|
3394
1386
|
* Answers with an error page, locked down: no sniffing, no ETag, and a content security policy
|
|
3395
1387
|
* that allows nothing, since the page carries a message that came from somewhere else.
|
|
3396
1388
|
*
|
|
3397
|
-
* @param {
|
|
3398
|
-
* @param {
|
|
3399
|
-
* @param {
|
|
1389
|
+
* @param {Request} request
|
|
1390
|
+
* @param {Response} response
|
|
1391
|
+
* @param {unknown} err whatever was thrown, which need not be an Error
|
|
3400
1392
|
* @param {boolean} [checkEnv] whether production should redact it
|
|
3401
1393
|
*/
|
|
3402
1394
|
_sendErrorPage(request, response, err, checkEnv = false) {
|
|
@@ -3416,13 +1408,13 @@ module.exports = class Router extends EventEmitter {
|
|
|
3416
1408
|
* answering when the head has already been written, which is what node's setHeader would do
|
|
3417
1409
|
* and what lets an error handler see it, as in Express.
|
|
3418
1410
|
*
|
|
3419
|
-
* @param {
|
|
3420
|
-
* @param {
|
|
1411
|
+
* @param {Request} request
|
|
1412
|
+
* @param {Response} response
|
|
3421
1413
|
* @param {Set<string>} methods the verbs the answering router knows, which are its own
|
|
3422
1414
|
*/
|
|
3423
1415
|
_sendOptionsReply(request, response, methods) {
|
|
3424
|
-
if (response.
|
|
3425
|
-
throw
|
|
1416
|
+
if (response.headersSent) {
|
|
1417
|
+
throw headersSentError("set");
|
|
3426
1418
|
}
|
|
3427
1419
|
// Express 5 sorts the methods and joins them with ", ", so the header reads the same
|
|
3428
1420
|
// regardless of the order the routes happened to be registered in
|
|
@@ -3440,14 +1432,15 @@ module.exports = class Router extends EventEmitter {
|
|
|
3440
1432
|
* OPTIONS reply, or with a 404. The native chain, the app's catch-all handler and the node shim
|
|
3441
1433
|
* all end here, so that they end a request the same way.
|
|
3442
1434
|
*
|
|
3443
|
-
* @param {
|
|
3444
|
-
* @param {
|
|
1435
|
+
* @param {Request} request
|
|
1436
|
+
* @param {Response} response
|
|
3445
1437
|
*/
|
|
3446
1438
|
_endUnmatched(request, response) {
|
|
3447
1439
|
if (request._error) {
|
|
3448
1440
|
return this._handleError(request._error, null, request, response);
|
|
3449
1441
|
}
|
|
3450
|
-
|
|
1442
|
+
// the null test costs nothing outside an OPTIONS request, and only one carries the set
|
|
1443
|
+
if (request._isOptions && request._matchedMethods !== null && request._matchedMethods.size > 0) {
|
|
3451
1444
|
try {
|
|
3452
1445
|
this._sendOptionsReply(request, response, request._matchedMethods);
|
|
3453
1446
|
} catch (err) {
|
|
@@ -3469,6 +1462,9 @@ module.exports = class Router extends EventEmitter {
|
|
|
3469
1462
|
// The verb methods go on the prototype, not on each instance. As own arrows they closed over the
|
|
3470
1463
|
// instance they were built on, so express.Router().post(...) answered with the object the callable
|
|
3471
1464
|
// was copied from. One closure per name for the process instead of one per router, too.
|
|
1465
|
+
// the optimizer needs the class to tell a mounted router from a plain handler
|
|
1466
|
+
useRouterClass(module.exports);
|
|
1467
|
+
|
|
3472
1468
|
for (const method of methods) {
|
|
3473
1469
|
module.exports.prototype[method] = function (path, ...callbacks) {
|
|
3474
1470
|
return this.createRoute(method, path, this, ...callbacks);
|