ultimate-express 1.0.8 → 1.1.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/README.md +2 -1
- package/package.json +1 -1
- package/src/application.js +24 -24
- package/src/middlewares.js +2 -1
- package/src/response.js +28 -22
- package/src/router.js +11 -6
- /package/src/{workers/fs.js → worker.js} +0 -0
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ Optimized routes can be up to 10 times faster than normal routes, as they're usi
|
|
|
93
93
|
|
|
94
94
|
3. Do not set `body methods` to read body of requests with GET method or other methods that don't need a body. Reading body makes server about 10k req/sec slower.
|
|
95
95
|
|
|
96
|
-
4. By default, µExpress creates 1
|
|
96
|
+
4. By default, µExpress creates 1 (or 0 if your CPU has only 1 core) child thread to improve performance of reading files and computing hashes for etag. You can change this number by setting `threads` to a different number in `express()`, or set to 0 to disable thread pool (`express({ threads: 0 })`). Threads are shared between all express() instances, with largest `threads` number being used. Using more threads will not necessarily improve performance. Sometimes not using threads at all is faster, please [test](https://github.com/wg/wrk/) both options.
|
|
97
97
|
|
|
98
98
|
## Compatibility
|
|
99
99
|
|
|
@@ -110,6 +110,7 @@ In general, basically all features and options are supported. Use [Express 4.x d
|
|
|
110
110
|
- ✅ express.json()
|
|
111
111
|
- ✅ express.urlencoded()
|
|
112
112
|
- ✅ express.static()
|
|
113
|
+
- - Additionally you can pass `options.ifModifiedSince` to support If-Modified-Since header (this header is not supported in normal Express, but is supported in µExpress)
|
|
113
114
|
- ✅ express.text()
|
|
114
115
|
- ✅ express.raw()
|
|
115
116
|
- 🚧 express.request (this is not a constructor but a prototype for replacing methods)
|
package/package.json
CHANGED
package/src/application.js
CHANGED
|
@@ -10,25 +10,25 @@ const { Worker } = require("worker_threads");
|
|
|
10
10
|
|
|
11
11
|
const cpuCount = os.cpus().length;
|
|
12
12
|
|
|
13
|
-
let
|
|
14
|
-
let
|
|
15
|
-
const
|
|
13
|
+
let workers = [];
|
|
14
|
+
let taskKey = 0;
|
|
15
|
+
const workerTasks = {};
|
|
16
16
|
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
|
|
17
|
+
function createWorker() {
|
|
18
|
+
const worker = new Worker(path.join(__dirname, 'worker.js'));
|
|
19
|
+
workers.push(worker);
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
worker.on('message', (message) => {
|
|
22
22
|
if(message.err) {
|
|
23
|
-
|
|
23
|
+
workerTasks[message.key].reject(new Error(message.err));
|
|
24
24
|
} else {
|
|
25
|
-
|
|
25
|
+
workerTasks[message.key].resolve(message.data);
|
|
26
26
|
}
|
|
27
|
-
delete
|
|
27
|
+
delete workerTasks[message.key];
|
|
28
28
|
});
|
|
29
|
-
|
|
29
|
+
worker.unref();
|
|
30
30
|
|
|
31
|
-
return
|
|
31
|
+
return worker;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
class Application extends Router {
|
|
@@ -37,8 +37,8 @@ class Application extends Router {
|
|
|
37
37
|
if(!settings?.uwsOptions) {
|
|
38
38
|
settings.uwsOptions = {};
|
|
39
39
|
}
|
|
40
|
-
if(typeof settings.
|
|
41
|
-
settings.
|
|
40
|
+
if(typeof settings.threads !== 'number') {
|
|
41
|
+
settings.threads = cpuCount > 1 ? 1 : 0;
|
|
42
42
|
}
|
|
43
43
|
if(settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
|
|
44
44
|
this.uwsApp = uWS.SSLApp(settings.uwsOptions);
|
|
@@ -53,12 +53,12 @@ class Application extends Router {
|
|
|
53
53
|
settings: this.settings
|
|
54
54
|
};
|
|
55
55
|
this.listenCalled = false;
|
|
56
|
-
this.
|
|
57
|
-
for(let i = 0; i < settings.
|
|
58
|
-
if(
|
|
59
|
-
this.
|
|
56
|
+
this.workers = [];
|
|
57
|
+
for(let i = 0; i < settings.threads; i++) {
|
|
58
|
+
if(workers[i]) {
|
|
59
|
+
this.workers[i] = workers[i];
|
|
60
60
|
} else {
|
|
61
|
-
this.
|
|
61
|
+
this.workers[i] = createWorker();
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
this.port = undefined;
|
|
@@ -77,12 +77,12 @@ class Application extends Router {
|
|
|
77
77
|
|
|
78
78
|
readFileWithWorker(path) {
|
|
79
79
|
return new Promise((resolve, reject) => {
|
|
80
|
-
const
|
|
81
|
-
const key =
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
const worker = this.workers[Math.floor(Math.random() * this.workers.length)];
|
|
81
|
+
const key = taskKey++;
|
|
82
|
+
worker.postMessage({ key, type: 'readFile', path });
|
|
83
|
+
workerTasks[key] = { resolve, reject };
|
|
84
84
|
if(key > 1000000) {
|
|
85
|
-
|
|
85
|
+
taskKey = 0;
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
}
|
package/src/middlewares.js
CHANGED
|
@@ -17,10 +17,11 @@ function static(root, options) {
|
|
|
17
17
|
options.extensions = options.extensions.map(ext => ext.startsWith('.') ? ext.slice(1) : ext);
|
|
18
18
|
}
|
|
19
19
|
options.root = root;
|
|
20
|
+
options.skipEncodePath = true;
|
|
20
21
|
|
|
21
22
|
return (req, res, next) => {
|
|
22
23
|
const iq = req.url.indexOf('?');
|
|
23
|
-
let url = iq !== -1 ? req.url.substring(0, iq) : req.url;
|
|
24
|
+
let url = decodeURIComponent(iq !== -1 ? req.url.substring(0, iq) : req.url);
|
|
24
25
|
let _path = url;
|
|
25
26
|
let fullpath = path.resolve(path.join(options.root, url));
|
|
26
27
|
if(options.root && !fullpath.startsWith(path.resolve(options.root))) {
|
package/src/response.js
CHANGED
|
@@ -244,6 +244,9 @@ module.exports = class Response extends Writable {
|
|
|
244
244
|
this.status(500);
|
|
245
245
|
return done(new Error('path must be absolute or specify root to res.sendFile'));
|
|
246
246
|
}
|
|
247
|
+
if(!options.skipEncodePath) {
|
|
248
|
+
path = encodeURI(path);
|
|
249
|
+
}
|
|
247
250
|
path = decode(path);
|
|
248
251
|
if(path === -1) {
|
|
249
252
|
this.status(400);
|
|
@@ -349,27 +352,30 @@ module.exports = class Response extends Writable {
|
|
|
349
352
|
|
|
350
353
|
// range requests
|
|
351
354
|
let offset = 0, len = stat.size, ranged = false;
|
|
352
|
-
if(options.acceptRanges
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
355
|
+
if(options.acceptRanges) {
|
|
356
|
+
this.set('accept-ranges', 'bytes');
|
|
357
|
+
if(this.req.headers.range) {
|
|
358
|
+
let ranges = this.req.range(stat.size, {
|
|
359
|
+
combine: true
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// if-range
|
|
363
|
+
if(!isRangeFresh(this.req, this)) {
|
|
364
|
+
ranges = -2;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if(ranges === -1) {
|
|
368
|
+
this.status(416);
|
|
369
|
+
this.set('Content-Range', `bytes */${stat.size}`);
|
|
370
|
+
return done(new Error('Range Not Satisfiable'));
|
|
371
|
+
}
|
|
372
|
+
if(ranges !== -2 && ranges.length === 1) {
|
|
373
|
+
this.status(206);
|
|
374
|
+
this.set('Content-Range', `bytes ${ranges[0].start}-${ranges[0].end}/${stat.size}`);
|
|
375
|
+
offset = ranges[0].start;
|
|
376
|
+
len = ranges[0].end - ranges[0].start + 1;
|
|
377
|
+
ranged = true;
|
|
378
|
+
}
|
|
373
379
|
}
|
|
374
380
|
}
|
|
375
381
|
|
|
@@ -379,7 +385,7 @@ module.exports = class Response extends Writable {
|
|
|
379
385
|
}
|
|
380
386
|
|
|
381
387
|
// serve smaller files using workers
|
|
382
|
-
if(this.app.
|
|
388
|
+
if(this.app.workers.length && stat.size < 1024 * 1024 && !ranged) {
|
|
383
389
|
this.app.readFileWithWorker(fullpath).then((data) => {
|
|
384
390
|
if(this._res.aborted) {
|
|
385
391
|
return;
|
package/src/router.js
CHANGED
|
@@ -12,6 +12,7 @@ const methods = [
|
|
|
12
12
|
'search', 'subscribe', 'unsubscribe', 'report', 'mkactivity', 'mkcalendar',
|
|
13
13
|
'checkout', 'merge', 'm-search', 'notify', 'subscribe', 'unsubscribe', 'search'
|
|
14
14
|
];
|
|
15
|
+
const supportedUwsMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE'];
|
|
15
16
|
|
|
16
17
|
module.exports = class Router extends EventEmitter {
|
|
17
18
|
#paramCallbacks = new Map();
|
|
@@ -122,8 +123,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
122
123
|
routes.push(route);
|
|
123
124
|
// normal routes optimization
|
|
124
125
|
if(typeof route.pattern === 'string' && route.pattern !== '/*' && !this.parent && this.get('case sensitive routing') && this.uwsApp) {
|
|
125
|
-
|
|
126
|
-
if(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE'].includes(method)) {
|
|
126
|
+
if(supportedUwsMethods.includes(method)) {
|
|
127
127
|
const optimizedPath = this.#optimizeRoute(route, this._routes);
|
|
128
128
|
if(optimizedPath) {
|
|
129
129
|
this.#registerUwsRoute(route, optimizedPath);
|
|
@@ -153,13 +153,13 @@ module.exports = class Router extends EventEmitter {
|
|
|
153
153
|
return; // can only optimize router whos parent is listening
|
|
154
154
|
}
|
|
155
155
|
for(let cbroute of callback._routes) {
|
|
156
|
-
if(!needsConversionToRegex(cbroute.path) && cbroute.path !== '/*') {
|
|
156
|
+
if(!needsConversionToRegex(cbroute.path) && cbroute.path !== '/*' && supportedUwsMethods.includes(cbroute.method)) {
|
|
157
157
|
let optimizedRouterPath = this.#optimizeRoute(cbroute, callback._routes);
|
|
158
158
|
if(optimizedRouterPath) {
|
|
159
159
|
optimizedRouterPath = optimizedRouterPath.slice(0, -1);
|
|
160
160
|
const optimizedPath = [...optimizedPathToRouter, {
|
|
161
|
-
...route,
|
|
162
161
|
// fake route to update req._opPath and req.url
|
|
162
|
+
...route,
|
|
163
163
|
callbacks: [
|
|
164
164
|
(req, res, next) => {
|
|
165
165
|
next('skipPop');
|
|
@@ -263,8 +263,13 @@ module.exports = class Router extends EventEmitter {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
#handleError(err, request, response) {
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
let errorRoute = this.errorRoute, parent = this.parent;
|
|
267
|
+
while(!errorRoute && parent) {
|
|
268
|
+
errorRoute = parent.errorRoute;
|
|
269
|
+
parent = parent.parent;
|
|
270
|
+
}
|
|
271
|
+
if(errorRoute) {
|
|
272
|
+
return errorRoute(err, request, response, () => {
|
|
268
273
|
if(!response.headersSent) {
|
|
269
274
|
if(response.statusCode === 200) {
|
|
270
275
|
response.statusCode = 500;
|
|
File without changes
|