ultimate-express 1.3.8 → 1.3.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-express",
3
- "version": "1.3.8",
3
+ "version": "1.3.10",
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": {
@@ -63,6 +63,7 @@
63
63
  "vary": "^1.1.2"
64
64
  },
65
65
  "devDependencies": {
66
+ "@codechecks/client": "^0.1.12",
66
67
  "body-parser": "^1.20.3",
67
68
  "compression": "^1.7.4",
68
69
  "cookie-parser": "^1.4.6",
@@ -63,7 +63,7 @@ class Application extends Router {
63
63
  this.ssl = false;
64
64
  }
65
65
  this.cache = new NullObject();
66
- this.engines = new NullObject();
66
+ this.engines = {};
67
67
  this.locals = {
68
68
  settings: this.settings
69
69
  };
@@ -292,7 +292,7 @@ class Application extends Router {
292
292
  view = new View(name, {
293
293
  defaultEngine: this.get('view engine'),
294
294
  root: this.get('views'),
295
- engines: this.engines
295
+ engines: {...this.engines}
296
296
  });
297
297
  if(!view.path) {
298
298
  const dirs = Array.isArray(view.root) && view.root.length > 1
@@ -6,6 +6,11 @@ const parser = acorn.Parser;
6
6
 
7
7
  const allowedResMethods = ['set', 'header', 'setHeader', 'status', 'send', 'end', 'append'];
8
8
  const allowedIdentifiers = ['query', 'params', ...allowedResMethods];
9
+ const objKeyRegex = /[\s{\n]([A-Za-z-0-9_]+)(\s|\n)*?:/g;
10
+
11
+ function replaceSingleCharacter(str, index, char) {
12
+ return str.slice(0, index) + char + str.slice(index + 1);
13
+ }
9
14
 
10
15
  // generates a declarative response from a callback
11
16
  // uWS allows creating such responses and they are extremely fast
@@ -274,11 +279,19 @@ module.exports = function compileDeclarative(cb, app) {
274
279
  }
275
280
  body.push(...stuff.reverse());
276
281
  } else if(arg.type === 'ObjectExpression') {
282
+ if(call.obj.propertyName === 'end') {
283
+ return false;
284
+ }
277
285
  // only simple objects can be optimized
286
+ let objCode = code;
278
287
  for(let property of arg.properties) {
279
288
  if(property.key.type !== 'Identifier' && property.key.type !== 'Literal') {
280
289
  return false;
281
290
  }
291
+ if(property.value.raw.startsWith("'") && property.value.raw.endsWith("'") && !property.value.value.includes("'")) {
292
+ objCode = replaceSingleCharacter(objCode, property.value.start, '"');
293
+ objCode = replaceSingleCharacter(objCode, property.value.end - 1, '"');
294
+ }
282
295
  if(property.value.type !== 'Literal') {
283
296
  return false;
284
297
  }
@@ -286,7 +299,18 @@ module.exports = function compileDeclarative(cb, app) {
286
299
  if(typeof app.get('json replacer') !== 'undefined' && typeof app.get('json replacer') !== 'string') {
287
300
  return false;
288
301
  }
289
- body.push({type: 'text', value: stringify(JSON.parse(code.slice(arg.start, arg.end)), app.get('json replacer'), app.get('json spaces'), app.get('json escape'))});
302
+
303
+ headers.push(['content-type', 'application/json; charset=utf-8']);
304
+ body.push({
305
+ type: 'text',
306
+ value:
307
+ stringify(
308
+ JSON.parse(objCode.slice(arg.start, arg.end).replace(objKeyRegex, '"$1":')),
309
+ app.get('json replacer'),
310
+ app.get('json spaces'),
311
+ app.get('json escape')
312
+ )
313
+ });
290
314
  } else {
291
315
  return false;
292
316
  }
package/src/request.js CHANGED
@@ -45,6 +45,7 @@ module.exports = class Request extends Readable {
45
45
  this._req.forEach((key, value) => {
46
46
  this.#rawHeadersEntries.push([key, value]);
47
47
  });
48
+ this.routeCount = 0;
48
49
  this.key = key++;
49
50
  if(key > 100000) {
50
51
  key = 0;
@@ -65,7 +66,7 @@ module.exports = class Request extends Readable {
65
66
  this._opPath = this._opPath.slice(0, -1);
66
67
  }
67
68
  this.method = req.getCaseSensitiveMethod().toUpperCase();
68
- this.params = new NullObject();
69
+ this.params = {};
69
70
 
70
71
  this._gotParams = new Set();
71
72
  this._stack = [];
@@ -209,7 +210,7 @@ module.exports = class Request extends Readable {
209
210
  } else {
210
211
  this.#cachedQuery = new NullObject();
211
212
  }
212
- return this.#cachedQuery;
213
+ return {...this.#cachedQuery};
213
214
  }
214
215
 
215
216
  get secure() {
@@ -307,23 +308,19 @@ module.exports = class Request extends Readable {
307
308
  }
308
309
 
309
310
  accepts(...types) {
310
- const accept = accepts({ headers: this.headers });
311
- return accept.types(...types);
311
+ return accepts(this).types(...types);
312
312
  }
313
313
 
314
314
  acceptsCharsets(...charsets) {
315
- const accept = accepts({ headers: this.headers });
316
- return accept.charsets(...charsets);
315
+ return accepts(this).charsets(...charsets);
317
316
  }
318
317
 
319
318
  acceptsEncodings(...encodings) {
320
- const accept = accepts({ headers: this.headers });
321
- return accept.encodings(...encodings);
319
+ return accepts(this).encodings(...encodings);
322
320
  }
323
321
 
324
322
  acceptsLanguages(...languages) {
325
- const accept = accepts({ headers: this.headers });
326
- return accept.languages(...languages);
323
+ return accepts(this).languages(...languages);
327
324
  }
328
325
 
329
326
  is(type) {
@@ -350,7 +347,7 @@ module.exports = class Request extends Readable {
350
347
  get headers() {
351
348
  // https://nodejs.org/api/http.html#messageheaders
352
349
  if(this.#cachedHeaders) {
353
- return this.#cachedHeaders;
350
+ return {...this.#cachedHeaders};
354
351
  }
355
352
  this.#cachedHeaders = new NullObject();
356
353
  for (let index = 0, len = this.#rawHeadersEntries.length; index < len; index++) {
@@ -375,7 +372,7 @@ module.exports = class Request extends Readable {
375
372
  this.#cachedHeaders[key] = value;
376
373
  }
377
374
  }
378
- return this.#cachedHeaders;
375
+ return {...this.#cachedHeaders};
379
376
  }
380
377
 
381
378
  get headersDistinct() {
@@ -391,7 +388,7 @@ module.exports = class Request extends Readable {
391
388
  }
392
389
  this.#cachedDistinctHeaders[key].push(value);
393
390
  });
394
- return this.#cachedDistinctHeaders;
391
+ return {...this.#cachedDistinctHeaders};
395
392
  }
396
393
 
397
394
  get rawHeaders() {
@@ -402,4 +399,4 @@ module.exports = class Request extends Readable {
402
399
  }
403
400
  return res;
404
401
  }
405
- }
402
+ }
package/src/response.js CHANGED
@@ -83,6 +83,7 @@ module.exports = class Response extends Writable {
83
83
  if(this.app.get('x-powered-by')) {
84
84
  this.headers['x-powered-by'] = 'UltimateExpress';
85
85
  }
86
+
86
87
  // support for node internal
87
88
  this[kOutHeaders] = new Proxy(this.headers, {
88
89
  set: (obj, prop, value) => {
@@ -256,8 +257,10 @@ module.exports = class Response extends Writable {
256
257
  if(this.socketExists) this.socket.emit('close');
257
258
  });
258
259
 
260
+ this.emit('finish')
259
261
  return this;
260
262
  }
263
+
261
264
  send(body) {
262
265
  if(this.headersSent) {
263
266
  throw new Error('Can\'t write body: Response was already sent');
@@ -288,6 +291,7 @@ module.exports = class Response extends Writable {
288
291
  }
289
292
  return this.end(body);
290
293
  }
294
+
291
295
  sendFile(path, options = new NullObject(), callback) {
292
296
  if(typeof path !== 'string') {
293
297
  throw new TypeError('path argument is required to res.sendFile');
@@ -496,10 +500,10 @@ module.exports = class Response extends Writable {
496
500
  if (typeof filename === 'function') {
497
501
  done = filename;
498
502
  name = null;
499
- opts = new NullObject();
503
+ opts = {};
500
504
  } else if (typeof options === 'function') {
501
505
  done = options;
502
- opts = new NullObject();
506
+ opts = {};
503
507
  }
504
508
 
505
509
  // support optional filename, where options may be in it's place
@@ -577,10 +581,10 @@ module.exports = class Response extends Writable {
577
581
  render(view, options, callback) {
578
582
  if(typeof options === 'function') {
579
583
  callback = options;
580
- options = new NullObject();
584
+ options = {};
581
585
  }
582
586
  if(!options) {
583
- options = new NullObject();
587
+ options = {};
584
588
  } else {
585
589
  options = Object.assign({}, options);
586
590
  }
@@ -594,7 +598,7 @@ module.exports = class Response extends Writable {
594
598
  }
595
599
  cookie(name, value, options) {
596
600
  if(!options) {
597
- options = new NullObject();
601
+ options = {};
598
602
  }
599
603
  let val = typeof value === 'object' ? "j:"+JSON.stringify(value) : String(value);
600
604
  if(options.maxAge != null) {
package/src/router.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, needsConversionToRegex, deprecated, findIndexStartingFrom, canBeOptimized, NullObject } = require("./utils.js");
17
+ const { patternToRegex, needsConversionToRegex, deprecated, findIndexStartingFrom, canBeOptimized, NullObject, EMPTY_REGEX } = require("./utils.js");
18
18
  const Response = require("./response.js");
19
19
  const Request = require("./request.js");
20
20
  const { EventEmitter } = require("tseep");
@@ -93,6 +93,9 @@ module.exports = class Router extends EventEmitter {
93
93
 
94
94
  getFullMountpath(req) {
95
95
  let fullStack = req._stack.join("");
96
+ if(!fullStack){
97
+ return EMPTY_REGEX;
98
+ }
96
99
  let fullMountpath = this._mountpathCache.get(fullStack);
97
100
  if(!fullMountpath) {
98
101
  fullMountpath = patternToRegex(fullStack, true);
@@ -118,7 +121,9 @@ module.exports = class Router extends EventEmitter {
118
121
  }
119
122
  return pattern === path;
120
123
  }
121
-
124
+ if (pattern === EMPTY_REGEX){
125
+ return true;
126
+ }
122
127
  return pattern.test(path);
123
128
  }
124
129
 
@@ -355,60 +360,60 @@ module.exports = class Router extends EventEmitter {
355
360
  }
356
361
 
357
362
  _preprocessRequest(req, res, route) {
358
- req.route = route;
359
- if(route.optimizedParams) {
360
- req.params = req.optimizedParams;
361
- } else if(typeof route.path === 'string' && (route.path.includes(':') || route.path.includes('*')) && route.pattern instanceof RegExp) {
362
- let path = req._originalPath;
363
- if(req._stack.length > 0) {
364
- path = path.replace(this.getFullMountpath(req), '');
365
- }
366
- req.params = this._extractParams(route.pattern, path);
367
- if(req._paramStack.length > 0) {
368
- for(let params of req._paramStack) {
369
- req.params = {...params, ...req.params};
370
- }
363
+ req.route = route;
364
+ if(route.optimizedParams) {
365
+ req.params = {...req.optimizedParams};
366
+ } else if(typeof route.path === 'string' && (route.path.includes(':') || route.path.includes('*')) && route.pattern instanceof RegExp) {
367
+ let path = req._originalPath;
368
+ if(req._stack.length > 0) {
369
+ path = path.replace(this.getFullMountpath(req), '');
370
+ }
371
+ req.params = {...this._extractParams(route.pattern, path)};
372
+ if(req._paramStack.length > 0) {
373
+ for(let params of req._paramStack) {
374
+ req.params = {...params, ...req.params};
371
375
  }
372
- } else {
373
- req.params = new NullObject();
374
- if(req._paramStack.length > 0) {
375
- for(let params of req._paramStack) {
376
- req.params = {...params, ...req.params};
377
- }
376
+ }
377
+ } else {
378
+ req.params = {};
379
+ if(req._paramStack.length > 0) {
380
+ for(let params of req._paramStack) {
381
+ req.params = {...params, ...req.params};
378
382
  }
379
383
  }
384
+ }
380
385
 
381
- if(this._paramCallbacks.size > 0) {
382
- return new Promise(async resolve => {
383
- for(let param in req.params) {
384
- if(this._paramCallbacks.has(param) && !req._gotParams.has(param)) {
385
- req._gotParams.add(param);
386
- const pcs = this._paramCallbacks.get(param);
387
- for(let i = 0; i < pcs.length; i++) {
388
- const fn = pcs[i];
389
- await new Promise(resolveRoute => {
390
- const next = (thingamabob) => {
391
- if(thingamabob) {
392
- if(thingamabob === 'route') {
393
- return resolve('route');
394
- } else {
395
- this._handleError(thingamabob, req, res);
396
- return resolve(false);
397
- }
386
+ if(this._paramCallbacks.size > 0) {
387
+ return new Promise(async resolve => {
388
+ for(let param in req.params) {
389
+ const pcs = this._paramCallbacks.get(param);
390
+ if(pcs && !req._gotParams.has(param)) {
391
+ req._gotParams.add(param);
392
+ for(let i = 0, len = pcs.length; i < len; i++) {
393
+ const fn = pcs[i];
394
+ await new Promise(resolveRoute => {
395
+ const next = (thingamabob) => {
396
+ if(thingamabob) {
397
+ if(thingamabob === 'route') {
398
+ return resolve('route');
399
+ } else {
400
+ this._handleError(thingamabob, req, res);
401
+ return resolve(false);
398
402
  }
399
- return resolveRoute();
400
- };
401
- req.next = next;
402
- fn(req, res, next, req.params[param], param);
403
- });
404
- }
403
+ }
404
+ return resolveRoute();
405
+ };
406
+ req.next = next;
407
+ fn(req, res, next, req.params[param], param);
408
+ });
405
409
  }
406
410
  }
411
+ }
407
412
 
408
- resolve(true)
409
- });
410
- }
411
- return true;
413
+ resolve(true)
414
+ });
415
+ }
416
+ return true;
412
417
  }
413
418
 
414
419
  param(name, fn) {
@@ -445,11 +450,18 @@ module.exports = class Router extends EventEmitter {
445
450
  return this._routeRequest(req, res, 0, this._routes, false, skipUntil);
446
451
  }
447
452
  let callbackindex = 0;
448
- const continueRoute = await this._preprocessRequest(req, res, route);
453
+
454
+ // avoid calling _preprocessRequest as async function as its slower
455
+ // but it seems like calling it as async has unintended consequence of resetting max call stack size
456
+ // so call it as async when the request has been through every 300 routes to reset it
457
+ const continueRoute = this._paramCallbacks.size === 0 && req.routeCount % 300 !== 0 ?
458
+ this._preprocessRequest(req, res, route) : await this._preprocessRequest(req, res, route);
459
+
460
+ const strictRouting = this.get('strict routing');
449
461
  if(route.use) {
450
- const strictRouting = this.get('strict routing');
451
462
  req._stack.push(route.path);
452
- req._opPath = req._originalPath.replace(this.getFullMountpath(req), '');
463
+ const fullMountpath = this.getFullMountpath(req);
464
+ req._opPath = fullMountpath !== EMPTY_REGEX ? req._originalPath.replace(fullMountpath, '') : req._originalPath;
453
465
  if(req.endsWithSlash && req._opPath[req._opPath.length - 1] !== '/') {
454
466
  if(strictRouting) {
455
467
  req._opPath += '/';
@@ -470,7 +482,7 @@ module.exports = class Router extends EventEmitter {
470
482
  if(thingamabob === 'route' || thingamabob === 'skipPop') {
471
483
  if(route.use && thingamabob !== 'skipPop') {
472
484
  req._stack.pop();
473
- const strictRouting = this.get('strict routing');
485
+
474
486
  req._opPath = req._stack.length > 0 ? req._originalPath.replace(this.getFullMountpath(req), '') : req._originalPath;
475
487
  if(strictRouting) {
476
488
  if(req.endsWithSlash && req._opPath[req._opPath.length - 1] !== '/') {
@@ -490,6 +502,7 @@ module.exports = class Router extends EventEmitter {
490
502
  req.app = req.app.parent;
491
503
  }
492
504
  }
505
+ req.routeCount++;
493
506
  return resolve(this._routeRequest(req, res, routeIndex + 1, routes, skipCheck, skipUntil));
494
507
  } else {
495
508
  this._handleError(thingamabob, req, res);
package/src/utils.js CHANGED
@@ -22,6 +22,8 @@ const querystring = require("fast-querystring");
22
22
  const etag = require("etag");
23
23
  const { Stats } = require("fs");
24
24
 
25
+ const EMPTY_REGEX = new RegExp(``);
26
+
25
27
  function fastQueryParse(query, options) {
26
28
  const len = query.length;
27
29
  if(len === 0){
@@ -29,10 +31,12 @@ function fastQueryParse(query, options) {
29
31
  }
30
32
  if(len <= 128) {
31
33
  if(!query.includes('[') && !query.includes('%5B') && !query.includes('.') && !query.includes('%2E')) {
32
- return querystring.parse(query);
34
+ // [Object: null prototype] issue
35
+ return {...querystring.parse(query)};
33
36
  }
34
37
  }
35
- return qs.parse(query, options);
38
+ // [Object: null prototype] issue
39
+ return {...qs.parse(query, options)};
36
40
  }
37
41
 
38
42
  function removeDuplicateSlashes(path) {
@@ -44,7 +48,7 @@ function patternToRegex(pattern, isPrefix = false) {
44
48
  return pattern;
45
49
  }
46
50
  if(isPrefix && pattern === '') {
47
- return new RegExp(``);
51
+ return EMPTY_REGEX;
48
52
  }
49
53
 
50
54
  let regexPattern = pattern
@@ -102,17 +106,33 @@ function canBeOptimized(pattern) {
102
106
  }
103
107
 
104
108
  function acceptParams(str) {
105
- const parts = str.split(/ *; */);
106
- const ret = { value: parts[0], quality: 1, params: {} }
107
-
108
- for (let i = 1, len = parts.length; i < len; ++i) {
109
- const pms = parts[i].split(/ *= */);
110
- const [pms_0, pms_1] = pms;
111
- if ('q' === pms_0) {
112
- ret.quality = parseFloat(pms_1);
113
- } else {
114
- ret.params[pms_0] = pms_1;
115
- }
109
+ const length = str.length;
110
+ const colonIndex = str.indexOf(';');
111
+ const index = colonIndex === -1 ? length : colonIndex;
112
+ const ret = { value: str.slice(0, index).trim(), quality: 1, params: {} };
113
+
114
+ while (index < length) {
115
+ const splitIndex = str.indexOf('=', index);
116
+ if (splitIndex === -1) break;
117
+
118
+ const colonIndex = str.indexOf(';', index);
119
+ const endIndex = colonIndex === -1 ? length : colonIndex;
120
+
121
+ if (splitIndex > endIndex) {
122
+ index = str.lastIndexOf(';', splitIndex - 1) + 1;
123
+ continue;
124
+ }
125
+
126
+ const key = str.slice(index, splitIndex).trim();
127
+ const value = str.slice(splitIndex + 1, endIndex).trim();
128
+
129
+ if (key === 'q') {
130
+ ret.quality = parseFloat(value);
131
+ } else {
132
+ ret.params[key] = value;
133
+ }
134
+
135
+ index = endIndex + 1;
116
136
  }
117
137
 
118
138
  return ret;
@@ -205,7 +225,7 @@ function deprecated(oldMethod, newMethod, full = false) {
205
225
  }
206
226
 
207
227
  function findIndexStartingFrom(arr, fn, index = 0) {
208
- for(let i = index; i < arr.length; i++) {
228
+ for(let i = index, end = arr.length; i < end; i++) {
209
229
  if(fn(arr[i], i, arr)) {
210
230
  return i;
211
231
  }
@@ -346,5 +366,6 @@ module.exports = {
346
366
  isRangeFresh,
347
367
  findIndexStartingFrom,
348
368
  fastQueryParse,
349
- canBeOptimized
369
+ canBeOptimized,
370
+ EMPTY_REGEX
350
371
  };