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 +1 -1
- package/package.json +1 -1
- package/src/declarative.js +12 -5
- package/src/middlewares.js +3 -2
- package/src/request.js +4 -0
- package/src/response.js +53 -28
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/#
|
|
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
package/src/declarative.js
CHANGED
|
@@ -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] =
|
|
197
|
+
sameHeader[1] = value;
|
|
192
198
|
} else {
|
|
193
|
-
headers.push([
|
|
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
|
|
package/src/middlewares.js
CHANGED
|
@@ -86,8 +86,9 @@ function static(root, options) {
|
|
|
86
86
|
|
|
87
87
|
if(stat.isDirectory()) {
|
|
88
88
|
if(!req.endsWithSlash) {
|
|
89
|
-
if(options.redirect)
|
|
90
|
-
|
|
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
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 =
|
|
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']
|
|
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
|
-
|
|
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
|
|
593
|
-
|
|
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
|
-
|
|
614
|
-
|
|
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
|
-
|
|
780
|
-
text
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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("<", "<").replaceAll(">", ">")}</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("<", "<").replaceAll(">", ">")}</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 {
|