ultimate-express 2.0.0 → 2.0.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 CHANGED
@@ -57,7 +57,7 @@ For full table with other runtimes, check [here](https://github.com/dimdenGD/bun
57
57
  | express | 10,411.313 | 11,245.57 | 10,598.74 | 9,389.63 |
58
58
 
59
59
  Other benchmarks:
60
- - [TechEmpower / FrameworkBenchmarks](https://www.techempower.com/benchmarks/#hw=ph&test=composite&section=data-r23&l=zik0sf-cn3)
60
+ - [TechEmpower / FrameworkBenchmarks](https://www.techempower.com/benchmarks/#section=data-r23&test=plaintext&l=zik0sf-pa7)
61
61
  - [the-benchmarker / web-frameworks](https://web-frameworks-benchmark.netlify.app/result?l=javascript)
62
62
 
63
63
  ### Performance on real-world application
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-express",
3
- "version": "2.0.0",
3
+ "version": "2.0.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": {
@@ -187,10 +187,16 @@ module.exports = function compileDeclarative(cb, app) {
187
187
  return false;
188
188
  }
189
189
  const sameHeader = headers.find(header => header[0].toLowerCase() === call.arguments[0].value.toLowerCase());
190
+ let [header, value] = [call.arguments[0].value, call.arguments[1].value];
191
+ if(call.obj.propertyName !== 'setHeader') {
192
+ if(value.includes('text/') && !value.includes('; charset=')) {
193
+ value += '; charset=utf-8';
194
+ }
195
+ }
190
196
  if(sameHeader) {
191
- sameHeader[1] = call.arguments[1].value;
197
+ sameHeader[1] = value;
192
198
  } else {
193
- headers.push([call.arguments[0].value, call.arguments[1].value]);
199
+ headers.push([header, value]);
194
200
  }
195
201
  } else if(call.obj.propertyName === 'append') {
196
202
  if(call.arguments[0].type !== 'Literal' || call.arguments[1].type !== 'Literal') {
@@ -211,6 +217,10 @@ module.exports = function compileDeclarative(cb, app) {
211
217
  const index = headers.findIndex(header => header[0].toLowerCase() === 'content-type');
212
218
  if(index === -1) {
213
219
  headers.push(['content-type', 'text/html; charset=utf-8']);
220
+ } else {
221
+ if(headers[index][1].includes('text/') && !headers[index][1].includes('; charset=')) {
222
+ headers[index][1] += '; charset=utf-8';
223
+ }
214
224
  }
215
225
  }
216
226
  const arg = call.arguments[0];
@@ -355,9 +365,6 @@ module.exports = function compileDeclarative(cb, app) {
355
365
  if(header[0].toLowerCase() === 'content-length') {
356
366
  return false;
357
367
  }
358
- if(header[0].toLowerCase() === 'content-type' && header[1].includes('text/') && !header[1].includes(';')) {
359
- header[1] += '; charset=utf-8';
360
- }
361
368
  decRes = decRes.writeHeader(header[0], header[1]);
362
369
  }
363
370
 
@@ -86,8 +86,9 @@ function static(root, options) {
86
86
 
87
87
  if(stat.isDirectory()) {
88
88
  if(!req.endsWithSlash) {
89
- if(options.redirect) return res.redirect(301, req._originalPath + '/');
90
- else {
89
+ if(options.redirect) {
90
+ return res.redirect(301, req._originalPath + '/', true);
91
+ } else {
91
92
  if(!options.fallthrough) {
92
93
  res.status(404);
93
94
  return next(new Error('Not found'));
package/src/request.js CHANGED
@@ -135,6 +135,10 @@ module.exports = class Request extends Readable {
135
135
  return match ? match[0] : '';
136
136
  }
137
137
 
138
+ set baseUrl(x) {
139
+ return this._originalPath = x;
140
+ }
141
+
138
142
  get #host() {
139
143
  const trust = this.app.get('trust proxy fn');
140
144
  if(!trust) {
package/src/response.js CHANGED
@@ -153,7 +153,7 @@ module.exports = class Response extends Writable {
153
153
  if (this.chunkedTransfer) {
154
154
  this.#pendingChunks.push(chunk);
155
155
  const size = this.#pendingChunks.reduce((acc, chunk) => acc + chunk.byteLength, 0);
156
- const now = Date.now();
156
+ const now = performance.now();
157
157
  // the first chunk is sent immediately (!this.#lastWriteChunkTime)
158
158
  // the other chunks are sent when watermark is reached (size >= HIGH_WATERMARK)
159
159
  // or if elapsed 50ms of last send (now - this.#lastWriteChunkTime > 50)
@@ -173,7 +173,7 @@ module.exports = class Response extends Writable {
173
173
  const size = this.#pendingChunks.reduce((acc, chunk) => acc + chunk.byteLength, 0);
174
174
  this._res.write(Buffer.concat(this.#pendingChunks, size));
175
175
  this.#pendingChunks = [];
176
- this.#lastWriteChunkTime = now;
176
+ this.#lastWriteChunkTime = performance.now();
177
177
  }
178
178
  });
179
179
  }, 50);
@@ -350,7 +350,9 @@ module.exports = class Response extends Writable {
350
350
  this.headers['content-type'] += '; charset=utf-8';
351
351
  }
352
352
  } else {
353
- this.headers['content-type'] = 'application/octet-stream';
353
+ if(!this.headers['content-type']) {
354
+ this.headers['content-type'] = 'application/octet-stream';
355
+ }
354
356
  }
355
357
  return this.end(body);
356
358
  }
@@ -585,23 +587,17 @@ module.exports = class Response extends Writable {
585
587
  this.attachment(name);
586
588
  this.sendFile(path, opts, done);
587
589
  }
588
- set(field, value) {
590
+ setHeader(field, value) {
589
591
  if(this.headersSent) {
590
592
  throw new Error('Cannot set headers after they are sent to the client');
591
593
  }
592
- if(typeof field === 'object') {
593
- for(const header in field) {
594
- this.set(header, field[header]);
595
- }
594
+ if(typeof field !== 'string') {
595
+ throw new TypeError('Header name must be a valid HTTP token');
596
596
  } else {
597
597
  field = field.toLowerCase();
598
598
  if(Array.isArray(value)) {
599
599
  this.headers[field] = value;
600
600
  return this;
601
- } else if(field === 'content-type') {
602
- if(!value.includes('charset=') && (value.startsWith('text/') || value === 'application/json' || value === 'application/javascript')) {
603
- value += '; charset=utf-8';
604
- }
605
601
  }
606
602
  this.headers[field] = String(value);
607
603
  }
@@ -610,8 +606,21 @@ module.exports = class Response extends Writable {
610
606
  header(field, value) {
611
607
  return this.set(field, value);
612
608
  }
613
- setHeader(field, value) {
614
- return this.set(field, value);
609
+ set(field, value) {
610
+ if(typeof field === 'object') {
611
+ for(const header in field) {
612
+ this.setHeader(header, field[header]);
613
+ }
614
+ } else {
615
+ field = field.toLowerCase();
616
+ if(field === 'content-type') {
617
+ if(!value.includes('charset=') && (value.startsWith('text/') || value === 'application/json' || value === 'application/javascript')) {
618
+ value += '; charset=utf-8';
619
+ }
620
+ }
621
+ this.setHeader(field, value);
622
+ }
623
+ return this;
615
624
  }
616
625
  get(field) {
617
626
  return this.headers[field.toLowerCase()];
@@ -766,28 +775,44 @@ module.exports = class Response extends Writable {
766
775
  }
767
776
  return this.headers['location'] = encodeUrl(path);
768
777
  }
769
- redirect(status, url) {
778
+ redirect(status, url, forceHtml = false) {
770
779
  if(typeof status !== 'number' && !url) {
771
780
  url = status;
772
781
  status = 302;
773
782
  }
774
783
  this.location(url);
775
784
  this.status(status);
776
- this.headers['content-type'] = 'text/plain; charset=utf-8';
777
785
  let body;
778
786
  // Support text/{plain,html} by default
779
- this.format({
780
- text: function() {
781
- body = statuses.message[status] + '. Redirecting to ' + url
782
- },
783
- html: function() {
784
- let u = escapeHtml(url);
785
- body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>'
786
- },
787
- default: function() {
788
- body = '';
789
- }
790
- });
787
+ if(forceHtml) {
788
+ this.set('Content-Type', 'text/html; charset=UTF-8');
789
+ body =
790
+ '<!DOCTYPE html>\n' +
791
+ '<html lang="en">\n' +
792
+ '<head>\n' +
793
+ '<meta charset="utf-8">\n' +
794
+ '<title>Redirecting</title>\n' +
795
+ '</head>\n' +
796
+ '<body>\n' +
797
+ `<pre>Redirecting to ${url.replaceAll("<", "&lt;").replaceAll(">", "&gt;")}</pre>\n` +
798
+ '</body>\n' +
799
+ '</html>\n';
800
+ } else {
801
+ this.format({
802
+ text: () => {
803
+ this.set('Content-Type', 'text/plain; charset=UTF-8');
804
+ body = statuses.message[status] + '. Redirecting to ' + url
805
+ },
806
+ html: () => {
807
+ this.set('Content-Type', 'text/html; charset=UTF-8');
808
+ body = `<p>${statuses.message[status]}. Redirecting to ${url.replaceAll("<", "&lt;").replaceAll(">", "&gt;")}</p>`;
809
+ },
810
+ default: () => {
811
+ this.set('Content-Type', 'text/plain; charset=UTF-8');
812
+ body = '';
813
+ }
814
+ });
815
+ }
791
816
  if (this.req.method === 'HEAD') {
792
817
  this.end();
793
818
  } else {