fulmine.js 5.21.0 → 5.21.2
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/README.md +2 -2
- package/package.json +2 -3
- package/src/adopt.js +9 -23
- package/src/application.js +194 -246
- package/src/cli.js +71 -163
- package/src/cluster.js +5 -15
- package/src/compression.js +52 -114
- package/src/create.js +3 -6
- package/src/declarative.js +87 -170
- package/src/hot-settings.js +29 -21
- package/src/index.js +2 -17
- package/src/lazy-readable.js +6 -18
- package/src/lazy-writable.js +5 -16
- package/src/middlewares.js +151 -366
- package/src/nest.js +10 -16
- package/src/node-shim.js +75 -83
- package/src/optimizer.js +68 -153
- package/src/parse-query.js +7 -15
- package/src/request-utils.js +12 -29
- package/src/request.js +268 -406
- package/src/response.js +333 -560
- package/src/route.js +10 -6
- package/src/router-utils.js +124 -249
- package/src/router.js +231 -362
- package/src/server-shape.js +4 -16
- package/src/server-timing.js +2 -14
- package/src/socket.js +23 -6
- package/src/testing.js +19 -44
- package/src/types.d.ts +11 -1
- package/src/usage.js +10 -20
- package/src/utils.js +173 -379
- package/src/uws.js +43 -0
- package/src/verify.js +20 -39
- package/src/view.js +29 -10
- package/src/walk.js +109 -156
- package/src/websocket.js +4 -7
- package/src/work.js +4 -11
package/src/application.js
CHANGED
|
@@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
|
|
17
17
|
limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const { loadUWS } = require("./uws.js");
|
|
21
21
|
const Router = require("./router.js");
|
|
22
22
|
const {
|
|
23
23
|
removeDuplicateSlashes,
|
|
@@ -43,8 +43,7 @@ const { workerCount, forkWorkers, isSupervising, becomeSupervisor } = require(".
|
|
|
43
43
|
|
|
44
44
|
const cpuCount = os.cpus().length;
|
|
45
45
|
|
|
46
|
-
// marks a "trust proxy"
|
|
47
|
-
// mounted sub-app knows it may inherit the parent's
|
|
46
|
+
// marks a "trust proxy" the application never set, under express's key, so a sub-app may inherit
|
|
48
47
|
const trustProxyDefaultSymbol = "@@symbol:trust_proxy_default";
|
|
49
48
|
|
|
50
49
|
const workers = /** @type {FSWorker[]} */ ([]);
|
|
@@ -52,26 +51,29 @@ let taskKey = 0;
|
|
|
52
51
|
const workerTasks = new NullObject();
|
|
53
52
|
|
|
54
53
|
class FSWorker {
|
|
54
|
+
/** Whether a read is in flight on it. @type {boolean} */
|
|
55
|
+
busy = false;
|
|
56
|
+
|
|
57
|
+
/** @type {Worker} */
|
|
58
|
+
worker;
|
|
59
|
+
|
|
55
60
|
/**
|
|
56
|
-
* A worker thread that
|
|
57
|
-
* It is unref'd, so an idle one does not keep the process alive, and it is shared between every
|
|
58
|
-
* app in the process rather than started per app.
|
|
61
|
+
* A worker thread that only reads files, unref'd and shared by every app in the process.
|
|
59
62
|
*/
|
|
60
63
|
constructor() {
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
// its own execArgv: a --import written for the parent (Angular's route extraction loader
|
|
65
|
+
// reads workerData) throws in a thread that only reads files
|
|
66
|
+
this.worker = new Worker(path.join(__dirname, "worker.js"), { execArgv: [] });
|
|
63
67
|
|
|
64
68
|
this.worker.on("message", (message) => {
|
|
65
|
-
//
|
|
66
|
-
// as {"watch:import": [...]}, which carries no key of ours
|
|
69
|
+
// under --watch node reports {"watch:import": [...]} here too, with no key of ours
|
|
67
70
|
if (workerTasks[message.key] === undefined) return;
|
|
68
71
|
this.busy = false;
|
|
69
72
|
if (message.err) {
|
|
70
73
|
workerTasks[message.key].reject(new Error(message.err));
|
|
71
74
|
} else {
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// res.end() implementations (e.g. express-session calls Buffer.byteLength on the chunk).
|
|
75
|
+
// the transferred ArrayBuffer as a Buffer, zero-copy: express-session calls
|
|
76
|
+
// Buffer.byteLength on what res.end() gets
|
|
75
77
|
workerTasks[message.key].resolve(
|
|
76
78
|
message.data instanceof ArrayBuffer ? Buffer.from(message.data) : message.data
|
|
77
79
|
);
|
|
@@ -84,41 +86,105 @@ class FSWorker {
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
// the worker path's
|
|
88
|
-
// holds an entry the read path would not have produced whole
|
|
89
|
+
// the worker path's bound, a bigger file streams
|
|
89
90
|
const FILE_CACHE_MAX_ENTRY = 768 * 1024;
|
|
90
|
-
// oldest-first once the budget is spent
|
|
91
|
-
// something other than an application server anyway
|
|
91
|
+
// oldest-first once the budget is spent
|
|
92
92
|
const FILE_CACHE_BUDGET = 64 * 1024 * 1024;
|
|
93
93
|
|
|
94
94
|
class Application extends Router {
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
97
|
-
* Router does not: express chains a mounted app's settings onto its parent's.
|
|
96
|
+
* A mounted app's settings chain onto its parent's, as in express.
|
|
98
97
|
*
|
|
99
98
|
* @type {boolean}
|
|
100
99
|
*/
|
|
101
100
|
_inheritsSettings = true;
|
|
102
101
|
|
|
103
102
|
/**
|
|
104
|
-
*
|
|
103
|
+
* See Router#_isApplication.
|
|
105
104
|
* @type {boolean}
|
|
106
105
|
*/
|
|
107
106
|
_isApplication = true;
|
|
108
107
|
|
|
109
108
|
/**
|
|
110
|
-
* Whether express.testing already compiled the routes
|
|
111
|
-
* else: a second compilation would register everything with uWS twice. See src/testing.js.
|
|
109
|
+
* Whether express.testing already compiled the routes, a second time would register them twice.
|
|
112
110
|
* @type {boolean|undefined}
|
|
113
111
|
*/
|
|
114
112
|
_testingCompiled;
|
|
115
113
|
|
|
116
114
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
* The uWS app once made, or the one settings.uwsApp handed in. See the uwsApp getter.
|
|
116
|
+
* @type {any}
|
|
117
|
+
*/
|
|
118
|
+
_uwsApp;
|
|
119
|
+
|
|
120
|
+
/** What uWS.App or uWS.SSLApp is given, kept until the app is made. */
|
|
121
|
+
_uwsOptions;
|
|
122
|
+
|
|
123
|
+
/** The forks the cluster setting asks for, 0 when none. @type {number} */
|
|
124
|
+
_clusterWorkers;
|
|
125
|
+
|
|
126
|
+
/** Whether uwsOptions carries a key and a certificate, which picks uWS.SSLApp. @type {string|undefined} */
|
|
127
|
+
ssl;
|
|
128
|
+
|
|
129
|
+
/** express's app.cache, the view cache. @type {Record<string, any>} */
|
|
130
|
+
cache = new NullObject();
|
|
131
|
+
|
|
132
|
+
/** The view engines by extension, chained onto the parent's on mount. @type {Record<string, any>} */
|
|
133
|
+
engines = { __proto__: null };
|
|
134
|
+
|
|
135
|
+
/** A null prototype, as express gives app.locals. @type {Record<string, any>} */
|
|
136
|
+
locals = Object.create(null);
|
|
137
|
+
|
|
138
|
+
/** The request prototype layer of this app, what app.request extends. @type {Request} */
|
|
139
|
+
request;
|
|
140
|
+
|
|
141
|
+
/** @type {Response} */
|
|
142
|
+
response;
|
|
143
|
+
|
|
144
|
+
/** @type {boolean} */
|
|
145
|
+
listenCalled = false;
|
|
146
|
+
|
|
147
|
+
/** @type {FSWorker[]} */
|
|
148
|
+
workers = [];
|
|
149
|
+
|
|
150
|
+
/** @type {number|undefined} */
|
|
151
|
+
port;
|
|
152
|
+
|
|
153
|
+
/** @type {boolean} */
|
|
154
|
+
listening = false;
|
|
155
|
+
|
|
156
|
+
/** What address() has to go on. @type {string|undefined} */
|
|
157
|
+
_listenHost;
|
|
158
|
+
|
|
159
|
+
/** close() stops the listen socket, then waits for the pending responses, as node does. @type {import("uWebSockets.js").us_listen_socket|undefined} */
|
|
160
|
+
_listenSocket;
|
|
161
|
+
|
|
162
|
+
/** The fork supervisor, in the primary of a clustered app only. @type {{stop: () => void}|undefined} */
|
|
163
|
+
_clusterHandle;
|
|
164
|
+
|
|
165
|
+
/** readSmallFile's cache, by absolute path. @type {Map<string, {mtimeMs: number, size: number, data: Buffer}>} */
|
|
166
|
+
_fileCache = new Map();
|
|
167
|
+
|
|
168
|
+
/** @type {number} */
|
|
169
|
+
_fileCacheBytes = 0;
|
|
170
|
+
|
|
171
|
+
/** readSmallFile's reads in flight, so concurrent asks share one. @type {Map<string, Promise<Buffer>>} */
|
|
172
|
+
_fileReadsInFlight = new Map();
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The responses being served, an intrusive list (a Set paid hashing per request). A holder
|
|
176
|
+
* object: the callable app copies own scalars by value.
|
|
177
|
+
* @type {{head: Response|null}}
|
|
178
|
+
*/
|
|
179
|
+
_pending = { head: null };
|
|
180
|
+
|
|
181
|
+
/** Whether close() is waiting for the pending responses. @type {boolean} */
|
|
182
|
+
_draining = false;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {Record<string, any>} [settings] the options express() takes: uwsOptions (HTTP or HTTPS), threads
|
|
186
|
+
* (the file-reading pool, 0 off), cluster, uwsApp (an existing uWS app); the rest are
|
|
187
|
+
* application settings
|
|
122
188
|
*/
|
|
123
189
|
constructor(settings = new NullObject()) {
|
|
124
190
|
super(settings);
|
|
@@ -128,44 +194,30 @@ class Application extends Router {
|
|
|
128
194
|
if (typeof settings.threads !== "number") {
|
|
129
195
|
settings.threads = cpuCount > 1 ? 1 : 0;
|
|
130
196
|
}
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// settles it for the whole process before any app has listened, see becomeSupervisor
|
|
197
|
+
// counted here so a bad setting throws where the app is written, and the process becomes
|
|
198
|
+
// the supervisor before any app listens
|
|
134
199
|
this._clusterWorkers = workerCount(settings.cluster);
|
|
135
200
|
if (this._clusterWorkers > 0 && cluster.isPrimary) {
|
|
136
201
|
becomeSupervisor();
|
|
137
202
|
}
|
|
138
|
-
if (settings.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// uWS.H3App exists in the pinned build but its QUIC stack does not: the constructor
|
|
142
|
-
// segfaults on Linux and hangs forever on Windows before serving a single request,
|
|
143
|
-
// verified 2026-08-05 with uWS alone. A clear throw beats a native crash; this
|
|
144
|
-
// branch goes back to H3App once uNetworking ships working QUIC in the prebuilts.
|
|
203
|
+
if (settings.http3) {
|
|
204
|
+
// uWS.H3App segfaults on Linux and hangs on Windows in the pinned build, verified
|
|
205
|
+
// 2026-08-05 with uWS alone
|
|
145
206
|
throw new Error(
|
|
146
207
|
"http3 is not usable with the pinned uWebSockets.js build: its H3App crashes " +
|
|
147
208
|
"during construction. Track uNetworking/uWebSockets.js for working QUIC support."
|
|
148
209
|
);
|
|
149
|
-
} else if (settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
|
|
150
|
-
this.uwsApp = uWS.SSLApp(settings.uwsOptions);
|
|
151
|
-
} else {
|
|
152
|
-
this.uwsApp = uWS.App(settings.uwsOptions);
|
|
153
210
|
}
|
|
154
211
|
this.ssl = settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name;
|
|
155
|
-
|
|
156
|
-
this.
|
|
157
|
-
|
|
158
|
-
// is just a local
|
|
159
|
-
this.locals = Object.create(null);
|
|
212
|
+
// the uWS app is made on first use, see the uwsApp getter
|
|
213
|
+
this._uwsApp = settings.uwsApp;
|
|
214
|
+
this._uwsOptions = settings.uwsOptions;
|
|
160
215
|
this.locals.settings = this.settings;
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// was an allocation on every request
|
|
216
|
+
// a request/response prototype layer per app, so extending app.request cannot leak into
|
|
217
|
+
// another app. The constructors are written out: the implicit one spreads its arguments,
|
|
218
|
+
// an allocation per request
|
|
165
219
|
this._request = class extends Request {
|
|
166
220
|
/**
|
|
167
|
-
* The base constructor's arguments, written out rather than spread. See Request.
|
|
168
|
-
*
|
|
169
221
|
* @param {import("uWebSockets.js").HttpRequest} req uWS request
|
|
170
222
|
* @param {import("uWebSockets.js").HttpResponse} res uWS response
|
|
171
223
|
* @param {Application} app the application this request arrived at
|
|
@@ -178,8 +230,6 @@ class Application extends Router {
|
|
|
178
230
|
};
|
|
179
231
|
this._response = class extends Response {
|
|
180
232
|
/**
|
|
181
|
-
* The base constructor's arguments, written out rather than spread. See Response.
|
|
182
|
-
*
|
|
183
233
|
* @param {import("uWebSockets.js").HttpResponse} res uWS response
|
|
184
234
|
* @param {Request} req the Request, already built
|
|
185
235
|
* @param {Application} app the application this request arrived at
|
|
@@ -191,22 +241,17 @@ class Application extends Router {
|
|
|
191
241
|
this.request = this._request.prototype;
|
|
192
242
|
this.response = this._response.prototype;
|
|
193
243
|
this.on("mount", (parent) => {
|
|
194
|
-
// the parent's extensions show through,
|
|
195
|
-
// application has a layer to hang onto: a plain router mount leaves things alone
|
|
244
|
+
// the parent's extensions and engines show through, as express chains them
|
|
196
245
|
if (parent.request) {
|
|
197
246
|
Object.setPrototypeOf(this.request, parent.request);
|
|
198
247
|
}
|
|
199
248
|
if (parent.response) {
|
|
200
249
|
Object.setPrototypeOf(this.response, parent.response);
|
|
201
250
|
}
|
|
202
|
-
// and the engines with them, which is the same chaining express does: a sub-app renders
|
|
203
|
-
// with whatever the parent registered unless it registered its own. Without this a
|
|
204
|
-
// render inside a mounted app looked for a module named after the extension.
|
|
205
251
|
if (parent.engines) {
|
|
206
252
|
Object.setPrototypeOf(this.engines, parent.engines);
|
|
207
253
|
}
|
|
208
|
-
// a "trust proxy"
|
|
209
|
-
// the defaults are deleted so get() falls through to the parent's value
|
|
254
|
+
// a "trust proxy" never set here is inherited: the defaults are deleted so get() falls through
|
|
210
255
|
if (
|
|
211
256
|
this._settings[trustProxyDefaultSymbol] === true &&
|
|
212
257
|
typeof parent._settings["trust proxy fn"] === "function"
|
|
@@ -215,8 +260,6 @@ class Application extends Router {
|
|
|
215
260
|
delete this._settings["trust proxy fn"];
|
|
216
261
|
}
|
|
217
262
|
});
|
|
218
|
-
this.listenCalled = false;
|
|
219
|
-
this.workers = [];
|
|
220
263
|
for (let i = 0; i < settings.threads; i++) {
|
|
221
264
|
if (workers[i]) {
|
|
222
265
|
this.workers[i] = workers[i];
|
|
@@ -224,30 +267,8 @@ class Application extends Router {
|
|
|
224
267
|
this.workers[i] = new FSWorker();
|
|
225
268
|
}
|
|
226
269
|
}
|
|
227
|
-
this.port = undefined;
|
|
228
|
-
this.listening = false;
|
|
229
|
-
// the host handed to listen(), which is all address() has to go on
|
|
230
|
-
this._listenHost = undefined;
|
|
231
|
-
// the uWS listen socket, and the responses being served right now: close() stops the
|
|
232
|
-
// first and waits for the second, the way node's server.close() does
|
|
233
|
-
this._listenSocket = undefined;
|
|
234
|
-
// the fork supervisor, in the primary of a clustered app and nowhere else
|
|
235
|
-
/** @type {{stop: () => void}|undefined} */
|
|
236
|
-
this._clusterHandle = undefined;
|
|
237
|
-
// readSmallFile's cache and its in-flight reads, see the method
|
|
238
|
-
this._fileCache = new Map();
|
|
239
|
-
this._fileCacheBytes = 0;
|
|
240
|
-
this._fileReadsInFlight = new Map();
|
|
241
|
-
// the responses being served right now, an intrusive list: linking is three pointer
|
|
242
|
-
// stores where a Set paid identity hashing and table upkeep per request. A holder object
|
|
243
|
-
// rather than a bare field, because the callable app copies own scalars by value and two
|
|
244
|
-
// copies of a head would disagree; an object rides by reference, the way the Set did
|
|
245
|
-
this._pending = /** @type {{ head: Response|null }} */ ({ head: null });
|
|
246
|
-
// on the per-app prototype layer, not per response, same as the Set was
|
|
247
270
|
/** @type {{_pendingIn?: {head: Response|null}}} */ (this.response)._pendingIn = this._pending;
|
|
248
|
-
|
|
249
|
-
// read here, at construction, the way express does; an empty NODE_ENV means development,
|
|
250
|
-
// which the ?? in the shared default would miss
|
|
271
|
+
// at construction as express reads it; an empty NODE_ENV is development
|
|
251
272
|
if (typeof this._settings.env === "undefined") {
|
|
252
273
|
this._settings.env = process.env.NODE_ENV || "development";
|
|
253
274
|
}
|
|
@@ -260,7 +281,6 @@ class Application extends Router {
|
|
|
260
281
|
}
|
|
261
282
|
}
|
|
262
283
|
}
|
|
263
|
-
// non-enumerable, so the marker never shows up walking the settings
|
|
264
284
|
Object.defineProperty(this._settings, trustProxyDefaultSymbol, {
|
|
265
285
|
configurable: true,
|
|
266
286
|
value: true
|
|
@@ -270,9 +290,8 @@ class Application extends Router {
|
|
|
270
290
|
}
|
|
271
291
|
|
|
272
292
|
/**
|
|
273
|
-
* Parks a promise's settle functions under a key the worker
|
|
274
|
-
*
|
|
275
|
-
* a million tasks being far more than can be outstanding at once.
|
|
293
|
+
* Parks a promise's settle functions under a key the worker sends back. The counter wraps at a
|
|
294
|
+
* million.
|
|
276
295
|
*
|
|
277
296
|
* @param {(value: Buffer) => void} resolve
|
|
278
297
|
* @param {(err: Error) => void} reject
|
|
@@ -288,9 +307,8 @@ class Application extends Router {
|
|
|
288
307
|
}
|
|
289
308
|
|
|
290
309
|
/**
|
|
291
|
-
* Reads a file on
|
|
292
|
-
*
|
|
293
|
-
* res.sendFile uses it for small files and streams the rest.
|
|
310
|
+
* Reads a file on a file thread picked at random. Only worth it for a small file, res.sendFile
|
|
311
|
+
* streams the rest.
|
|
294
312
|
*
|
|
295
313
|
* @param {string} path absolute path to read
|
|
296
314
|
* @returns {Promise<Buffer>}
|
|
@@ -305,11 +323,9 @@ class Application extends Router {
|
|
|
305
323
|
}
|
|
306
324
|
|
|
307
325
|
/**
|
|
308
|
-
* A small file through the worker pool
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* on a macrotask, which is when a worker's answer would have arrived.
|
|
312
|
-
* `app.set("file cache", false)` turns the cache off, the shared read stays.
|
|
326
|
+
* A small file through the worker pool: concurrent asks share one read, and an unchanged file
|
|
327
|
+
* (by the stat the caller paid for) comes from a bounded cache, on a macrotask as a worker's
|
|
328
|
+
* answer would. `app.set("file cache", false)` keeps only the shared read.
|
|
313
329
|
*
|
|
314
330
|
* @param {string} fullpath
|
|
315
331
|
* @param {import("fs").Stats} stat
|
|
@@ -347,17 +363,14 @@ class Application extends Router {
|
|
|
347
363
|
return data;
|
|
348
364
|
});
|
|
349
365
|
this._fileReadsInFlight.set(fullpath, pending);
|
|
350
|
-
// never cached past settlement: a rejection clears the slot the same way
|
|
351
366
|
const clear = () => this._fileReadsInFlight.delete(fullpath);
|
|
352
367
|
pending.then(clear, clear);
|
|
353
368
|
return pending;
|
|
354
369
|
}
|
|
355
370
|
|
|
356
371
|
/**
|
|
357
|
-
* Reads or writes
|
|
358
|
-
*
|
|
359
|
-
* `trust proxy`, `query parser` and `etag` compile the value into a function kept beside it,
|
|
360
|
-
* and `views` becomes an absolute path.
|
|
372
|
+
* Reads or writes a setting; `set(key, undefined)` still writes. `trust proxy`, `query parser`
|
|
373
|
+
* and `etag` compile the value into a function kept beside it.
|
|
361
374
|
*
|
|
362
375
|
* @param {string} key setting name
|
|
363
376
|
* @param {*} [value] value to store; omit to read instead
|
|
@@ -369,19 +382,16 @@ class Application extends Router {
|
|
|
369
382
|
}
|
|
370
383
|
if (key === "trust proxy") {
|
|
371
384
|
if (!value) {
|
|
372
|
-
// compiled, not deleted: an explicit false must shadow a parent's setting
|
|
373
|
-
// this app is mounted, and a deleted key would read straight through to it
|
|
385
|
+
// compiled, not deleted: an explicit false must shadow a parent's setting
|
|
374
386
|
this._settings["trust proxy fn"] = compileTrust(false);
|
|
375
387
|
} else {
|
|
376
388
|
this._settings["trust proxy fn"] = compileTrust(value);
|
|
377
389
|
}
|
|
378
|
-
// set explicitly, so a mount no longer inherits the parent's
|
|
379
390
|
Object.defineProperty(this._settings, trustProxyDefaultSymbol, {
|
|
380
391
|
configurable: true,
|
|
381
392
|
value: false
|
|
382
393
|
});
|
|
383
394
|
} else if (key === "stat cache") {
|
|
384
|
-
// compiled here so the read path is a number and not a duration to parse per request
|
|
385
395
|
this._settings["stat cache ms"] = durationSetting(value, "stat cache");
|
|
386
396
|
} else if (key === "query parser") {
|
|
387
397
|
if (value === "extended") {
|
|
@@ -393,24 +403,18 @@ class Application extends Router {
|
|
|
393
403
|
} else if (value === false) {
|
|
394
404
|
this._settings["query parser fn"] = undefined;
|
|
395
405
|
} else {
|
|
396
|
-
// express's wording, which applications match on
|
|
397
406
|
throw new TypeError("unknown value for query parser function: " + value);
|
|
398
407
|
}
|
|
399
408
|
} else if (key === "etag methods") {
|
|
400
|
-
// fulmine's own: the methods whose send() computes
|
|
401
|
-
//
|
|
402
|
-
// ["GET", "HEAD"] skips the digest everywhere a validator can never match, which
|
|
403
|
-
// measured +21% on a 4KB POST answer. See issue #10.
|
|
409
|
+
// fulmine's own: the methods whose send() computes an ETag, all of them unset as
|
|
410
|
+
// express does; ["GET", "HEAD"] measured +21% on a 4KB POST answer, see issue #10
|
|
404
411
|
if (value != null && (!Array.isArray(value) || value.some((m) => typeof m !== "string"))) {
|
|
405
412
|
throw new TypeError('"etag methods" wants an array of method names, or null for all of them');
|
|
406
413
|
}
|
|
407
414
|
value = value == null ? undefined : value.map((/** @type {string} */ m) => m.toUpperCase());
|
|
408
415
|
} else if (key === "etag") {
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
// whatever this setting says, and req.fresh reads nothing else off the request.
|
|
412
|
-
// Registering a route after listen still takes them back: that is a different
|
|
413
|
-
// question, about code the analysis never saw
|
|
416
|
+
// the header skips stay: the skip branch reads the conditional pair by name whatever
|
|
417
|
+
// this says
|
|
414
418
|
if (typeof value === "function") {
|
|
415
419
|
this._settings["etag fn"] = value;
|
|
416
420
|
} else {
|
|
@@ -426,14 +430,13 @@ class Application extends Router {
|
|
|
426
430
|
delete this._settings["etag fn"];
|
|
427
431
|
break;
|
|
428
432
|
default:
|
|
429
|
-
// express's wording, which applications match on
|
|
430
433
|
throw new TypeError("unknown value for etag function: " + value);
|
|
431
434
|
}
|
|
432
435
|
}
|
|
433
436
|
}
|
|
434
437
|
|
|
435
438
|
this._settings[key] = value;
|
|
436
|
-
//
|
|
439
|
+
// see Router#_hot
|
|
437
440
|
settingsEpoch.n++;
|
|
438
441
|
return this;
|
|
439
442
|
}
|
|
@@ -459,8 +462,7 @@ class Application extends Router {
|
|
|
459
462
|
}
|
|
460
463
|
|
|
461
464
|
/**
|
|
462
|
-
* Whether a setting is truthy
|
|
463
|
-
* and as express does: mounting chains a sub-app's settings onto its parent's.
|
|
465
|
+
* Whether a setting is truthy, through the parent as get() reads it.
|
|
464
466
|
* @param {string} key setting name
|
|
465
467
|
* @returns {boolean}
|
|
466
468
|
*/
|
|
@@ -478,25 +480,19 @@ class Application extends Router {
|
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
/**
|
|
481
|
-
* Router's handleRequest plus the
|
|
482
|
-
* is held in a set until it finishes, so close() knows when the last one is done. Native
|
|
483
|
-
* routes and the catch-all both come through here, since both call it on the app.
|
|
483
|
+
* Router's handleRequest plus the pending list a graceful close() drains.
|
|
484
484
|
*
|
|
485
485
|
* @param {import("uWebSockets.js").HttpResponse} res uWS response
|
|
486
486
|
* @param {import("uWebSockets.js").HttpRequest} req uWS request, readable only during this call
|
|
487
|
-
* @param {import("./router-utils.js").NativePreset} [preset]
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
* call this override
|
|
492
|
-
* @returns {Request} the request, with the response reachable as request.res
|
|
487
|
+
* @param {import("./router-utils.js").NativePreset} [preset] see nativePreset
|
|
488
|
+
* @param {import("./router-utils.js").SkipHolder} [skipHolder] forwarded whole, dropping it
|
|
489
|
+
* silently turned every skip off
|
|
490
|
+
* @returns {Request} the request, with the response as request.res
|
|
493
491
|
*/
|
|
494
492
|
handleRequest(res, req, preset, skipHolder) {
|
|
495
493
|
const request = super.handleRequest(res, req, preset, skipHolder);
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
// An aborted response only flips its flags without emitting 'close', which is why
|
|
499
|
-
// close()'s drain also sweeps the list by those flags instead of trusting this alone
|
|
494
|
+
// unlinked by the close listener the Response already has; an aborted response only
|
|
495
|
+
// flips its flags, so close()'s drain sweeps by them too
|
|
500
496
|
const response = request.res;
|
|
501
497
|
const pending = this._pending;
|
|
502
498
|
response._pendingLinked = true;
|
|
@@ -510,18 +506,27 @@ class Application extends Router {
|
|
|
510
506
|
}
|
|
511
507
|
|
|
512
508
|
/**
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
509
|
+
* The µWS app, for what µWS offers that this does not (socket.io attaches to it). Made on
|
|
510
|
+
* first ask, so an app served through node's http never loads the binary, see src/uws.js.
|
|
511
|
+
*
|
|
512
|
+
* @returns {any}
|
|
516
513
|
*/
|
|
514
|
+
get uwsApp() {
|
|
515
|
+
if (this._uwsApp === undefined) {
|
|
516
|
+
const uWS = loadUWS();
|
|
517
|
+
this._uwsApp = this.ssl ? uWS.SSLApp(this._uwsOptions) : uWS.App(this._uwsOptions);
|
|
518
|
+
}
|
|
519
|
+
return this._uwsApp;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** The catch-all uWS handler, for every request no native route took. */
|
|
517
523
|
_createRequestHandler() {
|
|
518
524
|
this.uwsApp.any("/*", (res, req) => this._serveGeneric(res, req));
|
|
519
525
|
}
|
|
520
526
|
|
|
521
527
|
/**
|
|
522
|
-
* Serves one request by walking
|
|
523
|
-
*
|
|
524
|
-
* request it must not answer itself, see the case guard in Router#_registerUwsRoute.
|
|
528
|
+
* Serves one request by walking the chain: the catch-all, and what a native registration
|
|
529
|
+
* falls back to on a request it must not answer itself, see the case guard in _registerUwsRoute.
|
|
525
530
|
*
|
|
526
531
|
* @param {import("uWebSockets.js").HttpResponse} res the uWS response
|
|
527
532
|
* @param {import("uWebSockets.js").HttpRequest} req the uWS request
|
|
@@ -535,11 +540,9 @@ class Application extends Router {
|
|
|
535
540
|
try {
|
|
536
541
|
this._routeRequestDirect(request, response);
|
|
537
542
|
} finally {
|
|
538
|
-
// the synchronous stretch
|
|
539
|
-
// whatever comes after it is outside
|
|
543
|
+
// the synchronous stretch ran under uWS's own cork
|
|
540
544
|
response._corkNeeded = true;
|
|
541
|
-
// an abort can only arrive after this callback returns
|
|
542
|
-
// finally says: a response that finished inside it never needs uWS told at all
|
|
545
|
+
// an abort can only arrive after this callback returns
|
|
543
546
|
if (!response.finished) {
|
|
544
547
|
this._armAbort(res, response);
|
|
545
548
|
}
|
|
@@ -547,12 +550,8 @@ class Application extends Router {
|
|
|
547
550
|
}
|
|
548
551
|
|
|
549
552
|
/**
|
|
550
|
-
* Binds
|
|
551
|
-
*
|
|
552
|
-
* Returns the app and not an `http.Server`, since there is no node server underneath. The app
|
|
553
|
-
* carries `address()`, `close()`, `listening` and the 'listening' and 'close' events; anything
|
|
554
|
-
* needing a real server, socket.io being the usual case, wants `app.uwsApp`. A path instead of
|
|
555
|
-
* a port is a unix socket.
|
|
553
|
+
* Binds and starts accepting. Returns the app, which answers as an `http.Server`; socket.io
|
|
554
|
+
* wants `app.uwsApp`. A path instead of a port is a unix socket.
|
|
556
555
|
*
|
|
557
556
|
* @param {number|string} [port] port, or a unix socket path; 0 picks a free port
|
|
558
557
|
* @param {string} [host] interface to bind; every interface when omitted
|
|
@@ -561,13 +560,9 @@ class Application extends Router {
|
|
|
561
560
|
* @returns {this} the app, which doubles as the server handle
|
|
562
561
|
*/
|
|
563
562
|
listen(port, host, backlog, callback) {
|
|
564
|
-
// With { cluster } the primary
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
// workers, listen callback included, so once per worker rather than once.
|
|
568
|
-
//
|
|
569
|
-
// The test is the process and not this app: a second app on a TLS port, without a cluster
|
|
570
|
-
// setting of its own, would take that port here exclusively and every worker would fail
|
|
563
|
+
// With { cluster } the primary only forks: each worker binds this port with SO_REUSEPORT
|
|
564
|
+
// and everything below runs once per worker. The test is the process, not this app: a
|
|
565
|
+
// second app without a cluster setting would take its port here and every worker would fail
|
|
571
566
|
if (cluster.isPrimary && isSupervising()) {
|
|
572
567
|
if (this._clusterWorkers > 0 && !this._clusterHandle) {
|
|
573
568
|
this._clusterHandle = forkWorkers(this._clusterWorkers);
|
|
@@ -575,11 +570,9 @@ class Application extends Router {
|
|
|
575
570
|
return this;
|
|
576
571
|
}
|
|
577
572
|
this._compileOptimizedRoutes();
|
|
578
|
-
// before the catch-all: µWS sends an upgrade to the websocket route even when a
|
|
579
|
-
// catch-all covers the same path, so the two coexist and the order is only tidiness
|
|
580
573
|
registerWebSocketRoutes(this);
|
|
581
574
|
this._createRequestHandler();
|
|
582
|
-
// node's shapes: (cb), (port, cb), (port, host, cb)
|
|
575
|
+
// node's shapes: (cb), (port, cb), (port, host, cb), (port, host, backlog, cb)
|
|
583
576
|
if (typeof port === "function") {
|
|
584
577
|
callback = port;
|
|
585
578
|
port = 0;
|
|
@@ -589,45 +582,36 @@ class Application extends Router {
|
|
|
589
582
|
} else if (typeof backlog === "function") {
|
|
590
583
|
callback = backlog;
|
|
591
584
|
}
|
|
592
|
-
// bare listen()
|
|
593
|
-
// undefined the port fell through to the unix-socket branch below
|
|
585
|
+
// a bare listen() binds an OS-assigned port, as node does
|
|
594
586
|
if (port == null) {
|
|
595
587
|
port = 0;
|
|
596
588
|
}
|
|
597
|
-
// uWS runs this
|
|
598
|
-
//
|
|
599
|
-
// and node emits both 'listening' and 'error' from a process.nextTick.
|
|
589
|
+
// uWS runs this inside its own listen(), so everything reported to the caller is deferred
|
|
590
|
+
// a tick, as node emits 'listening' and 'error'
|
|
600
591
|
const onListen = (/** @type {import("uWebSockets.js").us_listen_socket|false} */ socket) => {
|
|
601
592
|
if (!socket) {
|
|
602
593
|
/** @type {NodeJS.ErrnoException} */
|
|
603
594
|
const err = new Error("listen EADDRINUSE: address already in use :::" + port);
|
|
604
595
|
err.code = "EADDRINUSE";
|
|
605
|
-
// Express 5
|
|
606
|
-
// so a failed bind arrives at the callback rather than being thrown past it
|
|
596
|
+
// Express 5 hands a failed bind to the listen callback
|
|
607
597
|
if (callback) {
|
|
608
598
|
return process.nextTick(() => callback.call(this, err));
|
|
609
599
|
}
|
|
610
|
-
//
|
|
611
|
-
// unhandled error rethrows it from the tick that emitted it, not from listen()
|
|
600
|
+
// without one it is thrown from the tick, as an unhandled 'error' would be
|
|
612
601
|
return process.nextTick(() => {
|
|
613
602
|
throw err;
|
|
614
603
|
});
|
|
615
604
|
}
|
|
616
|
-
// the port
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
this.port = uWS.us_socket_local_port(socket);
|
|
605
|
+
// the port synchronously, so address() works as soon as listen() returns; the
|
|
606
|
+
// callback on a tick, `const server = app.listen(p, () => server.address())` would
|
|
607
|
+
// hit the temporal dead zone
|
|
608
|
+
this.port = loadUWS().us_socket_local_port(socket);
|
|
621
609
|
this.listening = true;
|
|
622
610
|
this._listenHost = host;
|
|
623
|
-
// kept so close() can stop accepting without dropping what is in flight
|
|
624
611
|
this._listenSocket = socket;
|
|
625
612
|
process.nextTick(() => {
|
|
626
|
-
// `this` is
|
|
627
|
-
//
|
|
628
|
-
// `function () { this.address() }` reads the same on both.
|
|
629
|
-
// The callback goes first: in Express it is registered as a 'listening' listener
|
|
630
|
-
// before the caller can add any of their own.
|
|
613
|
+
// `this` is what listen() returns, as in Express; the callback first, as the
|
|
614
|
+
// first 'listening' listener
|
|
631
615
|
if (callback) callback.call(this);
|
|
632
616
|
this.emit("listening");
|
|
633
617
|
});
|
|
@@ -659,10 +643,7 @@ class Application extends Router {
|
|
|
659
643
|
}
|
|
660
644
|
|
|
661
645
|
/**
|
|
662
|
-
* Publishes a message to every socket subscribed to a topic, from outside any
|
|
663
|
-
*
|
|
664
|
-
* The socket's own `publish` reaches the same topics; this one is for the sender that is
|
|
665
|
-
* not a socket, a timer or a route handler broadcasting to a room.
|
|
646
|
+
* Publishes a message to every socket subscribed to a topic, from outside any socket.
|
|
666
647
|
*
|
|
667
648
|
* @param {string} topic
|
|
668
649
|
* @param {string|ArrayBuffer|Buffer} message
|
|
@@ -685,9 +666,7 @@ class Application extends Router {
|
|
|
685
666
|
}
|
|
686
667
|
|
|
687
668
|
/**
|
|
688
|
-
*
|
|
689
|
-
* walk `app.router.stack`. Here the application is the router, so it hands back itself and the
|
|
690
|
-
* walk finds the same layers.
|
|
669
|
+
* express 5's `app.router`, for a caller walking `app.router.stack`: the application itself.
|
|
691
670
|
*
|
|
692
671
|
* @returns {this}
|
|
693
672
|
*/
|
|
@@ -703,10 +682,8 @@ class Application extends Router {
|
|
|
703
682
|
if (!this.listening || !this.port) {
|
|
704
683
|
return null;
|
|
705
684
|
}
|
|
706
|
-
// uWS hands back the port
|
|
707
|
-
//
|
|
708
|
-
// reported as written, since what it resolved to is not readable back from here: node
|
|
709
|
-
// would say "::1" where this says "localhost".
|
|
685
|
+
// uWS hands back only the port: no host is "::" as node reports it, a hostname is
|
|
686
|
+
// reported as written where node would say "::1"
|
|
710
687
|
const host = this._listenHost;
|
|
711
688
|
if (!host) {
|
|
712
689
|
return { address: "::", family: "IPv6", port: this.port };
|
|
@@ -715,8 +692,7 @@ class Application extends Router {
|
|
|
715
692
|
}
|
|
716
693
|
|
|
717
694
|
/**
|
|
718
|
-
* The full mount path
|
|
719
|
-
* A top level app returns the empty string rather than "/".
|
|
695
|
+
* The full mount path through every parent, "" at the top level.
|
|
720
696
|
* @returns {string}
|
|
721
697
|
*/
|
|
722
698
|
path() {
|
|
@@ -731,9 +707,7 @@ class Application extends Router {
|
|
|
731
707
|
}
|
|
732
708
|
|
|
733
709
|
/**
|
|
734
|
-
* Registers a template engine for
|
|
735
|
-
*
|
|
736
|
-
* The leading dot is optional: "pug" and ".pug" register the same thing.
|
|
710
|
+
* Registers a template engine for an extension, with or without the dot.
|
|
737
711
|
*
|
|
738
712
|
* @param {string} ext file extension the engine handles
|
|
739
713
|
* @param {(path: string, options: object, callback: (err: Error|null, rendered?: string) => void) => void} fn
|
|
@@ -751,29 +725,22 @@ class Application extends Router {
|
|
|
751
725
|
}
|
|
752
726
|
|
|
753
727
|
/**
|
|
754
|
-
* Renders a view
|
|
755
|
-
* `
|
|
756
|
-
*
|
|
757
|
-
* `app.locals` and `options._locals` are merged into the options, in that order, so a
|
|
758
|
-
* per-request local wins. Caching follows the "view cache" setting unless `options.cache` says
|
|
759
|
-
* otherwise. A function in the options position is taken as the callback.
|
|
728
|
+
* Renders a view into the callback, without sending: `res.render()` is the one that responds.
|
|
729
|
+
* `app.locals` then `options._locals` are merged in, so a per-request local wins. Caching
|
|
730
|
+
* follows the "view cache" setting unless `options.cache` says otherwise.
|
|
760
731
|
*
|
|
761
732
|
* @param {string} name view name, resolved against the "views" setting
|
|
762
|
-
* @param {Record<string, any>|((err: Error|null, html?: string) => void)} [options] locals
|
|
763
|
-
* the
|
|
764
|
-
* @param {(err: Error|null, html?: string) => void} [callback]
|
|
765
|
-
* is what render is for, so leaving it out throws, as it does in Express
|
|
733
|
+
* @param {Record<string, any>|((err: Error|null, html?: string) => void)} [options] locals, or
|
|
734
|
+
* the callback in its place
|
|
735
|
+
* @param {(err: Error|null, html?: string) => void} [callback] required, as in Express
|
|
766
736
|
*/
|
|
767
737
|
render(name, options, callback) {
|
|
768
738
|
if (typeof options === "function") {
|
|
769
739
|
callback = /** @type {(err: Error|null, html?: string) => void} */ (options);
|
|
770
740
|
options = new NullObject();
|
|
771
741
|
}
|
|
772
|
-
// render exists to hand the result somewhere, so there is always a callback by this point:
|
|
773
|
-
// either the third argument or the second one, shuffled above
|
|
774
742
|
const done = /** @type {(err: Error|null, html?: string) => void} */ (callback);
|
|
775
|
-
// express's order
|
|
776
|
-
// and what was passed to this call wins over both
|
|
743
|
+
// express's order: app.locals, then res.locals as _locals, then what was passed
|
|
777
744
|
const opts = options || new NullObject();
|
|
778
745
|
options = new NullObject();
|
|
779
746
|
for (const key in this.locals) {
|
|
@@ -802,11 +769,8 @@ class Application extends Router {
|
|
|
802
769
|
view = new View(name, {
|
|
803
770
|
defaultEngine: this.get("view engine"),
|
|
804
771
|
root: this.get("views"),
|
|
805
|
-
// the object itself,
|
|
806
|
-
// through the prototype chain, and a
|
|
807
|
-
// sub-app rendering with the parent's engine went off to require() a module named
|
|
808
|
-
// after the extension. Express hands its own object over too, and means to: a view
|
|
809
|
-
// that loads an engine by require caches it back here
|
|
772
|
+
// the object itself, as express hands it: a sub-app reaches its parent's engines
|
|
773
|
+
// through the prototype chain, and a view caches a required engine back here
|
|
810
774
|
engines: this.engines
|
|
811
775
|
});
|
|
812
776
|
if (!view.path) {
|
|
@@ -838,23 +802,17 @@ class Application extends Router {
|
|
|
838
802
|
}
|
|
839
803
|
|
|
840
804
|
/**
|
|
841
|
-
* Stops accepting connections, lets in-flight requests finish, then emits 'close'.
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
* was waiting for. It still runs, but only once the last pending response is done, to drop the
|
|
846
|
-
* idle keep-alive connections nothing else would close.
|
|
847
|
-
*
|
|
848
|
-
* The callback is the first 'close' listener. Closing a server that was not listening still
|
|
849
|
-
* calls back with ERR_SERVER_NOT_RUNNING, the way node does.
|
|
805
|
+
* Stops accepting connections, lets in-flight requests finish, then emits 'close'. uWS's
|
|
806
|
+
* close() kills every connection, so it runs only after the last pending response, to drop the
|
|
807
|
+
* idle keep-alive ones. Closing a server that was not listening calls back with
|
|
808
|
+
* ERR_SERVER_NOT_RUNNING, as node does.
|
|
850
809
|
*
|
|
851
810
|
* @param {(err?: Error) => void} [callback] called once closed
|
|
852
811
|
* @returns {this} the app, for chaining
|
|
853
812
|
*/
|
|
854
813
|
close(callback) {
|
|
855
|
-
// the primary of a clustered app never bound anything
|
|
856
|
-
//
|
|
857
|
-
// and drains itself when the signal reaches it
|
|
814
|
+
// the primary of a clustered app never bound anything: it stops the workers, each of
|
|
815
|
+
// which drains itself
|
|
858
816
|
if (this._clusterHandle) {
|
|
859
817
|
this._clusterHandle.stop();
|
|
860
818
|
this._clusterHandle = undefined;
|
|
@@ -866,8 +824,7 @@ class Application extends Router {
|
|
|
866
824
|
}
|
|
867
825
|
const wasListening = this.listening;
|
|
868
826
|
this.listening = false;
|
|
869
|
-
//
|
|
870
|
-
// server that was not running still gets called back, with an error
|
|
827
|
+
// the callback is the first 'close' listener, as in Express
|
|
871
828
|
if (callback) {
|
|
872
829
|
this.once("close", () => {
|
|
873
830
|
if (wasListening) {
|
|
@@ -880,15 +837,14 @@ class Application extends Router {
|
|
|
880
837
|
});
|
|
881
838
|
}
|
|
882
839
|
if (!this.listenCalled || !wasListening) {
|
|
883
|
-
// a close
|
|
884
|
-
// 'close' serves both calls, which is what node does too
|
|
840
|
+
// a close during a drain does not emit again, as in node
|
|
885
841
|
if (!this._draining) {
|
|
886
842
|
process.nextTick(() => this.emit("close"));
|
|
887
843
|
}
|
|
888
844
|
return this;
|
|
889
845
|
}
|
|
890
846
|
if (this._listenSocket) {
|
|
891
|
-
|
|
847
|
+
loadUWS().us_listen_socket_close(this._listenSocket);
|
|
892
848
|
this._listenSocket = undefined;
|
|
893
849
|
}
|
|
894
850
|
this._draining = true;
|
|
@@ -901,12 +857,11 @@ class Application extends Router {
|
|
|
901
857
|
process.nextTick(finish);
|
|
902
858
|
return this;
|
|
903
859
|
}
|
|
904
|
-
//
|
|
905
|
-
//
|
|
860
|
+
// an aborted response only flips its flags, so the drain sweeps by them; the timer keeps
|
|
861
|
+
// the loop alive
|
|
906
862
|
const sweep = setInterval(() => {
|
|
907
863
|
let response = this._pending.head;
|
|
908
864
|
while (response !== null) {
|
|
909
|
-
// taken before the unlink, which nulls the pointers
|
|
910
865
|
const next = response._pendingNext;
|
|
911
866
|
if (response.finished || response.aborted) {
|
|
912
867
|
response._unlinkPending();
|
|
@@ -922,22 +877,15 @@ class Application extends Router {
|
|
|
922
877
|
}
|
|
923
878
|
}
|
|
924
879
|
|
|
925
|
-
// An app is a function, as
|
|
926
|
-
//
|
|
927
|
-
// given something it could not call.
|
|
928
|
-
//
|
|
929
|
-
// Tried once before and reverted the same day, because a callable app broke supertest: `request(app)`
|
|
930
|
-
// reads `typeof app === "function"` and wraps what it finds in http.createServer, and there was
|
|
931
|
-
// nothing underneath that could serve node's IncomingMessage. src/node-shim.js closes that hole.
|
|
880
|
+
// An app is a function, as in Express: vhost and the like call it. supertest then wraps it in
|
|
881
|
+
// http.createServer, which is what src/node-shim.js serves.
|
|
932
882
|
/** @param {object} [options] the settings express() takes, see the Application constructor */
|
|
933
883
|
module.exports = function (options) {
|
|
934
884
|
return new Application(options)._asCallable();
|
|
935
885
|
};
|
|
936
886
|
|
|
937
|
-
// the class
|
|
938
|
-
// method to that prototype adds it to every app, which is what the property is for.
|
|
887
|
+
// the class, so index.js exposes its prototype as express.application
|
|
939
888
|
module.exports.Application = Application;
|
|
940
889
|
|
|
941
|
-
//
|
|
942
|
-
// result of listen() looks for. See server-shape.js for what is answered and what is not.
|
|
890
|
+
// what makes an application answer as an http.Server, see server-shape.js
|
|
943
891
|
addServerMembers(Application.prototype);
|