fulmine.js 5.2.0 → 5.3.0
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/NOTICE +29 -2
- package/README.md +90 -61
- package/package.json +6 -2
- package/src/application.js +67 -34
- package/src/cli.js +302 -5
- package/src/declarative.js +19 -0
- package/src/index.js +2 -0
- package/src/middlewares.js +67 -6
- package/src/node-shim.js +5 -3
- package/src/options.d.ts +16 -0
- package/src/parse-query.js +19 -2
- package/src/request.js +275 -31
- package/src/response.js +52 -7
- package/src/router.js +632 -159
- package/src/types.d.ts +16 -0
- package/src/usage.js +16 -0
- package/src/utils.js +267 -36
- package/src/view.js +2 -0
- package/src/websocket.js +16 -0
- package/src/worker.js +2 -0
package/NOTICE
CHANGED
|
@@ -13,8 +13,35 @@ Fulmine is not affiliated with, endorsed by, or maintained by the authors of
|
|
|
13
13
|
Ultimate Express.
|
|
14
14
|
|
|
15
15
|
This product includes code derived from fast-querystring
|
|
16
|
-
(https://github.com/anonrig/fast-querystring),
|
|
17
|
-
licensed under the MIT License,
|
|
16
|
+
(https://github.com/anonrig/fast-querystring), vendored in src/parse-query.js
|
|
17
|
+
and licensed under the MIT License, which requires the following to travel with
|
|
18
|
+
every copy:
|
|
19
|
+
|
|
20
|
+
Copyright (c) 2022 Yagiz Nizipli
|
|
21
|
+
|
|
22
|
+
Permission is hereby granted, free of charge, to any
|
|
23
|
+
person obtaining a copy of this software and associated
|
|
24
|
+
documentation files (the "Software"), to deal in the
|
|
25
|
+
Software without restriction, including without
|
|
26
|
+
limitation the rights to use, copy, modify, merge,
|
|
27
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
28
|
+
the Software, and to permit persons to whom the Software
|
|
29
|
+
is furnished to do so, subject to the following
|
|
30
|
+
conditions:
|
|
31
|
+
|
|
32
|
+
The above copyright notice and this permission notice
|
|
33
|
+
shall be included in all copies or substantial portions
|
|
34
|
+
of the Software.
|
|
35
|
+
|
|
36
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
37
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
38
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
39
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
40
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
41
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
42
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
43
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
44
|
+
DEALINGS IN THE SOFTWARE.
|
|
18
45
|
|
|
19
46
|
As required by section 4(b) of the Apache License, the following are the
|
|
20
47
|
significant changes made to the original work:
|
package/README.md
CHANGED
|
@@ -21,10 +21,17 @@ There is a command that does that replacing for you, across a whole project, and
|
|
|
21
21
|
npx fulmine migrate --dry-run # say what it would change, change nothing
|
|
22
22
|
npx fulmine migrate # do it
|
|
23
23
|
npx fulmine differences # just the list of what to check by hand
|
|
24
|
+
npx fulmine profile # what listen() decided about each route
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
See [Migrating](#migrating) for what it handles and what it deliberately does not.
|
|
27
28
|
|
|
29
|
+
There is a **[live demo](https://fulmine-demo.fly.dev)**, which is an ordinary Express application:
|
|
30
|
+
real routes, `helmet`, `cors`, `compression`, `express-session` and `morgan` unmodified, and a
|
|
31
|
+
WebSocket chat served by `app.ws()`. It links to [its own source](https://fulmine-demo.fly.dev/source),
|
|
32
|
+
which is [in this repository](./demo). It shows no throughput figure on purpose: it runs on a small
|
|
33
|
+
shared machine, so the number would describe the machine rather than the framework.
|
|
34
|
+
|
|
28
35
|
[](https://www.npmjs.com/package/fulmine.js)
|
|
29
36
|
[](https://nodejs.org)
|
|
30
37
|
[](https://coveralls.io/github/nigrosimone/fulmine.js?branch=main)
|
|
@@ -39,6 +46,7 @@ See [Migrating](#migrating) for what it handles and what it deliberately does no
|
|
|
39
46
|
- [Attribution](#attribution)
|
|
40
47
|
- [Difference from similar projects](#difference-from-similar-projects)
|
|
41
48
|
- [Migrating](#migrating)
|
|
49
|
+
- [When Express is somebody else's dependency](#when-express-is-somebody-elses-dependency)
|
|
42
50
|
- [Docker](#docker)
|
|
43
51
|
- [Differences from Express](#differences-from-express)
|
|
44
52
|
- [Performance tips](#performance-tips)
|
|
@@ -55,8 +63,8 @@ See [Migrating](#migrating) for what it handles and what it deliberately does no
|
|
|
55
63
|
- [Router](#router)
|
|
56
64
|
- [Tested middlewares](#tested-middlewares)
|
|
57
65
|
- [Tested view engines](#tested-view-engines)
|
|
58
|
-
- [
|
|
59
|
-
|
|
66
|
+
- [The demo](https://fulmine-demo.fly.dev)
|
|
67
|
+
- [Working on Fulmine](./CONTRIBUTING.md)
|
|
60
68
|
|
|
61
69
|
## Why this exists
|
|
62
70
|
|
|
@@ -121,22 +129,64 @@ npx fulmine differences # print the list below and change nothing
|
|
|
121
129
|
The command is installed under both `fulmine` and `fulmine.js`. Use `fulmine`: `npx` cannot run a
|
|
122
130
|
command whose name ends in `.js` on Windows, where it exits without a word.
|
|
123
131
|
|
|
132
|
+
### When Express is somebody else's dependency
|
|
133
|
+
|
|
134
|
+
A framework built on Express does not `require("express")` in your code, it requires it in its own,
|
|
135
|
+
so there is nothing for `migrate` to rewrite. Every package manager can answer `express` with this
|
|
136
|
+
package instead, for your project and everything under it:
|
|
137
|
+
|
|
138
|
+
```jsonc
|
|
139
|
+
// npm and its lockfile, in package.json
|
|
140
|
+
{
|
|
141
|
+
"overrides": {
|
|
142
|
+
"express": "npm:fulmine.js@^5"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// pnpm, in package.json
|
|
147
|
+
{
|
|
148
|
+
"pnpm": {
|
|
149
|
+
"overrides": {
|
|
150
|
+
"express": "npm:fulmine.js@^5"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// yarn 1 and berry, in package.json
|
|
156
|
+
{
|
|
157
|
+
"resolutions": {
|
|
158
|
+
"express": "npm:fulmine.js@^5"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Then reinstall, so the lockfile is rewritten: `rm -rf node_modules` and `npm install`, or the
|
|
164
|
+
equivalent for your manager. `npm ls express` should answer `express@npm:fulmine.js`.
|
|
165
|
+
|
|
166
|
+
Two things to know before you do it. The substitution reaches **every** dependency that asks for
|
|
167
|
+
Express, including ones you have never looked at, so run your own tests afterwards and read
|
|
168
|
+
[the differences](#differences-from-express): what a framework does with Express is usually more
|
|
169
|
+
than what an application does. And a package that reaches into `express/lib/...` rather than its
|
|
170
|
+
public surface will not find what it expects, since the files there are ours.
|
|
171
|
+
|
|
172
|
+
Bun is not an option: µWebSockets.js is a native Node addon, and Bun does not load it.
|
|
173
|
+
|
|
124
174
|
## Docker
|
|
125
175
|
|
|
126
176
|
Two things about µWebSockets.js make a Dockerfile that works for Express fail here, and both have easy answers:
|
|
127
177
|
|
|
128
|
-
- **No Alpine, and no Debian bookworm either.** µWebSockets.js ships prebuilt binaries linked against glibc 2.38 or newer. Alpine images use musl, so the binary does not load at all; `node:
|
|
129
|
-
- **`git` must be there when `npm install` runs.** µWebSockets.js is not on npm; it is installed straight from GitHub (`github:uNetworking/uWebSockets.js`), and npm uses git to fetch it. Full images like `node:
|
|
178
|
+
- **No Alpine, and no Debian bookworm either.** µWebSockets.js ships prebuilt binaries linked against glibc 2.38 or newer. Alpine images use musl, so the binary does not load at all; `node:26` and `node:26-slim` are Debian bookworm, whose glibc 2.36 fails at startup with `GLIBC_2.38' not found`. Use the trixie variants: `node:26-trixie-slim` and up.
|
|
179
|
+
- **`git` must be there when `npm install` runs.** µWebSockets.js is not on npm; it is installed straight from GitHub (`github:uNetworking/uWebSockets.js`), and npm uses git to fetch it. Full images like `node:26-trixie` have git; `-slim` ones do not.
|
|
130
180
|
|
|
131
181
|
The clean way to satisfy both is a multi-stage build: install with the full image, run with the slim one.
|
|
132
182
|
|
|
133
183
|
```dockerfile
|
|
134
|
-
FROM node:
|
|
184
|
+
FROM node:26-trixie AS build
|
|
135
185
|
WORKDIR /app
|
|
136
186
|
COPY package*.json ./
|
|
137
187
|
RUN npm ci --omit=dev
|
|
138
188
|
|
|
139
|
-
FROM node:
|
|
189
|
+
FROM node:26-trixie-slim
|
|
140
190
|
WORKDIR /app
|
|
141
191
|
COPY --from=build /app/node_modules ./node_modules
|
|
142
192
|
COPY . .
|
|
@@ -144,7 +194,7 @@ EXPOSE 3000
|
|
|
144
194
|
CMD ["node", "server.js"]
|
|
145
195
|
```
|
|
146
196
|
|
|
147
|
-
A single-stage `node:
|
|
197
|
+
A single-stage `node:26-trixie-slim` image works too if you `apt-get install -y git ca-certificates` before `npm ci`. Prebuilt binaries exist for x64 and arm64 on Linux, macOS and Windows, so nothing is compiled at install time either way.
|
|
148
198
|
|
|
149
199
|
## Differences from Express
|
|
150
200
|
|
|
@@ -211,6 +261,37 @@ On top of that, a handler simple enough to be read at registration time is compi
|
|
|
211
261
|
|
|
212
262
|
`app.set("declarative responses", false)` turns the whole thing off if you would rather have Express's exact framing than the speed.
|
|
213
263
|
|
|
264
|
+
None of that is guesswork you have to do from the outside. `listen()` decides it all, and `npx fulmine profile` prints what it decided:
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
npx fulmine profile # the file package.json's "main" points at
|
|
268
|
+
npx fulmine profile server.js # or name it
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
7 route(s), 4 answered by µWS itself
|
|
273
|
+
|
|
274
|
+
GET /api/health µWS /api/health (2 in front of it in its chain)
|
|
275
|
+
GET /hello µWS /hello (compiled to a response, reads no query)
|
|
276
|
+
GET /:anything router: something before it in the same router overlaps its paths
|
|
277
|
+
GET /after-the-param router: the parameter route /:anything is written before it
|
|
278
|
+
SEARCH /odd router: µWS does not serve SEARCH
|
|
279
|
+
|
|
280
|
+
What this adds up to
|
|
281
|
+
|
|
282
|
+
4 of 7 route(s) matched by µWS in C++
|
|
283
|
+
1 answered from a response written at startup, running no javascript
|
|
284
|
+
layers in front of a compiled handler: 1 at least, 2 at most, 1.8 on average
|
|
285
|
+
|
|
286
|
+
Worth changing, if these are routes that carry traffic
|
|
287
|
+
|
|
288
|
+
GET /after-the-param
|
|
289
|
+
write it above /:anything. Express answers whichever matches first, so the order is
|
|
290
|
+
already what decides, and with the literal first µWS can match it in C++ as well.
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
It loads the application with `listen()` replaced by the half that compiles the routes, so nothing binds a port and the listen callback does not run: profiling a running service does not start a second copy of it. There is no score, on purpose. A percentage of routes is not a percentage of traffic, and an application with a thousand cold routes and one hot one that fell back would score well and serve badly.
|
|
294
|
+
|
|
214
295
|
2. Do not use external `serve-static` module. Instead use built-in `express.static()` middleware, which is optimized for Fulmine.
|
|
215
296
|
|
|
216
297
|
3. Do not use `body-parser` module. Instead use built-in `express.text()`, `express.json()` etc.
|
|
@@ -524,57 +605,5 @@ Any Express view engine should work. Here's list of engines we include in our te
|
|
|
524
605
|
|
|
525
606
|
## Working on Fulmine
|
|
526
607
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
# Fulmine, and the two outputs have to match byte for byte
|
|
530
|
-
npm test middlewares # one category
|
|
531
|
-
npm test tests/tests/res/res-send.js # one file
|
|
532
|
-
|
|
533
|
-
npm run test:unit # the pure functions, which the comparison cannot reach
|
|
534
|
-
npm run test:types # the TypeScript declarations, through tsd
|
|
535
|
-
npm run typecheck # checkJs over src, which is where the JSDoc types are checked
|
|
536
|
-
|
|
537
|
-
npm run lint # eslint, including the rule that every function in src carries a JSDoc block
|
|
538
|
-
npm run format # prettier
|
|
539
|
-
npm run cover # the comparison suite under nyc, then an HTML report
|
|
540
|
-
|
|
541
|
-
npm run benchmark:compare -- --duration 20 # against Express, scenario by scenario
|
|
542
|
-
npm run benchmark:ab -- --against main # this working tree against another revision
|
|
543
|
-
|
|
544
|
-
npm run test:express # Express's own test suite, run against this
|
|
545
|
-
npm run test:express -- res.sendFile --verbose # one area of it, with mocha's output
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
The comparison suite is the load-bearing one. A test is a file that prints; the runner executes it
|
|
549
|
-
twice, once with `express` and once with this, and fails on any difference. That is why adding a
|
|
550
|
-
test means writing something that prints what you want compared, and why a test that prints from
|
|
551
|
-
both the server and the client at once is a bug: the two orderings are a race.
|
|
552
|
-
|
|
553
|
-
### Writing a comparison test
|
|
554
|
-
|
|
555
|
-
A test file is an ordinary script. The first line is its description, the second may carry a marker,
|
|
556
|
-
and the rest sets up an app, makes requests and prints. `tests/helpers.js` has what to print with:
|
|
557
|
-
|
|
558
|
-
| | |
|
|
559
|
-
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
560
|
-
| `fetchTest(url, init)` | `fetch`, plus a line with the status and the headers worth comparing. Returns the response untouched, so the test goes on to read the body as it would have. Lines come out in call order, never in arrival order. |
|
|
561
|
-
| `sequential([() => …])` | Runs requests one at a time. `Promise.all` starts them together and the two servers then answer in whatever order they scheduled, which is a difference the runner would report as a failure. |
|
|
562
|
-
| `// INSPECT` | On the second line. The runner then mounts `inspectRequest` in front of every app the file makes, and each request prints its `method`, `url`, `originalUrl`, `baseUrl`, `path`, `protocol`, `secure`, `hostname`, `host`, `xhr`, `subdomains` and `query`. |
|
|
563
|
-
| `// OFF: reason` | Skips the file. |
|
|
564
|
-
|
|
565
|
-
`// INSPECT` is not free everywhere, which is why it is asked for rather than always on. It is a
|
|
566
|
-
middleware, so a route behind it stops being compiled into a declarative response and is served by
|
|
567
|
-
the ordinary path instead: a file whose routes do compile would quietly stop covering the compiled
|
|
568
|
-
one. And Express builds its router at the first `use()`, freezing `strict routing` and
|
|
569
|
-
`case sensitive routing` as they are at that moment, so a file that sets either one afterwards must
|
|
570
|
-
not ask for it. Everywhere else it is worth having: it is what caught a pathless mount dropping the
|
|
571
|
-
middleware in front of it.
|
|
572
|
-
|
|
573
|
-
`npm run test:express` is the other kind of test: it clones Express at the version in
|
|
574
|
-
`devDependencies`, points its entry at this source and runs its suite against it. It is a bug mine
|
|
575
|
-
rather than a gate, and its exit status says nothing. Read the header of `tools/express-suite.js`
|
|
576
|
-
before reading its numbers: some of what it reports is Express testing its own internals, which the
|
|
577
|
-
clone still has, and some is internals used as public API.
|
|
578
|
-
|
|
579
|
-
`benchmark/README.md` covers measuring, including why the A/B runs pipelined by default and why a
|
|
580
|
-
null control matters.
|
|
608
|
+
How to run the suites, what each of them is for, and how to write a comparison test:
|
|
609
|
+
[`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fulmine.js",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Drop-in Express 5 replacement on uWebSockets.js. Your existing middleware keeps working.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"test:unit": "node --test \"tests/unit/*.test.js\"",
|
|
13
13
|
"test:types": "tsd --files tests/types/*.test-d.ts",
|
|
14
14
|
"test:express": "node tools/express-suite.js",
|
|
15
|
+
"fuzz": "node tools/fuzz.js",
|
|
15
16
|
"benchmark:compare": "node benchmark/run.js",
|
|
16
17
|
"cover": "npm run cover:full && npm run cover:report",
|
|
17
18
|
"cover:unit": "nyc --silent npm run test",
|
|
@@ -27,7 +28,10 @@
|
|
|
27
28
|
"benchmark:profile": "node benchmark/profile.js",
|
|
28
29
|
"release:local": "node tools/release-local.js",
|
|
29
30
|
"cover:full": "nyc --silent npm run test && nyc --silent --no-clean npm run test:unit && nyc --silent --no-clean npm run test:express && nyc report",
|
|
30
|
-
"cover:check": "nyc check-coverage --statements 93 --branches 88 --functions 92 --lines 93"
|
|
31
|
+
"cover:check": "nyc check-coverage --statements 93 --branches 88 --functions 92 --lines 93",
|
|
32
|
+
"demo:start": "npm --prefix demo install && npm --prefix demo start",
|
|
33
|
+
"demo:deploy": "fly deploy ./demo --config ./demo/fly.toml",
|
|
34
|
+
"demo:logs": "fly logs --app fulmine-demo"
|
|
31
35
|
},
|
|
32
36
|
"engines": {
|
|
33
37
|
"node": ">=22"
|
package/src/application.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Copyright 2024 dimden.dev
|
|
3
3
|
Copyright 2026 Nigro Simone
|
|
4
4
|
|
|
5
|
+
This file is derived from Ultimate Express and has been modified.
|
|
6
|
+
|
|
5
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
8
|
you may not use this file except in compliance with the License.
|
|
7
9
|
You may obtain a copy of the License at
|
|
@@ -83,6 +85,20 @@ const FILE_CACHE_MAX_ENTRY = 768 * 1024;
|
|
|
83
85
|
const FILE_CACHE_BUDGET = 64 * 1024 * 1024;
|
|
84
86
|
|
|
85
87
|
class Application extends Router {
|
|
88
|
+
/**
|
|
89
|
+
* An application reads an unset routing flag from the app it is mounted on, which a plain
|
|
90
|
+
* Router does not: express chains a mounted app's settings onto its parent's.
|
|
91
|
+
*
|
|
92
|
+
* @type {boolean}
|
|
93
|
+
*/
|
|
94
|
+
_inheritsSettings = true;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* An application, which a plain Router is not. See Router#_isApplication.
|
|
98
|
+
* @type {boolean}
|
|
99
|
+
*/
|
|
100
|
+
_isApplication = true;
|
|
101
|
+
|
|
86
102
|
/**
|
|
87
103
|
* @param {object} [settings] the options express() takes. uwsOptions goes to uWS and decides
|
|
88
104
|
* between an HTTP, an HTTPS and an HTTP/3 server; threads sizes the file-reading pool, and 0
|
|
@@ -171,6 +187,12 @@ class Application extends Router {
|
|
|
171
187
|
if (parent.response) {
|
|
172
188
|
Object.setPrototypeOf(this.response, parent.response);
|
|
173
189
|
}
|
|
190
|
+
// and the engines with them, which is the same chaining express does: a sub-app renders
|
|
191
|
+
// with whatever the parent registered unless it registered its own. Without this a
|
|
192
|
+
// render inside a mounted app looked for a module named after the extension.
|
|
193
|
+
if (parent.engines) {
|
|
194
|
+
Object.setPrototypeOf(this.engines, parent.engines);
|
|
195
|
+
}
|
|
174
196
|
// a "trust proxy" this app never set is inherited from the parent, as express does:
|
|
175
197
|
// the defaults are deleted so get() falls through to the parent's value
|
|
176
198
|
if (
|
|
@@ -357,10 +379,6 @@ class Application extends Router {
|
|
|
357
379
|
// express's wording, which applications match on
|
|
358
380
|
throw new TypeError("unknown value for query parser function: " + value);
|
|
359
381
|
}
|
|
360
|
-
} else if (key === "views") {
|
|
361
|
-
// a list of directories is searched in order by View.lookup, each resolved here once
|
|
362
|
-
this.settings[key] = Array.isArray(value) ? value.map((dir) => path.resolve(dir)) : path.resolve(value);
|
|
363
|
-
return this;
|
|
364
382
|
} else if (key === "etag") {
|
|
365
383
|
// an etag arriving after listen would make send consult freshness headers the
|
|
366
384
|
// header-skip routes never copied, so those skips are taken back
|
|
@@ -417,13 +435,13 @@ class Application extends Router {
|
|
|
417
435
|
}
|
|
418
436
|
|
|
419
437
|
/**
|
|
420
|
-
* Whether a setting is truthy. Reads
|
|
421
|
-
*
|
|
438
|
+
* Whether a setting is truthy. Reads through to the app this one is mounted on, as get() does
|
|
439
|
+
* and as express does: mounting chains a sub-app's settings onto its parent's.
|
|
422
440
|
* @param {string} key setting name
|
|
423
441
|
* @returns {boolean}
|
|
424
442
|
*/
|
|
425
443
|
enabled(key) {
|
|
426
|
-
return !!this.
|
|
444
|
+
return !!this.get(key);
|
|
427
445
|
}
|
|
428
446
|
|
|
429
447
|
/**
|
|
@@ -432,7 +450,7 @@ class Application extends Router {
|
|
|
432
450
|
* @returns {boolean}
|
|
433
451
|
*/
|
|
434
452
|
disabled(key) {
|
|
435
|
-
return !this.
|
|
453
|
+
return !this.get(key);
|
|
436
454
|
}
|
|
437
455
|
|
|
438
456
|
/**
|
|
@@ -471,32 +489,42 @@ class Application extends Router {
|
|
|
471
489
|
* between an error, the automatic OPTIONS reply and a 404.
|
|
472
490
|
*/
|
|
473
491
|
_createRequestHandler() {
|
|
474
|
-
this.uwsApp.any("/*",
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
492
|
+
this.uwsApp.any("/*", (res, req) => this._serveGeneric(res, req));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Serves one request by walking this app's chain, with no registration-time shortcut. It is
|
|
497
|
+
* what the catch-all runs, and also what a native registration falls back to when it sees a
|
|
498
|
+
* request it must not answer itself, see the case guard in Router#_registerUwsRoute.
|
|
499
|
+
*
|
|
500
|
+
* @param {any} res the uWS response
|
|
501
|
+
* @param {any} req the uWS request
|
|
502
|
+
*/
|
|
503
|
+
async _serveGeneric(res, req) {
|
|
504
|
+
const request = this.handleRequest(res, req);
|
|
505
|
+
const response = request.res;
|
|
506
|
+
// armed up front here: this handler awaits, so the response outlives the callback
|
|
507
|
+
// on every path through it
|
|
508
|
+
this._armAbort(res, response);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const routed = this._routeRequest(request, response);
|
|
512
|
+
// dispatch has run its synchronous stretch inside _routeRequest by now, still
|
|
513
|
+
// under the cork uWS holds for this callback; the await below leaves it
|
|
514
|
+
response._corkNeeded = true;
|
|
515
|
+
const matchedRoute = await routed;
|
|
516
|
+
if (!matchedRoute && !response.headersSent && !response.aborted) {
|
|
517
|
+
this._endUnmatched(request, response);
|
|
498
518
|
}
|
|
499
|
-
})
|
|
519
|
+
} catch (err) {
|
|
520
|
+
// an internal throw answers 500 as express's final handler would, instead of
|
|
521
|
+
// dying as an unhandled rejection
|
|
522
|
+
if (response.aborted || response.finished) {
|
|
523
|
+
console.error(err);
|
|
524
|
+
} else {
|
|
525
|
+
this._handleError(err, null, request, response);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
500
528
|
}
|
|
501
529
|
|
|
502
530
|
/**
|
|
@@ -733,7 +761,12 @@ class Application extends Router {
|
|
|
733
761
|
view = new View(name, {
|
|
734
762
|
defaultEngine: this.get("view engine"),
|
|
735
763
|
root: this.get("views"),
|
|
736
|
-
|
|
764
|
+
// the object itself, not a copy of it: a mounted app reaches its parent's engines
|
|
765
|
+
// through the prototype chain, and a spread only carries what the app owns, so a
|
|
766
|
+
// sub-app rendering with the parent's engine went off to require() a module named
|
|
767
|
+
// after the extension. Express hands its own object over too, and means to: a view
|
|
768
|
+
// that loads an engine by require caches it back here
|
|
769
|
+
engines: this.engines
|
|
737
770
|
});
|
|
738
771
|
if (!view.path) {
|
|
739
772
|
const dirs =
|