fulmine.js 5.19.2 → 5.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/router.js CHANGED
@@ -22,10 +22,8 @@ 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,
31
29
  EMPTY_REGEX,
@@ -34,1527 +32,47 @@ const {
34
32
  const Response = require("./response.js");
35
33
  const Request = require("./request.js");
36
34
  const { EventEmitter } = require("tseep");
37
- const compileDeclarative = require("./declarative.js");
38
35
  const statuses = require("statuses");
39
36
  const { METHODS } = require("http");
40
37
  const { isNodeRequest, serveNodeRequest } = require("./node-shim.js");
41
- const { chainUsage, kGetSafe } = require("./usage.js");
38
+ const { kGetSafe } = require("./usage.js");
42
39
  const { checkBehavior } = require("./websocket.js");
43
-
44
- // whether a registered path could be asked for in another case, which is what decides whether the
45
- // native router can be trusted to prefer it, see _optimizeRoute
46
- const HAS_LETTER = /[a-zA-Z]/;
40
+ const { HotSettings, settingsWriteTraps } = require("./hot-settings.js");
41
+ const Walk = require("./walk.js");
42
+ const { useRouterClass, optimizeRoute, compileOptimizedRoutes, registerUwsRoute } = require("./optimizer.js");
43
+ const {
44
+ layerFor,
45
+ routeLayer,
46
+ nativeDone,
47
+ nativeFail,
48
+ ownParamNames,
49
+ mergesParams,
50
+ EMPTY_INDICES,
51
+ buildLiteralIndex,
52
+ firstAtLeast,
53
+ mergeParams,
54
+ adoptPlainRequest,
55
+ logError,
56
+ onNativeAborted,
57
+ checkHandlers,
58
+ CALLBACK_PLAIN,
59
+ CALLBACK_ERROR,
60
+ CALLBACK_ROUTER,
61
+ raiseDecodeFailure,
62
+ stepsOver,
63
+ useApp,
64
+ methods,
65
+ callablePrototypeFor,
66
+ generateErrorPageHtml
67
+ } = require("./router-utils.js");
68
+
69
+ /** @typedef {import("./router-utils.js").RouteEntry} RouteEntry */
47
70
 
48
71
  // hands out one number per app.route(), so the routes it creates know they belong together
49
72
  let routeGroups = 0;
50
73
 
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
74
  let routeKey = 0;
134
75
 
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, "&amp;")
1538
- .replace(/</g, "&lt;")
1539
- .replace(/>/g, "&gt;")
1540
- .replace(/"/g, "&quot;")
1541
- .replace(/'/g, "&#39;")
1542
- .replace(/\n/g, "<br>")
1543
- .replace(/ {2}/g, " &nbsp;");
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
76
  module.exports = class Router extends EventEmitter {
1559
77
  /**
1560
78
  * The router or application this one is mounted on, undefined until it is.
@@ -1660,15 +178,17 @@ module.exports = class Router extends EventEmitter {
1660
178
  this.mountpath = "/";
1661
179
  // The settings twice: the plain object everything inside here reads, and the Proxy the
1662
180
  // outside gets. Express lets an application write app.settings["x"] straight, which set()
1663
- // never sees, so the hot copies in _hot() would keep answering the old value until an
1664
- // unrelated set() happened to bump the epoch and the change landed by surprise. The trap
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.
181
+ // never sees, so the hot copies in _hot() kept answering the old value. The trap bumps the
182
+ // epoch on the spot. Reading through a Proxy costs about 20ns, so the inside never does
1667
183
  this._settings = settings;
1668
184
  this.settings = new Proxy(settings, settingsWriteTraps);
1669
185
  // the base classes; an Application replaces these with its own per-app subclasses, and a
1670
186
  // plain router has no request/response prototype layer, as in Express
187
+ // Typed loosely because an Application replaces both with per-app subclasses of its own,
188
+ // and a field declared as the base class would not accept one under strictFunctionTypes
189
+ /** @type {any} */
1671
190
  this._request = Request;
191
+ /** @type {any} */
1672
192
  this._response = Response;
1673
193
 
1674
194
  if (typeof settings.caseSensitive !== "undefined") {
@@ -1704,8 +224,8 @@ module.exports = class Router extends EventEmitter {
1704
224
  * Routes a request through this router, as Express's app.handle and router.handle do. next() is
1705
225
  * called when nothing answered, so an unmatched request goes back to whoever is running this.
1706
226
  *
1707
- * @param {any} req
1708
- * @param {any} res
227
+ * @param {any} req a Request, or the plain object express's own router tests drive it with
228
+ * @param {any} res a Response, or whatever the caller is serving with
1709
229
  * @param {(err?: any) => void} [next]
1710
230
  * @returns {Promise<void>}
1711
231
  */
@@ -1715,11 +235,9 @@ module.exports = class Router extends EventEmitter {
1715
235
  return serveNodeRequest(this, req, /** @type {any} */ (res), next);
1716
236
  }
1717
237
  // 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 and not the
1719
- // settings of whatever handed the request over. A plain router is not an app and leaves it
1720
- // alone, which is what Express's router.handle does too.
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
238
+ // req.app.get("view engine") inside a sub-app reads the sub-app's settings. A plain router
239
+ // is not an app and leaves it alone, as Express's router.handle does.
240
+ // a plain object, which is how express's router can be driven and how its own tests drive it
1723
241
  if (req._opPath === undefined) {
1724
242
  if (typeof req.url !== "string" || req.url === "") {
1725
243
  // express reads the path with parseurl, which answers nothing for these, and it
@@ -1854,15 +372,15 @@ module.exports = class Router extends EventEmitter {
1854
372
  * what a nested router strips off the path before matching against it. Cached per stack, since
1855
373
  * the same mount chain is walked by every request that reaches it.
1856
374
  *
1857
- * @param {any} req
375
+ * @param {Request} req
1858
376
  * @returns {RegExp}
1859
377
  */
1860
378
  getFullMountpath(req) {
1861
379
  // path-less app.use() pushes "", so a stack of only those joins to "" no matter how deep it is.
1862
380
  // patternToRegex("", true) is EMPTY_REGEX, so this returns exactly what the join path would,
1863
- // without walking the whole stack on every hop. _stackMounted first: it is 0 whenever
1864
- // _stack is still null, and req.baseUrl asks from unmounted requests too
1865
- if (req._stackMounted === 0 || req._stack.length === 0) {
381
+ // The null first: _stack stays null until a mount is entered, and this is reachable from
382
+ // an unmounted request. It used to read a counter that no longer exists, so it threw
383
+ if (req._stack === null || req._stack.length === 0) {
1866
384
  return EMPTY_REGEX;
1867
385
  }
1868
386
  const fullStack = req._stack.join("");
@@ -1893,14 +411,12 @@ module.exports = class Router extends EventEmitter {
1893
411
  /**
1894
412
  * The generic scan over this router's own table, driven by the literal index: only the routes
1895
413
  * registered for this exact path, plus every non-literal route, are visited, in registration
1896
- * order, and each visited one still answers through the same method gate and _pathMatches the
1897
- * plain loop used. The routes skipped are exactly the literals whose string compare provably
1898
- * fails, so the first index this answers is the one the plain loop found.
414
+ * order, and each one still answers through the same method gate and _pathMatches the plain
415
+ * loop used. The routes skipped are exactly the literals whose string compare provably fails.
1899
416
  *
1900
- * Runs after _freezeRoutingFlags, which is what makes _caseFlag and _strictFlag readable here
1901
- * and the lazily built index stable.
417
+ * Runs after _freezeRoutingFlags, which is what makes _caseFlag and _strictFlag readable here.
1902
418
  *
1903
- * @param {any} req
419
+ * @param {Request} req
1904
420
  * @param {number} startIndex where to resume the scan
1905
421
  * @param {boolean} mayFailDecode whether the path carries a percent escape
1906
422
  * @returns {number} the index of the route to enter, or the table length for none
@@ -1979,8 +495,8 @@ module.exports = class Router extends EventEmitter {
1979
495
  * makes a route eligible for the native router; anything carrying a parameter or a wildcard was
1980
496
  * turned into a regular expression when it was registered.
1981
497
  *
1982
- * @param {any} route
1983
- * @param {any} req
498
+ * @param {RouteEntry} route
499
+ * @param {Request} req
1984
500
  * @returns {boolean}
1985
501
  */
1986
502
  _pathMatches(route, req) {
@@ -2038,14 +554,13 @@ module.exports = class Router extends EventEmitter {
2038
554
 
2039
555
  /**
2040
556
  * 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
- * application's endpoints walk this, and so do tests that reach in for a single handler by
2043
- * name, which is how LibreChat pulls one middleware out of its router to exercise it.
557
+ * route, and the route's own handlers under `route.stack`. Libraries that list an application's
558
+ * endpoints walk this, and so do tests that reach in for a handler by name, which is how
559
+ * LibreChat pulls one middleware out of its router.
2044
560
  *
2045
- * A view, built from the routes this router holds and rebuilt on every read, so it follows
2046
- * what has been registered. It is not the router's own storage: pushing a layer onto it, or
2047
- * splicing one out, moves nothing. The layer objects themselves are kept, so a caller that
2048
- * compares identities across two reads gets the same answer Express gives.
561
+ * A view, rebuilt on every read, not the router's own storage: pushing a layer onto it or
562
+ * splicing one out moves nothing. The layer objects are kept, so identities compare across two
563
+ * reads the way they do in Express.
2049
564
  *
2050
565
  * @returns {any[]}
2051
566
  */
@@ -2079,12 +594,10 @@ module.exports = class Router extends EventEmitter {
2079
594
  method = method.toUpperCase();
2080
595
  callbacks = callbacks.flat(Infinity);
2081
596
  checkHandlers(callbacks);
2082
- // What express hangs off req.route as its methods, and the three registrations do not
2083
- // agree on it: app.all() registers every verb one at a time, so the map names all of
2084
- // them; router.all() and app.route().all() mark the route _all instead; and everything
2085
- // hung off one app.route() shares one map, since express builds one Route for the lot.
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.
597
+ // What express hangs off req.route as its methods, and the three registrations disagree:
598
+ // app.all() registers every verb one at a time, so the map names all of them; router.all()
599
+ // and app.route().all() mark the route _all instead; everything hung off one app.route()
600
+ // shares one map. Built in node's own order, so it reads back key for key as express's does
2088
601
  let methodMap;
2089
602
  let stack;
2090
603
  if (method !== "USE") {
@@ -2251,327 +764,23 @@ module.exports = class Router extends EventEmitter {
2251
764
  }
2252
765
 
2253
766
  /**
2254
- * The chain a request would walk to reach this route, or false when it cannot be known ahead of
2255
- * time. The native router jumps straight to the route, so everything registered before it that
2256
- * could also match has to be in the chain, in order.
767
+ * The chain a request would walk to reach this route, see optimizeRoute in optimizer.js. Kept
768
+ * as a method because a mounted router is asked for its own through it.
2257
769
  *
2258
- * @param {any} route
770
+ * @param {RouteEntry} route
2259
771
  * @param {any[]} routes every route of this router, in registration order
2260
772
  * @returns {any[]|false} the chain, ending in the route itself
2261
773
  */
2262
774
  _optimizeRoute(route, routes) {
2263
- const optimizedPath = [];
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;
775
+ return optimizeRoute(this, route, routes);
2413
776
  }
2414
777
 
2415
778
  /**
2416
- * Hands every route reachable by path alone to the native uWS router, walking into mounted
2417
- * routers and carrying their prefix down. Runs once, when the app starts listening, since it
2418
- * needs every route to have been registered first.
779
+ * Hands every route reachable by path alone to the native uWS router, see
780
+ * compileOptimizedRoutes in optimizer.js. Runs once, when the app starts listening.
2419
781
  */
2420
782
  _compileOptimizedRoutes() {
2421
- if (!this.uwsApp) {
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, "", [], []);
783
+ compileOptimizedRoutes(this);
2575
784
  }
2576
785
 
2577
786
  /**
@@ -2579,7 +788,7 @@ module.exports = class Router extends EventEmitter {
2579
788
  * request does whichever path serves it. The response rides back as request.res: returning
2580
789
  * a `{ request, response }` pair was one throwaway object per request.
2581
790
  *
2582
- * @param {any} res uWS response
791
+ * @param {any} res uWS response, which the shipped typings do not describe
2583
792
  * @param {any} req uWS request, readable only during this call
2584
793
  * @param {any} [preset] a literal registration's constants, see nativePreset
2585
794
  * @param {any} [skipHolder] the object a granted header skip lives on: the preset itself
@@ -2598,19 +807,17 @@ module.exports = class Router extends EventEmitter {
2598
807
  * Refuses a request whose framing cannot be trusted and hangs up without answering. No route
2599
808
  * runs, so nothing downstream can be reached by one.
2600
809
  *
2601
- * Hanging up is the whole point: uWS has already read what followed the body it believed in as
2602
- * a second, pipelined request, and it dispatches that one unless the socket goes. Node answers
810
+ * Hanging up is the point: uWS has already read what followed the body it believed in as a
811
+ * second, pipelined request, and it dispatches that one unless the socket goes. Node answers
2603
812
  * 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 any of writeStatus, end or endWithoutBody
2605
- * completes it. Measured every combination, and delivering the 400 always let the smuggled
2606
- * request through, so the close wins and the client gets nothing. Nothing legitimate sends two
2607
- * content-lengths, so there is no well-behaved client to explain it to.
813
+ * response is closed rather than completed, and writeStatus, end and endWithoutBody all
814
+ * complete it. Every combination was measured and delivering the 400 always let the smuggled
815
+ * request through, so the close wins.
2608
816
  *
2609
817
  * 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 is what
2611
- * takes it back out again.
818
+ * response into its pending list after the base call, and the 'close' emitted here takes it out.
2612
819
  *
2613
- * @param {any} response
820
+ * @param {Response} response
2614
821
  */
2615
822
  _refuseRequest(response) {
2616
823
  response.finished = true;
@@ -2623,8 +830,8 @@ module.exports = class Router extends EventEmitter {
2623
830
  * for a response that outlives its handler callback: the native handler arms it in its
2624
831
  * finally when the answer is still pending, which on a synchronous route it never is.
2625
832
  *
2626
- * @param {any} res uWS response
2627
- * @param {any} response
833
+ * @param {any} res uWS response, which the shipped typings do not describe
834
+ * @param {Response} response
2628
835
  */
2629
836
  _armAbort(res, response) {
2630
837
  res.onAborted(onNativeAborted.bind(response));
@@ -2638,7 +845,7 @@ module.exports = class Router extends EventEmitter {
2638
845
  * mount or a pattern of an unknown shape counts as an overlap; two paths µWS could match itself
2639
846
  * are compared segment by segment.
2640
847
  *
2641
- * @param {any} route
848
+ * @param {RouteEntry} route
2642
849
  * @param {any[]} routes every route of the router this one belongs to
2643
850
  * @returns {boolean}
2644
851
  */
@@ -2670,244 +877,14 @@ module.exports = class Router extends EventEmitter {
2670
877
  }
2671
878
 
2672
879
  /**
2673
- * Hands one route to µWS, along with the chain of everything that has to run in front of it,
2674
- * and records that chain on the route so the handler can walk it.
880
+ * Registers one route with uWS, see registerUwsRoute in optimizer.js. Kept as a method because
881
+ * the optimizer tests replace it to see which routes went native.
2675
882
  *
2676
- * @param {any} route
2677
- * @param {any[]} optimizedPath the routes to run, in order, ending with this one
883
+ * @param {RouteEntry} route
884
+ * @param {any[]} optimizedPath the chain the route was optimized with
2678
885
  */
2679
886
  _registerUwsRoute(route, optimizedPath) {
2680
- let method = route.method.toLowerCase();
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
- }
887
+ registerUwsRoute(this, route, optimizedPath);
2911
888
  }
2912
889
 
2913
890
  /**
@@ -2915,10 +892,10 @@ module.exports = class Router extends EventEmitter {
2915
892
  * Passing something to next() from an error handler clears the error and resumes routing,
2916
893
  * which is how Express lets a handler decide the error was not fatal.
2917
894
  *
2918
- * @param {any} err
895
+ * @param {any} err whatever was thrown, which need not be an Error
2919
896
  * @param {Function|null} handler the four-argument handler to call, or null for the default
2920
- * @param {any} request
2921
- * @param {any} response
897
+ * @param {Request} request
898
+ * @param {Response} response
2922
899
  */
2923
900
  _handleError(err, handler, request, response) {
2924
901
  if (handler) {
@@ -2949,7 +926,7 @@ module.exports = class Router extends EventEmitter {
2949
926
  * The HTML for an error, which in production says only what the status means rather than what
2950
927
  * went wrong, so a stack trace does not reach the client.
2951
928
  *
2952
- * @param {any} err
929
+ * @param {any} err whatever was thrown, which need not be an Error
2953
930
  * @param {number} statusCode
2954
931
  * @param {boolean} [checkEnv] whether production should redact it
2955
932
  * @returns {string}
@@ -3026,10 +1003,10 @@ module.exports = class Router extends EventEmitter {
3026
1003
  * and from any mergeParams parents, and the app.param callbacks for the parameters this route
3027
1004
  * matched that this request has not already seen.
3028
1005
  *
3029
- * @param {any} req
3030
- * @param {any} res
3031
- * @param {any} route
3032
- * @returns {any} a promise only when a param callback is involved
1006
+ * @param {Request} req
1007
+ * @param {Response} res
1008
+ * @param {RouteEntry} route
1009
+ * @returns {Promise<true|"route">|true|"route"} a promise only when a param callback is involved
3033
1010
  */
3034
1011
  _preprocessRequest(req, res, route) {
3035
1012
  // express sets this inside Route#dispatch, so only a route ever writes one: a middleware
@@ -3081,11 +1058,10 @@ module.exports = class Router extends EventEmitter {
3081
1058
  // the route's own router's callbacks: an optimized chain is walked by the app even when it
3082
1059
  // ends in a mounted router's route
3083
1060
  //
3084
- // A route an OPTIONS request reaches only to have its verb counted for the automatic reply
3085
- // is not a route this request runs, and express does not run its app.param() callbacks for
3086
- // it. The same condition runRoute counts the verb under, see the OPTIONS branch there. The
3087
- // decoding above still happens either way, because express decodes a layer whose path
3088
- // matched whatever its method is, which is what answers 400 for a malformed escape.
1061
+ // A route an OPTIONS request reaches only to have its verb counted is not a route this
1062
+ // request runs, and express does not run its app.param() callbacks for it. Same condition
1063
+ // as the OPTIONS branch in runRoute. The decoding above happens either way, because express
1064
+ // decodes a layer whose path matched whatever its method is
3089
1065
  const paramCallbacks = route.paramCallbacks;
3090
1066
  if (paramCallbacks.size > 0 && !(req._isOptions && !route.all && route.method !== "OPTIONS")) {
3091
1067
  return this._runParamCallbacks(req, res, route, paramCallbacks);
@@ -3098,8 +1074,8 @@ module.exports = class Router extends EventEmitter {
3098
1074
  * route whose path matched and whose method did not, which express still decodes: the 400 it
3099
1075
  * answers there is what this reproduces.
3100
1076
  *
3101
- * @param {any} route
3102
- * @param {any} req
1077
+ * @param {RouteEntry} route
1078
+ * @param {Request} req
3103
1079
  * @returns {boolean}
3104
1080
  */
3105
1081
  _paramsFailToDecode(route, req) {
@@ -3118,13 +1094,13 @@ module.exports = class Router extends EventEmitter {
3118
1094
  * Runs the app.param() callbacks for the parameters this route matched, and says whether the
3119
1095
  * route may run.
3120
1096
  *
3121
- * Express calls one once per value and not once per request: the same name matched with a
3122
- * different value calls it again, and a value it has already seen restores whatever that call
3123
- * left in req.params, its deferral or its error included, without running anything.
1097
+ * Express calls one once per value and not once per request: the same name with a different
1098
+ * value calls it again, and a value already seen restores what that call left in req.params,
1099
+ * its deferral or its error included, without running anything.
3124
1100
  *
3125
- * @param {any} req
3126
- * @param {any} res
3127
- * @param {any} route
1101
+ * @param {Request} req
1102
+ * @param {Response} res
1103
+ * @param {RouteEntry} route
3128
1104
  * @param {Map<string, Function[]>} paramCallbacks the owning router's, which is also the key of
3129
1105
  * its own cache: two routers that declare the same parameter each call their own
3130
1106
  * @returns {Promise<true|"route">|true}
@@ -3260,8 +1236,8 @@ module.exports = class Router extends EventEmitter {
3260
1236
  * and nativeFail defer their epilogues to the microtask the await used to resume on, so the
3261
1237
  * visible order holds.
3262
1238
  *
3263
- * @param {any} req
3264
- * @param {any} res
1239
+ * @param {Request} req
1240
+ * @param {Response} res
3265
1241
  */
3266
1242
  _routeRequestDirect(req, res) {
3267
1243
  const walk = new Walk(this, req, res, this._routes, false, undefined, nativeDone, nativeFail);
@@ -3318,16 +1294,14 @@ module.exports = class Router extends EventEmitter {
3318
1294
  }
3319
1295
 
3320
1296
  /**
3321
- * Registers a websocket route, which µWS serves itself.
1297
+ * Registers a websocket route, which uWS serves itself.
3322
1298
  *
3323
- * The behavior is µWS's, settings and socket handlers alike, plus one addition: an
3324
- * `upgrade(req, res)` of this project's own shape, which runs before the handshake with a
3325
- * real request and response. Answering with the response declines the socket, which is how
3326
- * a check refuses one; returning a promise holds the handshake until it settles.
1299
+ * The behavior is uWS's, settings and socket handlers alike, plus one addition: an
1300
+ * `upgrade(req, res)` of this project's own shape, which runs before the handshake with a real
1301
+ * request and response. Answering with the response declines the socket; returning a promise
1302
+ * holds the handshake until it settles.
3327
1303
  *
3328
- * The request lives as long as the socket and reaches every handler as `ws.req`, so what
3329
- * the upgrade learned about the client, and anything it hangs on the request, is there when
3330
- * a message arrives.
1304
+ * The request lives as long as the socket and reaches every handler as `ws.req`.
3331
1305
  *
3332
1306
  * @example
3333
1307
  * app.ws("/room/:id", {
@@ -3340,7 +1314,7 @@ module.exports = class Router extends EventEmitter {
3340
1314
  * });
3341
1315
  *
3342
1316
  * @param {string} path a literal path, or one whose parameters are whole segments
3343
- * @param {object} behavior µWS's WebSocketBehavior, plus the optional `upgrade` above
1317
+ * @param {object} behavior uWS's WebSocketBehavior, plus the optional `upgrade` above
3344
1318
  * @returns {this}
3345
1319
  */
3346
1320
  ws(path, behavior) {
@@ -3394,9 +1368,9 @@ module.exports = class Router extends EventEmitter {
3394
1368
  * Answers with an error page, locked down: no sniffing, no ETag, and a content security policy
3395
1369
  * that allows nothing, since the page carries a message that came from somewhere else.
3396
1370
  *
3397
- * @param {any} request
3398
- * @param {any} response
3399
- * @param {any} err
1371
+ * @param {Request} request
1372
+ * @param {Response} response
1373
+ * @param {any} err whatever was thrown, which need not be an Error
3400
1374
  * @param {boolean} [checkEnv] whether production should redact it
3401
1375
  */
3402
1376
  _sendErrorPage(request, response, err, checkEnv = false) {
@@ -3416,8 +1390,8 @@ module.exports = class Router extends EventEmitter {
3416
1390
  * answering when the head has already been written, which is what node's setHeader would do
3417
1391
  * and what lets an error handler see it, as in Express.
3418
1392
  *
3419
- * @param {any} request
3420
- * @param {any} response
1393
+ * @param {Request} request
1394
+ * @param {Response} response
3421
1395
  * @param {Set<string>} methods the verbs the answering router knows, which are its own
3422
1396
  */
3423
1397
  _sendOptionsReply(request, response, methods) {
@@ -3440,14 +1414,15 @@ module.exports = class Router extends EventEmitter {
3440
1414
  * OPTIONS reply, or with a 404. The native chain, the app's catch-all handler and the node shim
3441
1415
  * all end here, so that they end a request the same way.
3442
1416
  *
3443
- * @param {any} request
3444
- * @param {any} response
1417
+ * @param {Request} request
1418
+ * @param {Response} response
3445
1419
  */
3446
1420
  _endUnmatched(request, response) {
3447
1421
  if (request._error) {
3448
1422
  return this._handleError(request._error, null, request, response);
3449
1423
  }
3450
- if (request._isOptions && request._matchedMethods.size > 0) {
1424
+ // the null test costs nothing outside an OPTIONS request, and only one carries the set
1425
+ if (request._isOptions && request._matchedMethods !== null && request._matchedMethods.size > 0) {
3451
1426
  try {
3452
1427
  this._sendOptionsReply(request, response, request._matchedMethods);
3453
1428
  } catch (err) {
@@ -3469,6 +1444,9 @@ module.exports = class Router extends EventEmitter {
3469
1444
  // The verb methods go on the prototype, not on each instance. As own arrows they closed over the
3470
1445
  // instance they were built on, so express.Router().post(...) answered with the object the callable
3471
1446
  // was copied from. One closure per name for the process instead of one per router, too.
1447
+ // the optimizer needs the class to tell a mounted router from a plain handler
1448
+ useRouterClass(module.exports);
1449
+
3472
1450
  for (const method of methods) {
3473
1451
  module.exports.prototype[method] = function (path, ...callbacks) {
3474
1452
  return this.createRoute(method, path, this, ...callbacks);