ultimate-express 1.3.18 → 1.4.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 +18 -0
- package/package.json +1 -1
- package/src/application.js +7 -3
- package/src/request.js +9 -8
- package/src/response.js +10 -3
- package/src/router.js +5 -1
package/README.md
CHANGED
|
@@ -53,6 +53,10 @@ For full table with other runtimes, check [here](https://github.com/dimdenGD/bun
|
|
|
53
53
|
| koa | 24,045.08 | 28,202.12 | 24,590.84 | 19,342.28 |
|
|
54
54
|
| express | 10,411.313 | 11,245.57 | 10,598.74 | 9,389.63 |
|
|
55
55
|
|
|
56
|
+
Other benchmarks:
|
|
57
|
+
- [TechEmpower / FrameworkBenchmarks](https://www.techempower.com/benchmarks/#hw=ph&test=composite§ion=data-r23&l=zik0sf-cn3)
|
|
58
|
+
- [the-benchmarker / web-frameworks](https://web-frameworks-benchmark.netlify.app/result?l=javascript)
|
|
59
|
+
|
|
56
60
|
### Performance on real-world application
|
|
57
61
|
|
|
58
62
|
Also tested on a [real-world application](https://nekoweb.org) with templates, static files and dynamic pages with data from database, and showed 1.5-4X speedup in requests per second depending on the page.
|
|
@@ -122,6 +126,20 @@ Since you don't create http server manually, you can't properly use http.on("upg
|
|
|
122
126
|
- There's a sister library that implements `ws` compatible API: [Ultimate WS](https://github.com/dimdenGD/ultimate-ws). It's same concept as this library, but for WebSockets: fast drop-in replacement for `ws` module with support for Ultimate Express upgrades. There's a guide for how to upgrade http requests in the documentation.
|
|
123
127
|
- You can simply use `app.uwsApp` to access uWebSockets.js `App` instance and call its `ws()` method directly.
|
|
124
128
|
|
|
129
|
+
## HTTP/3
|
|
130
|
+
|
|
131
|
+
HTTP/3 is supported. To use:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const app = express({
|
|
135
|
+
http3: true,
|
|
136
|
+
uwsOptions: {
|
|
137
|
+
key_file_name: '/path/to/example.key',
|
|
138
|
+
cert_file_name: '/path/to/example.crt'
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
125
143
|
## Compatibility
|
|
126
144
|
|
|
127
145
|
In general, basically all features and options are supported. Use [Express 4.x documentation](https://expressjs.com/en/4x/api.html) for API reference.
|
package/package.json
CHANGED
package/src/application.js
CHANGED
|
@@ -55,13 +55,17 @@ class Application extends Router {
|
|
|
55
55
|
if(typeof settings.threads !== 'number') {
|
|
56
56
|
settings.threads = cpuCount > 1 ? 1 : 0;
|
|
57
57
|
}
|
|
58
|
-
if(settings.
|
|
58
|
+
if(settings.http3) {
|
|
59
|
+
if(!settings.uwsOptions.key_file_name || !settings.uwsOptions.cert_file_name) {
|
|
60
|
+
throw new Error('uwsOptions.key_file_name and uwsOptions.cert_file_name are required for HTTP/3');
|
|
61
|
+
}
|
|
62
|
+
this.uwsApp = uWS.H3App(settings.uwsOptions);
|
|
63
|
+
} else if(settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
|
|
59
64
|
this.uwsApp = uWS.SSLApp(settings.uwsOptions);
|
|
60
|
-
this.ssl = true;
|
|
61
65
|
} else {
|
|
62
66
|
this.uwsApp = uWS.App(settings.uwsOptions);
|
|
63
|
-
this.ssl = false;
|
|
64
67
|
}
|
|
68
|
+
this.ssl = settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name;
|
|
65
69
|
this.cache = new NullObject();
|
|
66
70
|
this.engines = {};
|
|
67
71
|
this.locals = {
|
package/src/request.js
CHANGED
|
@@ -72,6 +72,7 @@ module.exports = class Request extends Readable {
|
|
|
72
72
|
this._stack = [];
|
|
73
73
|
this._paramStack = [];
|
|
74
74
|
this.receivedData = false;
|
|
75
|
+
this.doneReadingData = false;
|
|
75
76
|
// reading ip is very slow in UWS, so its better to not do it unless truly needed
|
|
76
77
|
if(this.app.needsIpAfterResponse || this.key < 100) {
|
|
77
78
|
// if app needs ip after response, read it now because after response its not accessible
|
|
@@ -92,14 +93,14 @@ module.exports = class Request extends Readable {
|
|
|
92
93
|
this._res.onData((ab, isLast) => {
|
|
93
94
|
// make stream actually readable
|
|
94
95
|
this.receivedData = true;
|
|
96
|
+
if(isLast) {
|
|
97
|
+
this.doneReadingData = true;
|
|
98
|
+
}
|
|
95
99
|
// instead of pushing data immediately, buffer it
|
|
96
100
|
// because writable streams cant handle the amount of data uWS gives (usually 512kb+)
|
|
97
101
|
const chunk = Buffer.from(ab);
|
|
98
102
|
this.bufferedData = Buffer.concat([this.bufferedData, chunk]);
|
|
99
|
-
|
|
100
|
-
// once its done start pushing data
|
|
101
|
-
this._read();
|
|
102
|
-
}
|
|
103
|
+
this._read();
|
|
103
104
|
});
|
|
104
105
|
} else {
|
|
105
106
|
this.receivedData = true;
|
|
@@ -111,11 +112,11 @@ module.exports = class Request extends Readable {
|
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
if(this.bufferedData.length > 0) {
|
|
114
|
-
// push
|
|
115
|
-
const chunk = this.bufferedData.subarray(0, 1024 *
|
|
116
|
-
this.bufferedData = this.bufferedData.subarray(1024 *
|
|
115
|
+
// push 128kb chunks
|
|
116
|
+
const chunk = this.bufferedData.subarray(0, 1024 * 128);
|
|
117
|
+
this.bufferedData = this.bufferedData.subarray(1024 * 128);
|
|
117
118
|
this.push(chunk);
|
|
118
|
-
} else {
|
|
119
|
+
} else if(this.doneReadingData) {
|
|
119
120
|
this.push(null);
|
|
120
121
|
}
|
|
121
122
|
}
|
package/src/response.js
CHANGED
|
@@ -78,6 +78,7 @@ module.exports = class Response extends Writable {
|
|
|
78
78
|
this.finished = false;
|
|
79
79
|
this.aborted = false;
|
|
80
80
|
this.statusCode = 200;
|
|
81
|
+
this.statusText = undefined;
|
|
81
82
|
this.chunkedTransfer = true;
|
|
82
83
|
this.totalSize = 0;
|
|
83
84
|
this.headers = {
|
|
@@ -132,7 +133,8 @@ module.exports = class Response extends Writable {
|
|
|
132
133
|
this._res.cork(() => {
|
|
133
134
|
if(!this.headersSent) {
|
|
134
135
|
this.writeHead(this.statusCode);
|
|
135
|
-
this.
|
|
136
|
+
const statusMessage = this.statusText ?? statuses.message[this.statusCode] ?? '';
|
|
137
|
+
this._res.writeStatus(`${this.statusCode} ${statusMessage}`.trim());
|
|
136
138
|
this.writeHeaders(typeof chunk === 'string');
|
|
137
139
|
}
|
|
138
140
|
if(!Buffer.isBuffer(chunk) && !(chunk instanceof ArrayBuffer)) {
|
|
@@ -181,6 +183,9 @@ module.exports = class Response extends Writable {
|
|
|
181
183
|
}
|
|
182
184
|
writeHead(statusCode, statusMessage, headers) {
|
|
183
185
|
this.statusCode = statusCode;
|
|
186
|
+
if(typeof statusMessage === 'string') {
|
|
187
|
+
this.statusText = statusMessage;
|
|
188
|
+
}
|
|
184
189
|
if(!headers) {
|
|
185
190
|
if(!statusMessage) return;
|
|
186
191
|
headers = statusMessage;
|
|
@@ -235,7 +240,8 @@ module.exports = class Response extends Writable {
|
|
|
235
240
|
this.headers['etag'] = etagFn(data);
|
|
236
241
|
}
|
|
237
242
|
const fresh = this.req.fresh;
|
|
238
|
-
this.
|
|
243
|
+
const statusMessage = this.statusText ?? statuses.message[this.statusCode] ?? '';
|
|
244
|
+
this._res.writeStatus(fresh ? '304 Not Modified' : `${this.statusCode} ${statusMessage}`.trim());
|
|
239
245
|
this.writeHeaders(true);
|
|
240
246
|
if(fresh) {
|
|
241
247
|
this._res.end();
|
|
@@ -742,7 +748,8 @@ function pipeStreamOverResponse(res, readStream, totalSize, callback) {
|
|
|
742
748
|
res._res.cork(() => {
|
|
743
749
|
if(!res.headersSent) {
|
|
744
750
|
res.writeHead(res.statusCode);
|
|
745
|
-
res.
|
|
751
|
+
const statusMessage = res.statusText ?? statuses.message[res.statusCode] ?? '';
|
|
752
|
+
res._res.writeStatus(`${res.statusCode} ${statusMessage}`.trim());
|
|
746
753
|
res.writeHeaders(true);
|
|
747
754
|
}
|
|
748
755
|
const ab = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
package/src/router.js
CHANGED
|
@@ -19,6 +19,7 @@ const Response = require("./response.js");
|
|
|
19
19
|
const Request = require("./request.js");
|
|
20
20
|
const { EventEmitter } = require("tseep");
|
|
21
21
|
const compileDeclarative = require("./declarative.js");
|
|
22
|
+
const statuses = require("statuses");
|
|
22
23
|
|
|
23
24
|
let resCodes = {}, resDecMethods = ['set', 'setHeader', 'header', 'send', 'end', 'append', 'status'];
|
|
24
25
|
for(let method of resDecMethods) {
|
|
@@ -355,6 +356,9 @@ module.exports = class Router extends EventEmitter {
|
|
|
355
356
|
}
|
|
356
357
|
|
|
357
358
|
_extractParams(pattern, path) {
|
|
359
|
+
if(path.endsWith('/')) {
|
|
360
|
+
path = path.slice(0, -1);
|
|
361
|
+
}
|
|
358
362
|
let match = pattern.exec(path);
|
|
359
363
|
const obj = match?.groups ?? new NullObject();
|
|
360
364
|
for(let i = 1; i < match.length; i++) {
|
|
@@ -602,7 +606,7 @@ module.exports = class Router extends EventEmitter {
|
|
|
602
606
|
|
|
603
607
|
_sendErrorPage(request, response, err, checkEnv = false) {
|
|
604
608
|
if(checkEnv && this.get('env') === 'production') {
|
|
605
|
-
err = 'Internal Server Error';
|
|
609
|
+
err = response.statusCode >= 400 ? (statuses.message[response.statusCode] ?? 'Internal Server Error') : 'Internal Server Error';
|
|
606
610
|
}
|
|
607
611
|
request.noEtag = true;
|
|
608
612
|
response.setHeader('Content-Type', 'text/html; charset=utf-8');
|