ultimate-express 1.3.0 → 1.3.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 -1
- package/package.json +6 -3
- package/src/application.js +10 -9
- package/src/middlewares.js +4 -4
- package/src/request.js +27 -26
- package/src/response.js +32 -40
- package/src/router.js +34 -31
- package/src/types.d.ts +49 -4
- package/src/utils.js +20 -10
- package/src/view.js +2 -1
package/README.md
CHANGED
|
@@ -289,6 +289,7 @@ Almost all middlewares that are compatible with Express are compatible with µEx
|
|
|
289
289
|
- ✅ [body-parser](https://npmjs.com/package/body-parser) (use `express.text()` etc instead for better performance)
|
|
290
290
|
- ✅ [cookie-parser](https://npmjs.com/package/cookie-parser)
|
|
291
291
|
- ✅ [cookie-session](https://npmjs.com/package/cookie-session)
|
|
292
|
+
- ✅ [compression](https://npmjs.com/package/compression)
|
|
292
293
|
- ✅ [serve-static](https://npmjs.com/package/serve-static) (use `express.static()` instead for better performance)
|
|
293
294
|
- ✅ [serve-index](https://npmjs.com/package/serve-index)
|
|
294
295
|
- ✅ [cors](https://npmjs.com/package/cors)
|
|
@@ -301,10 +302,10 @@ Almost all middlewares that are compatible with Express are compatible with µEx
|
|
|
301
302
|
- ✅ [express-rate-limit](https://npmjs.com/package/express-rate-limit)
|
|
302
303
|
- ✅ [express-subdomain](https://npmjs.com/package/express-subdomain)
|
|
303
304
|
- ✅ [vhost](https://npmjs.com/package/vhost)
|
|
305
|
+
- ✅ [tsoa](https://github.com/lukeautry/tsoa)
|
|
304
306
|
|
|
305
307
|
Middlewares and modules that are confirmed to not work:
|
|
306
308
|
|
|
307
|
-
- ❌ [compression](https://npmjs.com/package/compression) - doesn't error, but doesn't compress
|
|
308
309
|
- ❌ [express-async-errors](https://npmjs.com/package/express-async-errors) - doesn't work, use `app.set('catch async errors', true)` instead.
|
|
309
310
|
|
|
310
311
|
## Tested view engines
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultimate-express",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "The Ultimate Express. Fastest http server with full Express compatibility, based on uWebSockets.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "node tests/index.js"
|
|
7
|
+
"test": "node tests/index.js",
|
|
8
|
+
"dev": "node --inspect=9229 demo/index.js"
|
|
8
9
|
},
|
|
9
10
|
"engines": {
|
|
10
11
|
"node": ">=16"
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
"@types/express": "^4.0.0",
|
|
43
44
|
"accepts": "^1.3.8",
|
|
44
45
|
"bytes": "^3.1.2",
|
|
45
|
-
"cookie": "^0.
|
|
46
|
+
"cookie": "^1.0.1",
|
|
46
47
|
"cookie-signature": "^1.2.1",
|
|
47
48
|
"encodeurl": "^2.0.0",
|
|
48
49
|
"etag": "^1.8.1",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"body-parser": "^1.20.3",
|
|
66
|
+
"compression": "^1.7.4",
|
|
65
67
|
"cookie-parser": "^1.4.6",
|
|
66
68
|
"cookie-session": "^2.1.0",
|
|
67
69
|
"cors": "^2.8.5",
|
|
@@ -81,6 +83,7 @@
|
|
|
81
83
|
"multer": "^1.4.5-lts.1",
|
|
82
84
|
"mustache-express": "^1.3.2",
|
|
83
85
|
"pako": "^2.1.0",
|
|
86
|
+
"pkg-pr-new": "^0.0.29",
|
|
84
87
|
"pug": "^3.0.3",
|
|
85
88
|
"response-time": "^2.3.2",
|
|
86
89
|
"serve-index": "^1.9.1",
|
package/src/application.js
CHANGED
|
@@ -16,7 +16,7 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
const uWS = require("uWebSockets.js");
|
|
18
18
|
const Router = require("./router.js");
|
|
19
|
-
const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator, fastQueryParse } = require("./utils.js");
|
|
19
|
+
const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator, fastQueryParse, NullObject } = require("./utils.js");
|
|
20
20
|
const querystring = require("fast-querystring");
|
|
21
21
|
const ViewClass = require("./view.js");
|
|
22
22
|
const path = require("path");
|
|
@@ -27,7 +27,7 @@ const cpuCount = os.cpus().length;
|
|
|
27
27
|
|
|
28
28
|
let workers = [];
|
|
29
29
|
let taskKey = 0;
|
|
30
|
-
const workerTasks =
|
|
30
|
+
const workerTasks = new NullObject();
|
|
31
31
|
|
|
32
32
|
function createWorker() {
|
|
33
33
|
const worker = new Worker(path.join(__dirname, 'worker.js'));
|
|
@@ -47,7 +47,7 @@ function createWorker() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
class Application extends Router {
|
|
50
|
-
constructor(settings =
|
|
50
|
+
constructor(settings = new NullObject()) {
|
|
51
51
|
super(settings);
|
|
52
52
|
if(!settings?.uwsOptions) {
|
|
53
53
|
settings.uwsOptions = {};
|
|
@@ -62,8 +62,8 @@ class Application extends Router {
|
|
|
62
62
|
this.uwsApp = uWS.App(settings.uwsOptions);
|
|
63
63
|
this.ssl = false;
|
|
64
64
|
}
|
|
65
|
-
this.cache =
|
|
66
|
-
this.engines =
|
|
65
|
+
this.cache = new NullObject();
|
|
66
|
+
this.engines = new NullObject();
|
|
67
67
|
this.locals = {
|
|
68
68
|
settings: this.settings
|
|
69
69
|
};
|
|
@@ -206,7 +206,7 @@ class Application extends Router {
|
|
|
206
206
|
throw err;
|
|
207
207
|
}
|
|
208
208
|
this.port = uWS.us_socket_local_port(socket);
|
|
209
|
-
callback(this.port);
|
|
209
|
+
if(callback) callback(this.port);
|
|
210
210
|
};
|
|
211
211
|
let fn = 'listen';
|
|
212
212
|
let args = [];
|
|
@@ -229,6 +229,7 @@ class Application extends Router {
|
|
|
229
229
|
}
|
|
230
230
|
this.listenCalled = true;
|
|
231
231
|
this.uwsApp[fn](...args);
|
|
232
|
+
return this.uwsApp;
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
address() {
|
|
@@ -260,10 +261,10 @@ class Application extends Router {
|
|
|
260
261
|
render(name, options, callback) {
|
|
261
262
|
if(typeof options === 'function') {
|
|
262
263
|
callback = options;
|
|
263
|
-
options =
|
|
264
|
+
options = new NullObject();
|
|
264
265
|
}
|
|
265
266
|
if(!options) {
|
|
266
|
-
options =
|
|
267
|
+
options = new NullObject();
|
|
267
268
|
} else {
|
|
268
269
|
options = Object.assign({}, options);
|
|
269
270
|
}
|
|
@@ -318,4 +319,4 @@ class Application extends Router {
|
|
|
318
319
|
|
|
319
320
|
module.exports = function(options) {
|
|
320
321
|
return new Application(options);
|
|
321
|
-
}
|
|
322
|
+
}
|
package/src/middlewares.js
CHANGED
|
@@ -20,10 +20,10 @@ const bytes = require('bytes');
|
|
|
20
20
|
const zlib = require('fast-zlib');
|
|
21
21
|
const typeis = require('type-is');
|
|
22
22
|
const querystring = require('fast-querystring');
|
|
23
|
-
const { fastQueryParse } = require('./utils.js');
|
|
23
|
+
const { fastQueryParse, NullObject } = require('./utils.js');
|
|
24
24
|
|
|
25
25
|
function static(root, options) {
|
|
26
|
-
if(!options) options =
|
|
26
|
+
if(!options) options = new NullObject();
|
|
27
27
|
if(typeof options.index === 'undefined') options.index = 'index.html';
|
|
28
28
|
if(typeof options.redirect === 'undefined') options.redirect = true;
|
|
29
29
|
if(typeof options.fallthrough === 'undefined') options.fallthrough = true;
|
|
@@ -132,7 +132,7 @@ function createInflate(contentEncoding) {
|
|
|
132
132
|
function createBodyParser(defaultType, beforeReturn) {
|
|
133
133
|
return function(options) {
|
|
134
134
|
if(typeof options !== 'object') {
|
|
135
|
-
options =
|
|
135
|
+
options = new NullObject();
|
|
136
136
|
}
|
|
137
137
|
if(typeof options.limit === 'undefined') options.limit = bytes('100kb');
|
|
138
138
|
else options.limit = bytes(options.limit);
|
|
@@ -160,7 +160,7 @@ function createBodyParser(defaultType, beforeReturn) {
|
|
|
160
160
|
return next();
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
req.body =
|
|
163
|
+
req.body = new NullObject();
|
|
164
164
|
|
|
165
165
|
// skip reading body for non-json content type
|
|
166
166
|
if(!type) {
|
package/src/request.js
CHANGED
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
const { patternToRegex, deprecated } = require("./utils.js");
|
|
17
|
+
const { patternToRegex, deprecated, NullObject } = require("./utils.js");
|
|
18
18
|
const accepts = require("accepts");
|
|
19
19
|
const typeis = require("type-is");
|
|
20
20
|
const parseRange = require("range-parser");
|
|
@@ -64,7 +64,7 @@ module.exports = class Request extends Readable {
|
|
|
64
64
|
this._opPath = this._opPath.slice(0, -1);
|
|
65
65
|
}
|
|
66
66
|
this.method = req.getCaseSensitiveMethod().toUpperCase();
|
|
67
|
-
this.params =
|
|
67
|
+
this.params = new NullObject();
|
|
68
68
|
|
|
69
69
|
this._gotParams = new Set();
|
|
70
70
|
this._stack = [];
|
|
@@ -206,7 +206,7 @@ module.exports = class Request extends Readable {
|
|
|
206
206
|
if(qp) {
|
|
207
207
|
this.#cachedQuery = qp(this.urlQuery.slice(1));
|
|
208
208
|
} else {
|
|
209
|
-
this.#cachedQuery =
|
|
209
|
+
this.#cachedQuery = new NullObject();
|
|
210
210
|
}
|
|
211
211
|
return this.#cachedQuery;
|
|
212
212
|
}
|
|
@@ -351,53 +351,54 @@ module.exports = class Request extends Readable {
|
|
|
351
351
|
if(this.#cachedHeaders) {
|
|
352
352
|
return this.#cachedHeaders;
|
|
353
353
|
}
|
|
354
|
-
|
|
355
|
-
this.#rawHeadersEntries.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if(
|
|
354
|
+
this.#cachedHeaders = new NullObject();
|
|
355
|
+
for (let index = 0, len = this.#rawHeadersEntries.length; index < len; index++) {
|
|
356
|
+
let [key, value] = this.#rawHeadersEntries[index];
|
|
357
|
+
key = key.toLowerCase();
|
|
358
|
+
if(this.#cachedHeaders[key]) {
|
|
359
359
|
if(discardedDuplicates.includes(key)) {
|
|
360
|
-
|
|
360
|
+
continue;
|
|
361
361
|
}
|
|
362
362
|
if(key === 'cookie') {
|
|
363
|
-
|
|
363
|
+
this.#cachedHeaders[key] += '; ' + value;
|
|
364
364
|
} else if(key === 'set-cookie') {
|
|
365
|
-
|
|
365
|
+
this.#cachedHeaders[key].push(value);
|
|
366
366
|
} else {
|
|
367
|
-
|
|
367
|
+
this.#cachedHeaders[key] += ', ' + value;
|
|
368
368
|
}
|
|
369
|
-
|
|
369
|
+
continue;
|
|
370
370
|
}
|
|
371
371
|
if(key === 'set-cookie') {
|
|
372
|
-
|
|
372
|
+
this.#cachedHeaders[key] = [value];
|
|
373
373
|
} else {
|
|
374
|
-
|
|
374
|
+
this.#cachedHeaders[key] = value;
|
|
375
375
|
}
|
|
376
|
-
}
|
|
377
|
-
this.#cachedHeaders
|
|
378
|
-
return headers;
|
|
376
|
+
}
|
|
377
|
+
return this.#cachedHeaders;
|
|
379
378
|
}
|
|
380
379
|
|
|
381
380
|
get headersDistinct() {
|
|
382
381
|
if(this.#cachedDistinctHeaders) {
|
|
383
382
|
return this.#cachedDistinctHeaders;
|
|
384
383
|
}
|
|
385
|
-
|
|
384
|
+
this.#cachedDistinctHeaders = new NullObject();
|
|
386
385
|
this.#rawHeadersEntries.forEach((val) => {
|
|
387
|
-
|
|
388
|
-
|
|
386
|
+
const [key, value] = val;
|
|
387
|
+
if(!this.#cachedDistinctHeaders[key]) {
|
|
388
|
+
this.#cachedDistinctHeaders[key] = [value];
|
|
389
|
+
return;
|
|
389
390
|
}
|
|
390
|
-
|
|
391
|
+
this.#cachedDistinctHeaders[key].push(value);
|
|
391
392
|
});
|
|
392
|
-
this.#cachedDistinctHeaders
|
|
393
|
-
return headers;
|
|
393
|
+
return this.#cachedDistinctHeaders;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
get rawHeaders() {
|
|
397
397
|
const res = [];
|
|
398
|
-
this.#rawHeadersEntries.
|
|
398
|
+
for (let index = 0, len = this.#rawHeadersEntries.length; index < len; index++) {
|
|
399
|
+
const val = this.#rawHeadersEntries[index];
|
|
399
400
|
res.push(val[0], val[1]);
|
|
400
|
-
}
|
|
401
|
+
}
|
|
401
402
|
return res;
|
|
402
403
|
}
|
|
403
404
|
}
|
package/src/response.js
CHANGED
|
@@ -20,7 +20,7 @@ const vary = require("vary");
|
|
|
20
20
|
const encodeUrl = require("encodeurl");
|
|
21
21
|
const {
|
|
22
22
|
normalizeType, stringify, deprecated, UP_PATH_REGEXP, decode,
|
|
23
|
-
containsDotFile, isPreconditionFailure, isRangeFresh,
|
|
23
|
+
containsDotFile, isPreconditionFailure, isRangeFresh, NullObject
|
|
24
24
|
} = require("./utils.js");
|
|
25
25
|
const { Writable } = require("stream");
|
|
26
26
|
const { isAbsolute } = require("path");
|
|
@@ -70,7 +70,7 @@ module.exports = class Response extends Writable {
|
|
|
70
70
|
this._res = res;
|
|
71
71
|
this.headersSent = false;
|
|
72
72
|
this.app = app;
|
|
73
|
-
this.locals =
|
|
73
|
+
this.locals = new NullObject();
|
|
74
74
|
this.finished = false;
|
|
75
75
|
this.aborted = false;
|
|
76
76
|
this.statusCode = 200;
|
|
@@ -208,12 +208,9 @@ module.exports = class Response extends Writable {
|
|
|
208
208
|
_implicitHeader() {
|
|
209
209
|
// compatibility function
|
|
210
210
|
// usually should send headers but this is useless for us
|
|
211
|
-
|
|
211
|
+
this.writeHead(this.statusCode);
|
|
212
212
|
}
|
|
213
213
|
status(code) {
|
|
214
|
-
if(this.headersSent) {
|
|
215
|
-
throw new Error('Can\'t set status: Response was already sent');
|
|
216
|
-
}
|
|
217
214
|
this.statusCode = parseInt(code);
|
|
218
215
|
return this;
|
|
219
216
|
}
|
|
@@ -228,7 +225,7 @@ module.exports = class Response extends Writable {
|
|
|
228
225
|
this._res.cork(() => {
|
|
229
226
|
if(!this.headersSent) {
|
|
230
227
|
const etagFn = this.app.get('etag fn');
|
|
231
|
-
if(data && !this.headers['etag'] &&
|
|
228
|
+
if(etagFn && data && !this.headers['etag'] && !this.req.noEtag) {
|
|
232
229
|
this.headers['etag'] = etagFn(data);
|
|
233
230
|
}
|
|
234
231
|
const fresh = this.req.fresh;
|
|
@@ -241,8 +238,9 @@ module.exports = class Response extends Writable {
|
|
|
241
238
|
return;
|
|
242
239
|
}
|
|
243
240
|
}
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
const contentLength = this.headers['content-length'];
|
|
242
|
+
if(!data && contentLength) {
|
|
243
|
+
this._res.endWithoutBody(contentLength.toString());
|
|
246
244
|
} else {
|
|
247
245
|
if(data instanceof Buffer) {
|
|
248
246
|
data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
@@ -282,21 +280,23 @@ module.exports = class Response extends Writable {
|
|
|
282
280
|
}
|
|
283
281
|
if(typeof body === 'string') {
|
|
284
282
|
const contentType = this.headers['content-type'];
|
|
285
|
-
if(
|
|
283
|
+
if(!contentType) {
|
|
284
|
+
this.headers['content-type'] = 'text/html; charset=utf-8';
|
|
285
|
+
} else if(!contentType.includes(';')) {
|
|
286
286
|
this.headers['content-type'] += '; charset=utf-8';
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
return this.end(body);
|
|
290
290
|
}
|
|
291
|
-
sendFile(path, options =
|
|
291
|
+
sendFile(path, options = new NullObject(), callback) {
|
|
292
292
|
if(typeof path !== 'string') {
|
|
293
293
|
throw new TypeError('path argument is required to res.sendFile');
|
|
294
294
|
}
|
|
295
295
|
if(typeof options === 'function') {
|
|
296
296
|
callback = options;
|
|
297
|
-
options =
|
|
297
|
+
options = new NullObject();
|
|
298
298
|
}
|
|
299
|
-
if(!options) options =
|
|
299
|
+
if(!options) options = new NullObject();
|
|
300
300
|
let done = callback;
|
|
301
301
|
if(!done) done = this.req.next;
|
|
302
302
|
// default options
|
|
@@ -362,7 +362,8 @@ module.exports = class Response extends Writable {
|
|
|
362
362
|
this.status(403);
|
|
363
363
|
return done(new Error('Forbidden'));
|
|
364
364
|
case 'ignore_files':
|
|
365
|
-
|
|
365
|
+
const len = parts.length;
|
|
366
|
+
if(len > 1 && parts[len - 1].startsWith('.')) {
|
|
366
367
|
this.status(404);
|
|
367
368
|
return done(new Error('Not found'));
|
|
368
369
|
}
|
|
@@ -409,7 +410,7 @@ module.exports = class Response extends Writable {
|
|
|
409
410
|
}
|
|
410
411
|
|
|
411
412
|
// etag
|
|
412
|
-
if(options.etag && !this.headers['etag']
|
|
413
|
+
if(options.etag && etagFn && !this.headers['etag']) {
|
|
413
414
|
this.headers['etag'] = etagFn(stat);
|
|
414
415
|
}
|
|
415
416
|
if(!options.etag) {
|
|
@@ -443,9 +444,10 @@ module.exports = class Response extends Writable {
|
|
|
443
444
|
}
|
|
444
445
|
if(ranges !== -2 && ranges.length === 1) {
|
|
445
446
|
this.status(206);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
const range = ranges[0];
|
|
448
|
+
this.headers['content-range'] = `bytes ${range.start}-${range.end}/${stat.size}`;
|
|
449
|
+
offset = range.start;
|
|
450
|
+
len = range.end - range.start + 1;
|
|
449
451
|
ranged = true;
|
|
450
452
|
}
|
|
451
453
|
}
|
|
@@ -488,16 +490,16 @@ module.exports = class Response extends Writable {
|
|
|
488
490
|
download(path, filename, options, callback) {
|
|
489
491
|
let done = callback;
|
|
490
492
|
let name = filename;
|
|
491
|
-
let opts = options ||
|
|
493
|
+
let opts = options || new NullObject();
|
|
492
494
|
|
|
493
495
|
// support function as second or third arg
|
|
494
496
|
if (typeof filename === 'function') {
|
|
495
497
|
done = filename;
|
|
496
498
|
name = null;
|
|
497
|
-
opts =
|
|
499
|
+
opts = new NullObject();
|
|
498
500
|
} else if (typeof options === 'function') {
|
|
499
501
|
done = options;
|
|
500
|
-
opts =
|
|
502
|
+
opts = new NullObject();
|
|
501
503
|
}
|
|
502
504
|
|
|
503
505
|
// support optional filename, where options may be in it's place
|
|
@@ -518,7 +520,7 @@ module.exports = class Response extends Writable {
|
|
|
518
520
|
}
|
|
519
521
|
set(field, value) {
|
|
520
522
|
if(this.headersSent) {
|
|
521
|
-
throw new Error('
|
|
523
|
+
throw new Error('Cannot set headers after they are sent to the client');
|
|
522
524
|
}
|
|
523
525
|
if(typeof field === 'object') {
|
|
524
526
|
for(const header in field) {
|
|
@@ -537,18 +539,12 @@ module.exports = class Response extends Writable {
|
|
|
537
539
|
}
|
|
538
540
|
return this;
|
|
539
541
|
}
|
|
540
|
-
header
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
setHeader(field, value) {
|
|
544
|
-
return this.set(field, value);
|
|
545
|
-
}
|
|
542
|
+
header = this.set;
|
|
543
|
+
setHeader = this.set;
|
|
546
544
|
get(field) {
|
|
547
545
|
return this.headers[field.toLowerCase()];
|
|
548
546
|
}
|
|
549
|
-
getHeader
|
|
550
|
-
return this.get(field);
|
|
551
|
-
}
|
|
547
|
+
getHeader = this.get;
|
|
552
548
|
removeHeader(field) {
|
|
553
549
|
delete this.headers[field.toLowerCase()];
|
|
554
550
|
return this;
|
|
@@ -577,10 +573,10 @@ module.exports = class Response extends Writable {
|
|
|
577
573
|
render(view, options, callback) {
|
|
578
574
|
if(typeof options === 'function') {
|
|
579
575
|
callback = options;
|
|
580
|
-
options =
|
|
576
|
+
options = new NullObject();
|
|
581
577
|
}
|
|
582
578
|
if(!options) {
|
|
583
|
-
options =
|
|
579
|
+
options = new NullObject();
|
|
584
580
|
} else {
|
|
585
581
|
options = Object.assign({}, options);
|
|
586
582
|
}
|
|
@@ -594,7 +590,7 @@ module.exports = class Response extends Writable {
|
|
|
594
590
|
}
|
|
595
591
|
cookie(name, value, options) {
|
|
596
592
|
if(!options) {
|
|
597
|
-
options =
|
|
593
|
+
options = new NullObject();
|
|
598
594
|
}
|
|
599
595
|
let val = typeof value === 'object' ? "j:"+JSON.stringify(value) : String(value);
|
|
600
596
|
if(options.maxAge != null) {
|
|
@@ -707,14 +703,10 @@ module.exports = class Response extends Writable {
|
|
|
707
703
|
let ct = type.indexOf('/') === -1
|
|
708
704
|
? (mime.contentType(type) || 'application/octet-stream')
|
|
709
705
|
: type;
|
|
710
|
-
|
|
711
|
-
ct += '; charset=UTF-8';
|
|
712
|
-
}
|
|
706
|
+
|
|
713
707
|
return this.set('content-type', ct);
|
|
714
708
|
}
|
|
715
|
-
contentType
|
|
716
|
-
return this.type(type);
|
|
717
|
-
}
|
|
709
|
+
contentType = this.type;
|
|
718
710
|
|
|
719
711
|
vary(field) {
|
|
720
712
|
vary(this, field);
|
package/src/router.js
CHANGED
|
@@ -18,6 +18,7 @@ const { patternToRegex, needsConversionToRegex, deprecated, findIndexStartingFro
|
|
|
18
18
|
const Response = require("./response.js");
|
|
19
19
|
const Request = require("./request.js");
|
|
20
20
|
const { EventEmitter } = require("tseep");
|
|
21
|
+
const { NullObject } = require("./utils.js");
|
|
21
22
|
|
|
22
23
|
let routeKey = 0;
|
|
23
24
|
|
|
@@ -30,6 +31,8 @@ const methods = [
|
|
|
30
31
|
];
|
|
31
32
|
const supportedUwsMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE'];
|
|
32
33
|
|
|
34
|
+
const regExParam = /:(\w+)/g;
|
|
35
|
+
|
|
33
36
|
module.exports = class Router extends EventEmitter {
|
|
34
37
|
constructor(settings = {}) {
|
|
35
38
|
super();
|
|
@@ -263,12 +266,12 @@ module.exports = class Router extends EventEmitter {
|
|
|
263
266
|
method = 'del';
|
|
264
267
|
}
|
|
265
268
|
if(!route.optimizedRouter && route.path.includes(":")) {
|
|
266
|
-
route.optimizedParams = route.path.match(
|
|
269
|
+
route.optimizedParams = route.path.match(regExParam).map(p => p.slice(1));
|
|
267
270
|
}
|
|
268
271
|
const fn = async (res, req) => {
|
|
269
272
|
const { request, response } = this.handleRequest(res, req);
|
|
270
273
|
if(route.optimizedParams) {
|
|
271
|
-
request.optimizedParams =
|
|
274
|
+
request.optimizedParams = new NullObject();
|
|
272
275
|
for(let i = 0; i < route.optimizedParams.length; i++) {
|
|
273
276
|
request.optimizedParams[route.optimizedParams[i]] = req.getParameter(i);
|
|
274
277
|
}
|
|
@@ -281,7 +284,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
281
284
|
}
|
|
282
285
|
};
|
|
283
286
|
route.optimizedPath = optimizedPath;
|
|
284
|
-
let replacedPath = route.path.replace(
|
|
287
|
+
let replacedPath = route.path.replace(regExParam, ':x');
|
|
285
288
|
this.uwsApp[method](replacedPath, fn);
|
|
286
289
|
if(!this.get('strict routing') && route.path[route.path.length - 1] !== '/') {
|
|
287
290
|
this.uwsApp[method](replacedPath + '/', fn);
|
|
@@ -319,7 +322,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
319
322
|
|
|
320
323
|
_extractParams(pattern, path) {
|
|
321
324
|
let match = pattern.exec(path);
|
|
322
|
-
const obj = match?.groups ??
|
|
325
|
+
const obj = match?.groups ?? new NullObject();
|
|
323
326
|
for(let i = 1; i < match.length; i++) {
|
|
324
327
|
obj[i - 1] = match[i];
|
|
325
328
|
}
|
|
@@ -342,7 +345,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
342
345
|
}
|
|
343
346
|
}
|
|
344
347
|
} else {
|
|
345
|
-
req.params =
|
|
348
|
+
req.params = new NullObject();
|
|
346
349
|
if(req._paramStack.length > 0) {
|
|
347
350
|
for(let params of req._paramStack) {
|
|
348
351
|
req.params = {...params, ...req.params};
|
|
@@ -406,33 +409,33 @@ module.exports = class Router extends EventEmitter {
|
|
|
406
409
|
}
|
|
407
410
|
|
|
408
411
|
async _routeRequest(req, res, startIndex = 0, routes = this._routes, skipCheck = false, skipUntil) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if(!
|
|
413
|
-
if
|
|
414
|
-
|
|
415
|
-
return resolve(false);
|
|
416
|
-
}
|
|
417
|
-
// on optimized routes, there can be more routes, so we have to use unoptimized routing and skip until we find route we stopped at
|
|
418
|
-
return resolve(this._routeRequest(req, res, 0, this._routes, false, skipUntil));
|
|
412
|
+
let routeIndex = skipCheck ? startIndex : findIndexStartingFrom(routes, r => (r.all || r.method === req.method || (r.gettable && req.method === 'HEAD')) && this._pathMatches(r, req), startIndex);
|
|
413
|
+
const route = routes[routeIndex];
|
|
414
|
+
if(!route) {
|
|
415
|
+
if(!skipCheck) {
|
|
416
|
+
// on normal unoptimized routes, if theres no match then there is no route
|
|
417
|
+
return false;
|
|
419
418
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
419
|
+
// on optimized routes, there can be more routes, so we have to use unoptimized routing and skip until we find route we stopped at
|
|
420
|
+
return this._routeRequest(req, res, 0, this._routes, false, skipUntil);
|
|
421
|
+
}
|
|
422
|
+
let callbackindex = 0;
|
|
423
|
+
const continueRoute = await this._preprocessRequest(req, res, route);
|
|
424
|
+
if(route.use) {
|
|
425
|
+
const strictRouting = this.get('strict routing');
|
|
426
|
+
req._stack.push(route.path);
|
|
427
|
+
req._opPath = req.path.replace(this.getFullMountpath(req), '');
|
|
428
|
+
if(strictRouting) {
|
|
429
|
+
if(req.endsWithSlash && req.path !== '/') {
|
|
430
|
+
req._opPath += '/';
|
|
432
431
|
}
|
|
433
|
-
|
|
434
|
-
|
|
432
|
+
} else if(req.endsWithSlash && req.path !== '/') {
|
|
433
|
+
req._opPath = req._opPath.slice(0, -1);
|
|
435
434
|
}
|
|
435
|
+
req.url = req._opPath + req.urlQuery;
|
|
436
|
+
if(req.url === '') req.url = '/';
|
|
437
|
+
}
|
|
438
|
+
return new Promise((resolve) => {
|
|
436
439
|
const next = async (thingamabob) => {
|
|
437
440
|
if(thingamabob) {
|
|
438
441
|
if(thingamabob === 'route' || thingamabob === 'skipPop') {
|
|
@@ -533,7 +536,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
533
536
|
}
|
|
534
537
|
|
|
535
538
|
route(path) {
|
|
536
|
-
let fns =
|
|
539
|
+
let fns = new NullObject();
|
|
537
540
|
for(let method of methods) {
|
|
538
541
|
fns[method] = (...callbacks) => {
|
|
539
542
|
return this.createRoute(method.toUpperCase(), path, fns, ...callbacks);
|
|
@@ -564,4 +567,4 @@ module.exports = class Router extends EventEmitter {
|
|
|
564
567
|
`</body>\n` +
|
|
565
568
|
`</html>\n`);
|
|
566
569
|
}
|
|
567
|
-
}
|
|
570
|
+
}
|
package/src/types.d.ts
CHANGED
|
@@ -1,4 +1,49 @@
|
|
|
1
|
-
declare module
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
declare module "ultimate-express" {
|
|
2
|
+
import e from "@types/express";
|
|
3
|
+
import { AppOptions } from "uWebSockets.js";
|
|
4
|
+
|
|
5
|
+
type Settings = {
|
|
6
|
+
uwsOptions?: AppOptions;
|
|
7
|
+
threads?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
namespace express {
|
|
11
|
+
export import json = e.json;
|
|
12
|
+
export import raw = e.raw;
|
|
13
|
+
export import text = e.text;
|
|
14
|
+
|
|
15
|
+
// export import application = e.application;
|
|
16
|
+
export import request = e.request;
|
|
17
|
+
export import response = e.response;
|
|
18
|
+
|
|
19
|
+
export import static = e.static;
|
|
20
|
+
// export import query = e.query;
|
|
21
|
+
|
|
22
|
+
export import urlencoded = e.urlencoded;
|
|
23
|
+
|
|
24
|
+
export import RouterOptions = e.RouterOptions;
|
|
25
|
+
export import Application = e.Application;
|
|
26
|
+
export import CookieOptions = e.CookieOptions;
|
|
27
|
+
export import Errback = e.Errback;
|
|
28
|
+
export import ErrorRequestHandler = e.ErrorRequestHandler;
|
|
29
|
+
export import Express = e.Express;
|
|
30
|
+
export import Handler = e.Handler;
|
|
31
|
+
export import IRoute = e.IRoute;
|
|
32
|
+
export import IRouter = e.IRouter;
|
|
33
|
+
export import IRouterHandler = e.IRouterHandler;
|
|
34
|
+
export import IRouterMatcher = e.IRouterMatcher;
|
|
35
|
+
export import MediaType = e.MediaType;
|
|
36
|
+
export import NextFunction = e.NextFunction;
|
|
37
|
+
export import Locals = e.Locals;
|
|
38
|
+
export import Request = e.Request;
|
|
39
|
+
export import RequestHandler = e.RequestHandler;
|
|
40
|
+
export import RequestParamHandler = e.RequestParamHandler;
|
|
41
|
+
export import Response = e.Response;
|
|
42
|
+
export import Router = e.Router;
|
|
43
|
+
export import Send = e.Send;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function express(settings?: Settings): e.Express;
|
|
47
|
+
|
|
48
|
+
export = express;
|
|
49
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -23,7 +23,11 @@ const etag = require("etag");
|
|
|
23
23
|
const { Stats } = require("fs");
|
|
24
24
|
|
|
25
25
|
function fastQueryParse(query, options) {
|
|
26
|
-
|
|
26
|
+
const len = query.length;
|
|
27
|
+
if(len === 0){
|
|
28
|
+
return new NullObject();
|
|
29
|
+
}
|
|
30
|
+
if(len <= 128) {
|
|
27
31
|
if(!query.includes('[') && !query.includes('%5B') && !query.includes('.') && !query.includes('%2E')) {
|
|
28
32
|
return querystring.parse(query);
|
|
29
33
|
}
|
|
@@ -44,9 +48,9 @@ function patternToRegex(pattern, isPrefix = false) {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
let regexPattern = pattern
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
51
|
+
.replaceAll('.', '\\.')
|
|
52
|
+
.replaceAll('-', '\\-')
|
|
53
|
+
.replaceAll('*', '(.*)') // Convert * to .*
|
|
50
54
|
.replace(/:(\w+)(\(.+?\))?/g, (match, param, regex) => {
|
|
51
55
|
return `(?<${param}>${regex ? regex + '($|\\/)' : '[^/]+'})`;
|
|
52
56
|
}); // Convert :param to capture group
|
|
@@ -101,12 +105,13 @@ function acceptParams(str) {
|
|
|
101
105
|
const parts = str.split(/ *; */);
|
|
102
106
|
const ret = { value: parts[0], quality: 1, params: {} }
|
|
103
107
|
|
|
104
|
-
for (let i = 1
|
|
108
|
+
for (let i = 1, len = parts.length; i < len; ++i) {
|
|
105
109
|
const pms = parts[i].split(/ *= */);
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
const [pms_0, pms_1] = pms;
|
|
111
|
+
if ('q' === pms_0) {
|
|
112
|
+
ret.quality = parseFloat(pms_1);
|
|
108
113
|
} else {
|
|
109
|
-
ret.params[
|
|
114
|
+
ret.params[pms_0] = pms_1;
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
117
|
|
|
@@ -218,7 +223,7 @@ function decode (path) {
|
|
|
218
223
|
const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
|
|
219
224
|
|
|
220
225
|
function containsDotFile(parts) {
|
|
221
|
-
for(let i = 0
|
|
226
|
+
for(let i = 0, len = parts.length; i < len; i++) {
|
|
222
227
|
const part = parts[i];
|
|
223
228
|
if(part.length > 1 && part[0] === '.') {
|
|
224
229
|
return true;
|
|
@@ -315,6 +320,10 @@ function isRangeFresh(req, res) {
|
|
|
315
320
|
return parseHttpDate(lastModified) <= parseHttpDate(ifRange);
|
|
316
321
|
}
|
|
317
322
|
|
|
323
|
+
// fast null object
|
|
324
|
+
const NullObject = function() {};
|
|
325
|
+
NullObject.prototype = Object.create(null);
|
|
326
|
+
|
|
318
327
|
module.exports = {
|
|
319
328
|
removeDuplicateSlashes,
|
|
320
329
|
patternToRegex,
|
|
@@ -326,6 +335,7 @@ module.exports = {
|
|
|
326
335
|
compileTrust,
|
|
327
336
|
deprecated,
|
|
328
337
|
UP_PATH_REGEXP,
|
|
338
|
+
NullObject,
|
|
329
339
|
decode,
|
|
330
340
|
containsDotFile,
|
|
331
341
|
parseTokenList,
|
|
@@ -336,4 +346,4 @@ module.exports = {
|
|
|
336
346
|
findIndexStartingFrom,
|
|
337
347
|
fastQueryParse,
|
|
338
348
|
canBeOptimized
|
|
339
|
-
};
|
|
349
|
+
};
|
package/src/view.js
CHANGED
|
@@ -16,11 +16,12 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
const path = require("path");
|
|
18
18
|
const fs = require("fs");
|
|
19
|
+
const { NullObject } = require("./utils.js");
|
|
19
20
|
|
|
20
21
|
module.exports = class View {
|
|
21
22
|
constructor(name, options) {
|
|
22
23
|
this.name = name;
|
|
23
|
-
this.options = options ? Object.assign({}, options) :
|
|
24
|
+
this.options = options ? Object.assign({}, options) : new NullObject();
|
|
24
25
|
this.defaultEngine = options.defaultEngine;
|
|
25
26
|
this.ext = path.extname(name);
|
|
26
27
|
this.root = options.root;
|