ultimate-express 1.3.10 → 1.3.11

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.
@@ -1,322 +1,322 @@
1
- /*
2
- Copyright 2024 dimden.dev
3
-
4
- Licensed under the Apache License, Version 2.0 (the "License");
5
- you may not use this file except in compliance with the License.
6
- You may obtain a copy of the License at
7
-
8
- http://www.apache.org/licenses/LICENSE-2.0
9
-
10
- Unless required by applicable law or agreed to in writing, software
11
- distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- See the License for the specific language governing permissions and
14
- limitations under the License.
15
- */
16
-
17
- const uWS = require("uWebSockets.js");
18
- const Router = require("./router.js");
19
- const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator, fastQueryParse, NullObject } = require("./utils.js");
20
- const querystring = require("fast-querystring");
21
- const ViewClass = require("./view.js");
22
- const path = require("path");
23
- const os = require("os");
24
- const { Worker } = require("worker_threads");
25
-
26
- const cpuCount = os.cpus().length;
27
-
28
- let workers = [];
29
- let taskKey = 0;
30
- const workerTasks = new NullObject();
31
-
32
- function createWorker() {
33
- const worker = new Worker(path.join(__dirname, 'worker.js'));
34
- workers.push(worker);
35
-
36
- worker.on('message', (message) => {
37
- if(message.err) {
38
- workerTasks[message.key].reject(new Error(message.err));
39
- } else {
40
- workerTasks[message.key].resolve(message.data);
41
- }
42
- delete workerTasks[message.key];
43
- });
44
- worker.unref();
45
-
46
- return worker;
47
- }
48
-
49
- class Application extends Router {
50
- constructor(settings = new NullObject()) {
51
- super(settings);
52
- if(!settings?.uwsOptions) {
53
- settings.uwsOptions = {};
54
- }
55
- if(typeof settings.threads !== 'number') {
56
- settings.threads = cpuCount > 1 ? 1 : 0;
57
- }
58
- if(settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
59
- this.uwsApp = uWS.SSLApp(settings.uwsOptions);
60
- this.ssl = true;
61
- } else {
62
- this.uwsApp = uWS.App(settings.uwsOptions);
63
- this.ssl = false;
64
- }
65
- this.cache = new NullObject();
66
- this.engines = {};
67
- this.locals = {
68
- settings: this.settings
69
- };
70
- this.listenCalled = false;
71
- this.workers = [];
72
- for(let i = 0; i < settings.threads; i++) {
73
- if(workers[i]) {
74
- this.workers[i] = workers[i];
75
- } else {
76
- this.workers[i] = createWorker();
77
- }
78
- }
79
- this.port = undefined;
80
- for(const key in defaultSettings) {
81
- if(typeof this.settings[key] === 'undefined') {
82
- if(typeof defaultSettings[key] === 'function') {
83
- this.settings[key] = defaultSettings[key](this);
84
- } else {
85
- this.settings[key] = defaultSettings[key];
86
- }
87
- }
88
- }
89
- this.set('view', ViewClass);
90
- this.set('views', path.resolve('views'));
91
- }
92
-
93
- createWorkerTask(resolve, reject) {
94
- const key = taskKey++;
95
- workerTasks[key] = { resolve, reject };
96
- if(key > 1000000) {
97
- taskKey = 0;
98
- }
99
- return key;
100
- }
101
-
102
- readFileWithWorker(path) {
103
- return new Promise((resolve, reject) => {
104
- const worker = this.workers[Math.floor(Math.random() * this.workers.length)];
105
- const key = this.createWorkerTask(resolve, reject);
106
- worker.postMessage({ key, type: 'readFile', path });
107
- });
108
- }
109
-
110
- set(key, value) {
111
- if(key === 'trust proxy') {
112
- if(!value) {
113
- delete this.settings['trust proxy fn'];
114
- } else {
115
- this.settings['trust proxy fn'] = compileTrust(value);
116
- }
117
- } else if(key === 'query parser') {
118
- if(value === 'extended') {
119
- this.settings['query parser fn'] = fastQueryParse;
120
- } else if(value === 'simple') {
121
- this.settings['query parser fn'] = querystring.parse;
122
- } else if(typeof value === 'function') {
123
- this.settings['query parser fn'] = value;
124
- } else {
125
- this.settings['query parser fn'] = undefined;
126
- }
127
- } else if(key === 'env') {
128
- if(value === 'production') {
129
- this.settings['view cache'] = true;
130
- } else {
131
- this.settings['view cache'] = undefined;
132
- }
133
- } else if(key === 'views') {
134
- this.settings[key] = path.resolve(value);
135
- return this;
136
- } else if(key === 'etag') {
137
- if(typeof value === 'function') {
138
- this.settings['etag fn'] = value;
139
- } else {
140
- switch(value) {
141
- case true:
142
- case 'weak':
143
- this.settings['etag fn'] = createETagGenerator({ weak: true });
144
- break;
145
- case 'strong':
146
- this.settings['etag fn'] = createETagGenerator({ weak: false });
147
- break;
148
- case false:
149
- delete this.settings['etag fn'];
150
- break;
151
- default:
152
- throw new Error(`Invalid etag mode: ${value}`);
153
- }
154
- }
155
- }
156
-
157
- this.settings[key] = value;
158
- return this;
159
- }
160
-
161
- enable(key) {
162
- this.set(key, true);
163
- return this;
164
- }
165
-
166
- disable(key) {
167
- this.set(key, false);
168
- return this;
169
- }
170
-
171
- enabled(key) {
172
- return !!this.settings[key];
173
- }
174
-
175
- disabled(key) {
176
- return !this.settings[key];
177
- }
178
-
179
- #createRequestHandler() {
180
- this.uwsApp.any('/*', async (res, req) => {
181
- const { request, response } = this.handleRequest(res, req);
182
-
183
- const matchedRoute = await this._routeRequest(request, response);
184
- if(!matchedRoute && !response.headersSent && !response.aborted) {
185
- response.status(404);
186
- this._sendErrorPage(request, response, `Cannot ${request.method} ${request.path}`, false);
187
- }
188
- });
189
- }
190
-
191
- listen(port, host, callback) {
192
- this.#createRequestHandler();
193
- // support listen(callback)
194
- if(!callback && typeof port === 'function') {
195
- callback = port;
196
- port = 0;
197
- }
198
- // support listen(port, callback)
199
- if(typeof host === 'function') {
200
- callback = host;
201
- host = undefined;
202
- }
203
- const onListen = socket => {
204
- if(!socket) {
205
- let err = new Error('Failed to listen on port ' + port + '. No permission or address already in use.');
206
- throw err;
207
- }
208
- this.port = uWS.us_socket_local_port(socket);
209
- if(callback) callback(this.port);
210
- };
211
- let fn = 'listen';
212
- let args = [];
213
- if(typeof port !== 'number') {
214
- if(!isNaN(Number(port))) {
215
- port = Number(port);
216
- args.push(port, onListen);
217
- if(host) {
218
- args.unshift(host);
219
- }
220
- } else {
221
- fn = 'listen_unix';
222
- args.push(onListen, port);
223
- }
224
- } else {
225
- args.push(port, onListen);
226
- if(host) {
227
- args.unshift(host);
228
- }
229
- }
230
- this.listenCalled = true;
231
- this.uwsApp[fn](...args);
232
- return this.uwsApp;
233
- }
234
-
235
- address() {
236
- return { port: this.port };
237
- }
238
-
239
- path() {
240
- let paths = [this.mountpath];
241
- let parent = this.parent;
242
- while(parent) {
243
- paths.unshift(parent.mountpath);
244
- parent = parent.parent;
245
- }
246
- let path = removeDuplicateSlashes(paths.join(''));
247
- return path === '/' ? '' : path;
248
- }
249
-
250
- engine(ext, fn) {
251
- if (typeof fn !== 'function') {
252
- throw new Error('callback function required');
253
- }
254
- const extension = ext[0] !== '.'
255
- ? '.' + ext
256
- : ext;
257
- this.engines[extension] = fn;
258
- return this;
259
- }
260
-
261
- render(name, options, callback) {
262
- if(typeof options === 'function') {
263
- callback = options;
264
- options = new NullObject();
265
- }
266
- if(!options) {
267
- options = new NullObject();
268
- } else {
269
- options = Object.assign({}, options);
270
- }
271
- for(let key in this.locals) {
272
- options[key] = this.locals[key];
273
- }
274
-
275
- if(options._locals) {
276
- for(let key in options._locals) {
277
- options[key] = options._locals[key];
278
- }
279
- }
280
-
281
- if(options.cache == null) {
282
- options.cache = this.enabled('view cache');
283
- }
284
-
285
- let view;
286
- if(options.cache) {
287
- view = this.cache[name];
288
- }
289
-
290
- if(!view) {
291
- const View = this.get('view');
292
- view = new View(name, {
293
- defaultEngine: this.get('view engine'),
294
- root: this.get('views'),
295
- engines: {...this.engines}
296
- });
297
- if(!view.path) {
298
- const dirs = Array.isArray(view.root) && view.root.length > 1
299
- ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
300
- : 'directory "' + view.root + '"';
301
-
302
- const err = new Error(`Failed to lookup view "${name}" in views ${dirs}`);
303
- err.view = view;
304
- return callback(err);
305
- }
306
-
307
- if(options.cache) {
308
- this.cache[name] = view;
309
- }
310
- }
311
-
312
- try {
313
- view.render(options, callback);
314
- } catch(err) {
315
- callback(err);
316
- }
317
- }
318
- }
319
-
320
- module.exports = function(options) {
321
- return new Application(options);
322
- }
1
+ /*
2
+ Copyright 2024 dimden.dev
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ const uWS = require("uWebSockets.js");
18
+ const Router = require("./router.js");
19
+ const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator, fastQueryParse, NullObject } = require("./utils.js");
20
+ const querystring = require("fast-querystring");
21
+ const ViewClass = require("./view.js");
22
+ const path = require("path");
23
+ const os = require("os");
24
+ const { Worker } = require("worker_threads");
25
+
26
+ const cpuCount = os.cpus().length;
27
+
28
+ let workers = [];
29
+ let taskKey = 0;
30
+ const workerTasks = new NullObject();
31
+
32
+ function createWorker() {
33
+ const worker = new Worker(path.join(__dirname, 'worker.js'));
34
+ workers.push(worker);
35
+
36
+ worker.on('message', (message) => {
37
+ if(message.err) {
38
+ workerTasks[message.key].reject(new Error(message.err));
39
+ } else {
40
+ workerTasks[message.key].resolve(message.data);
41
+ }
42
+ delete workerTasks[message.key];
43
+ });
44
+ worker.unref();
45
+
46
+ return worker;
47
+ }
48
+
49
+ class Application extends Router {
50
+ constructor(settings = new NullObject()) {
51
+ super(settings);
52
+ if(!settings?.uwsOptions) {
53
+ settings.uwsOptions = {};
54
+ }
55
+ if(typeof settings.threads !== 'number') {
56
+ settings.threads = cpuCount > 1 ? 1 : 0;
57
+ }
58
+ if(settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
59
+ this.uwsApp = uWS.SSLApp(settings.uwsOptions);
60
+ this.ssl = true;
61
+ } else {
62
+ this.uwsApp = uWS.App(settings.uwsOptions);
63
+ this.ssl = false;
64
+ }
65
+ this.cache = new NullObject();
66
+ this.engines = {};
67
+ this.locals = {
68
+ settings: this.settings
69
+ };
70
+ this.listenCalled = false;
71
+ this.workers = [];
72
+ for(let i = 0; i < settings.threads; i++) {
73
+ if(workers[i]) {
74
+ this.workers[i] = workers[i];
75
+ } else {
76
+ this.workers[i] = createWorker();
77
+ }
78
+ }
79
+ this.port = undefined;
80
+ for(const key in defaultSettings) {
81
+ if(typeof this.settings[key] === 'undefined') {
82
+ if(typeof defaultSettings[key] === 'function') {
83
+ this.settings[key] = defaultSettings[key](this);
84
+ } else {
85
+ this.settings[key] = defaultSettings[key];
86
+ }
87
+ }
88
+ }
89
+ this.set('view', ViewClass);
90
+ this.set('views', path.resolve('views'));
91
+ }
92
+
93
+ createWorkerTask(resolve, reject) {
94
+ const key = taskKey++;
95
+ workerTasks[key] = { resolve, reject };
96
+ if(key > 1000000) {
97
+ taskKey = 0;
98
+ }
99
+ return key;
100
+ }
101
+
102
+ readFileWithWorker(path) {
103
+ return new Promise((resolve, reject) => {
104
+ const worker = this.workers[Math.floor(Math.random() * this.workers.length)];
105
+ const key = this.createWorkerTask(resolve, reject);
106
+ worker.postMessage({ key, type: 'readFile', path });
107
+ });
108
+ }
109
+
110
+ set(key, value) {
111
+ if(key === 'trust proxy') {
112
+ if(!value) {
113
+ delete this.settings['trust proxy fn'];
114
+ } else {
115
+ this.settings['trust proxy fn'] = compileTrust(value);
116
+ }
117
+ } else if(key === 'query parser') {
118
+ if(value === 'extended') {
119
+ this.settings['query parser fn'] = fastQueryParse;
120
+ } else if(value === 'simple') {
121
+ this.settings['query parser fn'] = querystring.parse;
122
+ } else if(typeof value === 'function') {
123
+ this.settings['query parser fn'] = value;
124
+ } else {
125
+ this.settings['query parser fn'] = undefined;
126
+ }
127
+ } else if(key === 'env') {
128
+ if(value === 'production') {
129
+ this.settings['view cache'] = true;
130
+ } else {
131
+ this.settings['view cache'] = undefined;
132
+ }
133
+ } else if(key === 'views') {
134
+ this.settings[key] = path.resolve(value);
135
+ return this;
136
+ } else if(key === 'etag') {
137
+ if(typeof value === 'function') {
138
+ this.settings['etag fn'] = value;
139
+ } else {
140
+ switch(value) {
141
+ case true:
142
+ case 'weak':
143
+ this.settings['etag fn'] = createETagGenerator({ weak: true });
144
+ break;
145
+ case 'strong':
146
+ this.settings['etag fn'] = createETagGenerator({ weak: false });
147
+ break;
148
+ case false:
149
+ delete this.settings['etag fn'];
150
+ break;
151
+ default:
152
+ throw new Error(`Invalid etag mode: ${value}`);
153
+ }
154
+ }
155
+ }
156
+
157
+ this.settings[key] = value;
158
+ return this;
159
+ }
160
+
161
+ enable(key) {
162
+ this.set(key, true);
163
+ return this;
164
+ }
165
+
166
+ disable(key) {
167
+ this.set(key, false);
168
+ return this;
169
+ }
170
+
171
+ enabled(key) {
172
+ return !!this.settings[key];
173
+ }
174
+
175
+ disabled(key) {
176
+ return !this.settings[key];
177
+ }
178
+
179
+ #createRequestHandler() {
180
+ this.uwsApp.any('/*', async (res, req) => {
181
+ const { request, response } = this.handleRequest(res, req);
182
+
183
+ const matchedRoute = await this._routeRequest(request, response);
184
+ if(!matchedRoute && !response.headersSent && !response.aborted) {
185
+ response.status(404);
186
+ this._sendErrorPage(request, response, `Cannot ${request.method} ${request.path}`, false);
187
+ }
188
+ });
189
+ }
190
+
191
+ listen(port, host, callback) {
192
+ this.#createRequestHandler();
193
+ // support listen(callback)
194
+ if(!callback && typeof port === 'function') {
195
+ callback = port;
196
+ port = 0;
197
+ }
198
+ // support listen(port, callback)
199
+ if(typeof host === 'function') {
200
+ callback = host;
201
+ host = undefined;
202
+ }
203
+ const onListen = socket => {
204
+ if(!socket) {
205
+ let err = new Error('Failed to listen on port ' + port + '. No permission or address already in use.');
206
+ throw err;
207
+ }
208
+ this.port = uWS.us_socket_local_port(socket);
209
+ if(callback) callback(this.port);
210
+ };
211
+ let fn = 'listen';
212
+ let args = [];
213
+ if(typeof port !== 'number') {
214
+ if(!isNaN(Number(port))) {
215
+ port = Number(port);
216
+ args.push(port, onListen);
217
+ if(host) {
218
+ args.unshift(host);
219
+ }
220
+ } else {
221
+ fn = 'listen_unix';
222
+ args.push(onListen, port);
223
+ }
224
+ } else {
225
+ args.push(port, onListen);
226
+ if(host) {
227
+ args.unshift(host);
228
+ }
229
+ }
230
+ this.listenCalled = true;
231
+ this.uwsApp[fn](...args);
232
+ return this.uwsApp;
233
+ }
234
+
235
+ address() {
236
+ return { port: this.port };
237
+ }
238
+
239
+ path() {
240
+ let paths = [this.mountpath];
241
+ let parent = this.parent;
242
+ while(parent) {
243
+ paths.unshift(parent.mountpath);
244
+ parent = parent.parent;
245
+ }
246
+ let path = removeDuplicateSlashes(paths.join(''));
247
+ return path === '/' ? '' : path;
248
+ }
249
+
250
+ engine(ext, fn) {
251
+ if (typeof fn !== 'function') {
252
+ throw new Error('callback function required');
253
+ }
254
+ const extension = ext[0] !== '.'
255
+ ? '.' + ext
256
+ : ext;
257
+ this.engines[extension] = fn;
258
+ return this;
259
+ }
260
+
261
+ render(name, options, callback) {
262
+ if(typeof options === 'function') {
263
+ callback = options;
264
+ options = new NullObject();
265
+ }
266
+ if(!options) {
267
+ options = new NullObject();
268
+ } else {
269
+ options = Object.assign({}, options);
270
+ }
271
+ for(let key in this.locals) {
272
+ options[key] = this.locals[key];
273
+ }
274
+
275
+ if(options._locals) {
276
+ for(let key in options._locals) {
277
+ options[key] = options._locals[key];
278
+ }
279
+ }
280
+
281
+ if(options.cache == null) {
282
+ options.cache = this.enabled('view cache');
283
+ }
284
+
285
+ let view;
286
+ if(options.cache) {
287
+ view = this.cache[name];
288
+ }
289
+
290
+ if(!view) {
291
+ const View = this.get('view');
292
+ view = new View(name, {
293
+ defaultEngine: this.get('view engine'),
294
+ root: this.get('views'),
295
+ engines: {...this.engines}
296
+ });
297
+ if(!view.path) {
298
+ const dirs = Array.isArray(view.root) && view.root.length > 1
299
+ ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
300
+ : 'directory "' + view.root + '"';
301
+
302
+ const err = new Error(`Failed to lookup view "${name}" in views ${dirs}`);
303
+ err.view = view;
304
+ return callback(err);
305
+ }
306
+
307
+ if(options.cache) {
308
+ this.cache[name] = view;
309
+ }
310
+ }
311
+
312
+ try {
313
+ view.render(options, callback);
314
+ } catch(err) {
315
+ callback(err);
316
+ }
317
+ }
318
+ }
319
+
320
+ module.exports = function(options) {
321
+ return new Application(options);
322
+ }