tspace-spear 1.2.1 → 1.2.3
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 +36 -4
- package/dist/lib/core/decorators/context.d.ts +91 -0
- package/dist/lib/core/decorators/controller.d.ts +34 -0
- package/dist/lib/core/decorators/headers.d.ts +31 -0
- package/dist/lib/core/decorators/index.d.ts +9 -0
- package/dist/lib/core/decorators/methods.d.ts +82 -0
- package/dist/lib/core/decorators/middleware.d.ts +39 -0
- package/dist/lib/core/decorators/middleware.js.map +1 -1
- package/dist/lib/core/decorators/statusCode.d.ts +26 -0
- package/dist/lib/core/decorators/swagger.d.ts +36 -0
- package/dist/lib/core/server/index.d.ts +302 -0
- package/dist/lib/core/server/index.js +343 -140
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/parser-factory.d.ts +32 -0
- package/dist/lib/core/server/parser-factory.js +269 -4
- package/dist/lib/core/server/parser-factory.js.map +1 -1
- package/dist/lib/core/server/router.d.ts +109 -0
- package/dist/lib/core/server/router.js +12 -0
- package/dist/lib/core/server/router.js.map +1 -1
- package/dist/lib/core/types/index.d.ts +248 -0
- package/dist/lib/index.d.ts +11 -0
- package/package.json +24 -10
- package/dist/lib/core/server/radix-router.js +0 -65
- package/dist/lib/core/server/radix-router.js.map +0 -1
- package/dist/tests/benchmark.test.js +0 -145
- package/dist/tests/benchmark.test.js.map +0 -1
|
@@ -15,27 +15,36 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
37
|
};
|
|
28
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
39
|
exports.Spear = exports.Application = void 0;
|
|
40
|
+
const http_1 = __importStar(require("http"));
|
|
41
|
+
const find_my_way_1 = __importDefault(require("find-my-way"));
|
|
30
42
|
const cluster_1 = __importDefault(require("cluster"));
|
|
31
43
|
const os_1 = __importDefault(require("os"));
|
|
32
44
|
const fs_1 = __importDefault(require("fs"));
|
|
33
45
|
const path_1 = __importDefault(require("path"));
|
|
34
|
-
const url_1 = require("url");
|
|
35
46
|
const on_finished_1 = __importDefault(require("on-finished"));
|
|
36
47
|
const ws_1 = __importDefault(require("ws"));
|
|
37
|
-
const http_1 = __importStar(require("http"));
|
|
38
|
-
const find_my_way_1 = __importDefault(require("find-my-way"));
|
|
39
48
|
const parser_factory_1 = require("./parser-factory");
|
|
40
49
|
/**
|
|
41
50
|
*
|
|
@@ -59,6 +68,7 @@ class Spear {
|
|
|
59
68
|
_globalPrefix;
|
|
60
69
|
_router = (0, find_my_way_1.default)();
|
|
61
70
|
_parser = new parser_factory_1.ParserFactory();
|
|
71
|
+
_adapter = http_1.default;
|
|
62
72
|
_cluster;
|
|
63
73
|
_cors;
|
|
64
74
|
_swagger = {
|
|
@@ -76,10 +86,10 @@ class Spear {
|
|
|
76
86
|
version: "1.0.0"
|
|
77
87
|
}
|
|
78
88
|
};
|
|
89
|
+
_swaggerSpecs = [];
|
|
79
90
|
_wss;
|
|
80
91
|
_ws;
|
|
81
92
|
_wsOptions;
|
|
82
|
-
_swaggerSpecs = [];
|
|
83
93
|
_errorHandler = null;
|
|
84
94
|
_globalMiddlewares = [];
|
|
85
95
|
_formatResponse = null;
|
|
@@ -92,11 +102,13 @@ class Spear {
|
|
|
92
102
|
ms: 1000 * 60 * 10
|
|
93
103
|
}
|
|
94
104
|
};
|
|
95
|
-
constructor({ controllers, middlewares, globalPrefix, logger, cluster } = {}) {
|
|
105
|
+
constructor({ controllers, middlewares, globalPrefix, logger, cluster, adapter } = {}) {
|
|
96
106
|
if (logger)
|
|
97
107
|
this.useLogger();
|
|
98
108
|
if (cluster)
|
|
99
109
|
this.useCluster(cluster);
|
|
110
|
+
if (adapter)
|
|
111
|
+
this.useAdater(adapter);
|
|
100
112
|
this._controllers = controllers;
|
|
101
113
|
this._middlewares = middlewares;
|
|
102
114
|
this._globalPrefix = globalPrefix == null ? '' : globalPrefix;
|
|
@@ -141,6 +153,18 @@ class Spear {
|
|
|
141
153
|
this._globalMiddlewares.push(middleware);
|
|
142
154
|
return this;
|
|
143
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The 'useAdater' method is used to switch between different server implementations,
|
|
158
|
+
* such as the native Node.js HTTP server or uWebSockets.js (uWS).
|
|
159
|
+
*
|
|
160
|
+
* @param {T.Adapter} adapter - The adapter instance (e.g., HTTP or uWS).
|
|
161
|
+
* @returns {this} Returns the current instance for chaining
|
|
162
|
+
*/
|
|
163
|
+
useAdater(adapter) {
|
|
164
|
+
this._adapter = adapter;
|
|
165
|
+
this._parser.useAdater(adapter);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
144
168
|
/**
|
|
145
169
|
* The 'useCluster' method is used cluster run the server
|
|
146
170
|
*
|
|
@@ -209,7 +233,7 @@ class Spear {
|
|
|
209
233
|
*/
|
|
210
234
|
useBodyParser({ except } = {}) {
|
|
211
235
|
this._globalMiddlewares.push((ctx, next) => {
|
|
212
|
-
const { req } = ctx;
|
|
236
|
+
const { req, res } = ctx;
|
|
213
237
|
const contentType = req?.headers['content-type'] ?? '';
|
|
214
238
|
const isFileUpload = contentType && contentType.startsWith('multipart/form-data');
|
|
215
239
|
const isCanParserBody = contentType.includes('application/json') ||
|
|
@@ -225,7 +249,7 @@ class Spear {
|
|
|
225
249
|
return next();
|
|
226
250
|
if (req?.body != null)
|
|
227
251
|
return next();
|
|
228
|
-
Promise.resolve(this._parser.body(req))
|
|
252
|
+
Promise.resolve(this._parser.body(req, res))
|
|
229
253
|
.then(r => {
|
|
230
254
|
req.body = r;
|
|
231
255
|
return next();
|
|
@@ -258,7 +282,7 @@ class Spear {
|
|
|
258
282
|
this._fileUploadOptions.removeTempFile = removeTempFile;
|
|
259
283
|
}
|
|
260
284
|
this._globalMiddlewares.push((ctx, next) => {
|
|
261
|
-
const { req } = ctx;
|
|
285
|
+
const { req, res } = ctx;
|
|
262
286
|
if (req.method === 'GET') {
|
|
263
287
|
return next();
|
|
264
288
|
}
|
|
@@ -269,7 +293,7 @@ class Spear {
|
|
|
269
293
|
if (req?.files != null)
|
|
270
294
|
return next();
|
|
271
295
|
Promise
|
|
272
|
-
.resolve(this._parser.files(req, this._fileUploadOptions))
|
|
296
|
+
.resolve(this._parser.files({ req, res, options: this._fileUploadOptions }))
|
|
273
297
|
.then(r => {
|
|
274
298
|
req.files = r.files;
|
|
275
299
|
req.body = r.body;
|
|
@@ -349,6 +373,22 @@ class Spear {
|
|
|
349
373
|
});
|
|
350
374
|
return server;
|
|
351
375
|
}
|
|
376
|
+
if ('App' in this._adapter) {
|
|
377
|
+
const handler = () => {
|
|
378
|
+
this._onListeners.forEach(listener => listener());
|
|
379
|
+
if (this._swagger.use) {
|
|
380
|
+
this._swaggerHandler();
|
|
381
|
+
}
|
|
382
|
+
callback?.({ server, port });
|
|
383
|
+
};
|
|
384
|
+
if (hostname) {
|
|
385
|
+
server.listen(port, String(hostname), handler);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
server.listen(port, handler);
|
|
389
|
+
}
|
|
390
|
+
return server;
|
|
391
|
+
}
|
|
352
392
|
const args = hostname
|
|
353
393
|
? [port, hostname, () => callback?.({ server, port: port })]
|
|
354
394
|
: [port, () => callback?.({ server, port: port })];
|
|
@@ -359,10 +399,6 @@ class Spear {
|
|
|
359
399
|
this._swaggerHandler();
|
|
360
400
|
}
|
|
361
401
|
});
|
|
362
|
-
server.on('error', (_) => {
|
|
363
|
-
port = Math.floor(Math.random() * 8999) + 1000;
|
|
364
|
-
server.listen(port);
|
|
365
|
-
});
|
|
366
402
|
return server;
|
|
367
403
|
}
|
|
368
404
|
/**
|
|
@@ -441,7 +477,8 @@ class Spear {
|
|
|
441
477
|
files: {},
|
|
442
478
|
body: {},
|
|
443
479
|
params: {},
|
|
444
|
-
cookies: {}
|
|
480
|
+
cookies: {},
|
|
481
|
+
ip: null
|
|
445
482
|
});
|
|
446
483
|
};
|
|
447
484
|
this._onListeners.push(() => {
|
|
@@ -584,37 +621,6 @@ class Spear {
|
|
|
584
621
|
});
|
|
585
622
|
return this;
|
|
586
623
|
}
|
|
587
|
-
_clusterMode({ server, port, hostname, callback }) {
|
|
588
|
-
if (cluster_1.default.isPrimary) {
|
|
589
|
-
const numCPUs = os_1.default.cpus().length;
|
|
590
|
-
const maxWorkers = typeof this._cluster === 'boolean' || this._cluster == null
|
|
591
|
-
? numCPUs
|
|
592
|
-
: this._cluster;
|
|
593
|
-
for (let i = 0; i < maxWorkers; i++) {
|
|
594
|
-
cluster_1.default.fork();
|
|
595
|
-
}
|
|
596
|
-
cluster_1.default.on('exit', () => {
|
|
597
|
-
cluster_1.default.fork();
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
if (cluster_1.default.isWorker) {
|
|
601
|
-
const args = hostname
|
|
602
|
-
? [port, hostname, () => callback?.({ server, port: port })]
|
|
603
|
-
: [port, () => callback?.({ server, port: port })];
|
|
604
|
-
server.listen(...args);
|
|
605
|
-
server.on('listening', () => {
|
|
606
|
-
this._onListeners.forEach(listener => listener());
|
|
607
|
-
if (this._swagger.use) {
|
|
608
|
-
this._swaggerHandler();
|
|
609
|
-
}
|
|
610
|
-
});
|
|
611
|
-
server.on('error', (_) => {
|
|
612
|
-
port = Math.floor(Math.random() * 8999) + 1000;
|
|
613
|
-
server.listen(port);
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
624
|
async _import(dir, pattern) {
|
|
619
625
|
const directories = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
620
626
|
const files = (await Promise.all(directories.map((directory) => {
|
|
@@ -744,18 +750,18 @@ class Spear {
|
|
|
744
750
|
}
|
|
745
751
|
if (results == null) {
|
|
746
752
|
if (this._formatResponse != null) {
|
|
747
|
-
return res.end(JSON.stringify(this._formatResponse(null, res.statusCode)
|
|
753
|
+
return res.end(JSON.stringify(this._formatResponse(null, res.statusCode)));
|
|
748
754
|
}
|
|
749
755
|
return res.end();
|
|
750
756
|
}
|
|
751
757
|
if (this._formatResponse != null) {
|
|
752
758
|
return res.end(JSON.stringify(this._formatResponse({
|
|
753
759
|
...results
|
|
754
|
-
}, res.statusCode)
|
|
760
|
+
}, res.statusCode)));
|
|
755
761
|
}
|
|
756
762
|
return res.end(JSON.stringify({
|
|
757
763
|
...results,
|
|
758
|
-
}
|
|
764
|
+
}));
|
|
759
765
|
};
|
|
760
766
|
response.send = (results) => {
|
|
761
767
|
if (res.writableEnded)
|
|
@@ -812,81 +818,81 @@ class Spear {
|
|
|
812
818
|
response.status(400);
|
|
813
819
|
message = message ?? `The request '${req.url}' resulted in a bad request. Please review the data and try again.`;
|
|
814
820
|
if (this._formatResponse != null) {
|
|
815
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 400)
|
|
821
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 400)));
|
|
816
822
|
}
|
|
817
823
|
return res.end(JSON.stringify({
|
|
818
824
|
message: message
|
|
819
|
-
}
|
|
825
|
+
}));
|
|
820
826
|
};
|
|
821
827
|
response.unauthorized = (message) => {
|
|
822
828
|
response.status(401);
|
|
823
829
|
message = message ?? `The request '${req.url}' is unauthorized. Please verify.`;
|
|
824
830
|
if (this._formatResponse != null) {
|
|
825
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 401)
|
|
831
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 401)));
|
|
826
832
|
}
|
|
827
833
|
return res.end(JSON.stringify({
|
|
828
834
|
message
|
|
829
|
-
}
|
|
835
|
+
}));
|
|
830
836
|
};
|
|
831
837
|
response.paymentRequired = (message) => {
|
|
832
838
|
response.status(402);
|
|
833
839
|
message = message ?? `The request '${req.url}' requires payment. Please proceed with payment.`;
|
|
834
840
|
if (this._formatResponse != null) {
|
|
835
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 402)
|
|
841
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 402)));
|
|
836
842
|
}
|
|
837
843
|
return res.end(JSON.stringify({
|
|
838
844
|
message
|
|
839
|
-
}
|
|
845
|
+
}));
|
|
840
846
|
};
|
|
841
847
|
response.forbidden = (message) => {
|
|
842
848
|
response.status(403);
|
|
843
849
|
message = message ?? `The request '${req.url}' is forbidden. Please check the permissions or access rights.`;
|
|
844
850
|
if (this._formatResponse != null) {
|
|
845
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 403)
|
|
851
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 403)));
|
|
846
852
|
}
|
|
847
853
|
return res.end(JSON.stringify({
|
|
848
854
|
message
|
|
849
|
-
}
|
|
855
|
+
}));
|
|
850
856
|
};
|
|
851
857
|
response.notFound = (message) => {
|
|
852
858
|
response.status(404);
|
|
853
859
|
message = message ?? `The request '${req.url}' was not found. Please re-check the your url again.`;
|
|
854
860
|
if (this._formatResponse != null) {
|
|
855
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 404)
|
|
861
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 404)));
|
|
856
862
|
}
|
|
857
863
|
return res.end(JSON.stringify({
|
|
858
864
|
message
|
|
859
|
-
}
|
|
865
|
+
}));
|
|
860
866
|
};
|
|
861
867
|
response.unprocessable = (message) => {
|
|
862
868
|
response.status(422);
|
|
863
869
|
message = message ?? `The request to '${req.url}' failed validation.`;
|
|
864
870
|
if (this._formatResponse != null) {
|
|
865
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 422)
|
|
871
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 422)));
|
|
866
872
|
}
|
|
867
873
|
return res.end(JSON.stringify({
|
|
868
874
|
message
|
|
869
|
-
}
|
|
875
|
+
}));
|
|
870
876
|
};
|
|
871
877
|
response.tooManyRequests = (message) => {
|
|
872
878
|
response.status(429);
|
|
873
879
|
message = message ?? `The request '${req.url}' is too many request. Please wait and try agian.`;
|
|
874
880
|
if (this._formatResponse != null) {
|
|
875
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 429)
|
|
881
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 429)));
|
|
876
882
|
}
|
|
877
883
|
return res.end(JSON.stringify({
|
|
878
884
|
message
|
|
879
|
-
}
|
|
885
|
+
}));
|
|
880
886
|
};
|
|
881
887
|
response.serverError = (message) => {
|
|
882
888
|
response.status(500);
|
|
883
889
|
message = message ?? `The request '${req.url}' resulted in a server error. Please investigate.`;
|
|
884
890
|
if (this._formatResponse != null) {
|
|
885
|
-
return res.end(JSON.stringify(this._formatResponse({ message }, 500)
|
|
891
|
+
return res.end(JSON.stringify(this._formatResponse({ message }, 500)));
|
|
886
892
|
}
|
|
887
893
|
return res.end(JSON.stringify({
|
|
888
894
|
message
|
|
889
|
-
}
|
|
895
|
+
}));
|
|
890
896
|
};
|
|
891
897
|
response.status = (code) => {
|
|
892
898
|
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
@@ -922,58 +928,48 @@ class Spear {
|
|
|
922
928
|
return response;
|
|
923
929
|
}
|
|
924
930
|
_wrapHandlers(...handlers) {
|
|
925
|
-
return (req, res,
|
|
926
|
-
|
|
931
|
+
return (req, res, ps) => {
|
|
932
|
+
if (res.writableEnded)
|
|
933
|
+
return;
|
|
934
|
+
const request = req;
|
|
935
|
+
const response = this._customizeResponse(req, res);
|
|
936
|
+
const params = ps;
|
|
937
|
+
const headers = req.headers;
|
|
938
|
+
const query = this._parser.queryString(req.url);
|
|
939
|
+
const xff = headers['x-forwarded-for'];
|
|
940
|
+
const ip = (Array.isArray(xff) ? xff[0] : xff)?.split(',')[0]?.trim()
|
|
941
|
+
|| (Array.isArray(headers['x-real-ip']) ? headers['x-real-ip'][0] : headers['x-real-ip'])
|
|
942
|
+
|| (Array.isArray(headers['cf-connecting-ip']) ? headers['cf-connecting-ip'][0] : headers['cf-connecting-ip'])
|
|
943
|
+
|| null;
|
|
944
|
+
const dispatch = (index = 0) => {
|
|
945
|
+
const body = request.body;
|
|
946
|
+
const files = request.files;
|
|
947
|
+
const cookies = request.cookies;
|
|
948
|
+
const ctx = {
|
|
949
|
+
req: request,
|
|
950
|
+
res: response,
|
|
951
|
+
headers: headers ?? {},
|
|
952
|
+
params: params ?? {},
|
|
953
|
+
query: query ?? {},
|
|
954
|
+
body: body ?? {},
|
|
955
|
+
files: files ?? {},
|
|
956
|
+
cookies: cookies ?? {},
|
|
957
|
+
ip: ip ?? null
|
|
958
|
+
};
|
|
927
959
|
try {
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const body = request.body;
|
|
932
|
-
const files = request.files;
|
|
933
|
-
const cookies = request.cookies;
|
|
934
|
-
const headers = request.headers;
|
|
935
|
-
const url = new url_1.URL(req.url, "http://localhost");
|
|
936
|
-
const query = Object.fromEntries(url.searchParams);
|
|
937
|
-
const RecordOrEmptyRecord = (data) => {
|
|
938
|
-
if (data == null)
|
|
939
|
-
return {};
|
|
940
|
-
return Object.keys(data).length ? data : {};
|
|
941
|
-
};
|
|
942
|
-
const ctx = {
|
|
943
|
-
req: request,
|
|
944
|
-
res: response,
|
|
945
|
-
headers: RecordOrEmptyRecord(headers),
|
|
946
|
-
params: RecordOrEmptyRecord(params),
|
|
947
|
-
query: RecordOrEmptyRecord(query),
|
|
948
|
-
body: RecordOrEmptyRecord(body),
|
|
949
|
-
files: RecordOrEmptyRecord(files),
|
|
950
|
-
cookies: RecordOrEmptyRecord(cookies)
|
|
951
|
-
};
|
|
960
|
+
const handler = handlers[index];
|
|
961
|
+
if (!handler)
|
|
962
|
+
return;
|
|
952
963
|
if (index === handlers.length - 1) {
|
|
953
|
-
return this._wrapResponse(
|
|
954
|
-
.bind(handlers[index]))(ctx, this._nextFunction(ctx));
|
|
964
|
+
return Promise.resolve(this._wrapResponse(handler)(ctx, this._nextFunction(ctx))).catch(err => this._nextFunction(ctx)(err));
|
|
955
965
|
}
|
|
956
|
-
return
|
|
957
|
-
return nextHandler(index + 1);
|
|
958
|
-
});
|
|
966
|
+
return Promise.resolve(handler(ctx, () => dispatch(index + 1))).catch(err => this._nextFunction(ctx)(err));
|
|
959
967
|
}
|
|
960
968
|
catch (err) {
|
|
961
|
-
const ctx = {
|
|
962
|
-
req,
|
|
963
|
-
res: this._customizeResponse(req, res),
|
|
964
|
-
params: Object.keys(params).length ? params : {},
|
|
965
|
-
headers: {},
|
|
966
|
-
query: {},
|
|
967
|
-
body: {},
|
|
968
|
-
files: {},
|
|
969
|
-
cookies: {}
|
|
970
|
-
};
|
|
971
969
|
return this._nextFunction(ctx)(err);
|
|
972
970
|
}
|
|
973
971
|
};
|
|
974
|
-
|
|
975
|
-
return;
|
|
976
|
-
return nextHandler();
|
|
972
|
+
return dispatch();
|
|
977
973
|
};
|
|
978
974
|
}
|
|
979
975
|
_wrapResponse(handler) {
|
|
@@ -996,9 +992,6 @@ class Spear {
|
|
|
996
992
|
return;
|
|
997
993
|
}
|
|
998
994
|
if (typeof result === 'string') {
|
|
999
|
-
if (!ctx.res.headersSent) {
|
|
1000
|
-
ctx.res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
1001
|
-
}
|
|
1002
995
|
ctx.res.end(result ?? '');
|
|
1003
996
|
return;
|
|
1004
997
|
}
|
|
@@ -1038,7 +1031,7 @@ class Spear {
|
|
|
1038
1031
|
ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1039
1032
|
return ctx.res.end(JSON.stringify({
|
|
1040
1033
|
message: err?.message
|
|
1041
|
-
}
|
|
1034
|
+
}));
|
|
1042
1035
|
}
|
|
1043
1036
|
return callback;
|
|
1044
1037
|
}
|
|
@@ -1046,39 +1039,109 @@ class Spear {
|
|
|
1046
1039
|
if (this._formatResponse != null) {
|
|
1047
1040
|
return ctx.res.end(JSON.stringify(this._formatResponse({
|
|
1048
1041
|
message: err?.message,
|
|
1049
|
-
}, ctx.res.statusCode)
|
|
1042
|
+
}, ctx.res.statusCode)));
|
|
1050
1043
|
}
|
|
1051
|
-
|
|
1044
|
+
ctx.res.end(JSON.stringify({
|
|
1052
1045
|
message: err?.message
|
|
1053
|
-
}
|
|
1046
|
+
}));
|
|
1047
|
+
return;
|
|
1054
1048
|
}
|
|
1055
1049
|
if (this._errorHandler != null) {
|
|
1056
1050
|
return this._errorHandler(new Error(NEXT_MESSAGE), ctx);
|
|
1057
1051
|
}
|
|
1058
1052
|
ctx.res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1059
1053
|
if (this._formatResponse != null) {
|
|
1060
|
-
|
|
1054
|
+
ctx.res.end(JSON.stringify(this._formatResponse({
|
|
1061
1055
|
message: NEXT_MESSAGE
|
|
1062
|
-
}, ctx.res.statusCode)
|
|
1056
|
+
}, ctx.res.statusCode)));
|
|
1057
|
+
return;
|
|
1063
1058
|
}
|
|
1064
|
-
|
|
1059
|
+
ctx.res.end(JSON.stringify({
|
|
1065
1060
|
message: NEXT_MESSAGE
|
|
1066
|
-
}
|
|
1061
|
+
}));
|
|
1062
|
+
return;
|
|
1067
1063
|
};
|
|
1068
1064
|
}
|
|
1065
|
+
_clusterMode({ server, port, hostname, callback }) {
|
|
1066
|
+
if (cluster_1.default.isPrimary) {
|
|
1067
|
+
const numCPUs = os_1.default.cpus().length;
|
|
1068
|
+
const maxWorkers = typeof this._cluster === 'boolean' || this._cluster == null
|
|
1069
|
+
? numCPUs
|
|
1070
|
+
: this._cluster;
|
|
1071
|
+
for (let i = 0; i < maxWorkers; i++) {
|
|
1072
|
+
cluster_1.default.fork();
|
|
1073
|
+
}
|
|
1074
|
+
cluster_1.default.on('exit', () => {
|
|
1075
|
+
cluster_1.default.fork();
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
if (cluster_1.default.isWorker) {
|
|
1079
|
+
if ('App' in this._adapter) {
|
|
1080
|
+
const handler = () => {
|
|
1081
|
+
this._onListeners.forEach(listener => listener());
|
|
1082
|
+
if (this._swagger.use) {
|
|
1083
|
+
this._swaggerHandler();
|
|
1084
|
+
}
|
|
1085
|
+
callback?.({ server, port });
|
|
1086
|
+
};
|
|
1087
|
+
if (hostname) {
|
|
1088
|
+
server.listen(port, hostname, handler);
|
|
1089
|
+
return server;
|
|
1090
|
+
}
|
|
1091
|
+
server.listen(port, handler);
|
|
1092
|
+
return server;
|
|
1093
|
+
}
|
|
1094
|
+
const args = hostname
|
|
1095
|
+
? [port, hostname, () => callback?.({ server, port: port })]
|
|
1096
|
+
: [port, () => callback?.({ server, port: port })];
|
|
1097
|
+
server.listen(...args);
|
|
1098
|
+
server.on('listening', () => {
|
|
1099
|
+
this._onListeners.forEach(listener => listener());
|
|
1100
|
+
if (this._swagger.use) {
|
|
1101
|
+
this._swaggerHandler();
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
server.on('error', (_) => {
|
|
1105
|
+
port = Math.floor(Math.random() * 8999) + 1000;
|
|
1106
|
+
server.listen(port);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1069
1111
|
async _createServer() {
|
|
1070
1112
|
await this._registerMiddlewares();
|
|
1071
1113
|
await this._registerControllers();
|
|
1072
1114
|
const lookup = this._router.lookup.bind(this._router);
|
|
1073
1115
|
const cors = this._cors;
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1116
|
+
const adapter = this._adapter;
|
|
1117
|
+
if ('App' in adapter) {
|
|
1118
|
+
const server = adapter.App();
|
|
1119
|
+
server.any('/*', (uwsRes, uwsReq) => {
|
|
1120
|
+
const { req, res } = this._uWSRequestResponse(uwsReq, uwsRes);
|
|
1121
|
+
if (cors)
|
|
1122
|
+
cors(req, res);
|
|
1080
1123
|
return lookup(req, res);
|
|
1081
1124
|
});
|
|
1125
|
+
if (this._ws) {
|
|
1126
|
+
server.ws('/*', {
|
|
1127
|
+
open: (ws) => {
|
|
1128
|
+
this._ws?.connection?.(ws);
|
|
1129
|
+
},
|
|
1130
|
+
message: (ws, message) => {
|
|
1131
|
+
this._ws?.message?.(ws, Buffer.from(message));
|
|
1132
|
+
},
|
|
1133
|
+
close: (ws, code, message) => {
|
|
1134
|
+
this._ws?.close?.(ws, code, Buffer.from(message));
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
return server;
|
|
1139
|
+
}
|
|
1140
|
+
const server = http_1.default.createServer((req, res) => {
|
|
1141
|
+
if (cors)
|
|
1142
|
+
cors(req, res);
|
|
1143
|
+
return lookup(req, res);
|
|
1144
|
+
});
|
|
1082
1145
|
if (this._ws) {
|
|
1083
1146
|
this._wss = new ws_1.default.Server({ server, ...this._wsOptions });
|
|
1084
1147
|
this._wss.on('connection', (ws) => {
|
|
@@ -1086,9 +1149,7 @@ class Spear {
|
|
|
1086
1149
|
this._ws.connection(ws);
|
|
1087
1150
|
}
|
|
1088
1151
|
ws.on('message', (data) => {
|
|
1089
|
-
|
|
1090
|
-
this._ws.message(ws, data);
|
|
1091
|
-
}
|
|
1152
|
+
this._ws?.message?.(ws, data);
|
|
1092
1153
|
});
|
|
1093
1154
|
ws.on('close', (code, reason) => {
|
|
1094
1155
|
if (this._ws?.close) {
|
|
@@ -1104,27 +1165,169 @@ class Spear {
|
|
|
1104
1165
|
}
|
|
1105
1166
|
return server;
|
|
1106
1167
|
}
|
|
1168
|
+
_uWSRequestResponse(uwsReq, uwsRes) {
|
|
1169
|
+
const req = {
|
|
1170
|
+
method: String(uwsReq.getMethod()).toLocaleUpperCase(),
|
|
1171
|
+
url: uwsReq.getUrl() + (uwsReq.getQuery() ? `?${uwsReq.getQuery()}` : ''),
|
|
1172
|
+
headers: {}
|
|
1173
|
+
};
|
|
1174
|
+
uwsReq.forEach((key, value) => req.headers[key] = value);
|
|
1175
|
+
const res = {
|
|
1176
|
+
writeHeader: (key, value) => {
|
|
1177
|
+
if (!res.aborted) {
|
|
1178
|
+
uwsRes.writeHeader(key, value);
|
|
1179
|
+
}
|
|
1180
|
+
return res;
|
|
1181
|
+
},
|
|
1182
|
+
setHeader: (key, value) => {
|
|
1183
|
+
if (!res.aborted) {
|
|
1184
|
+
uwsRes.writeHeader(key, value);
|
|
1185
|
+
}
|
|
1186
|
+
return res;
|
|
1187
|
+
},
|
|
1188
|
+
writeHead(status, context) {
|
|
1189
|
+
res.writeHeaders = {
|
|
1190
|
+
...res.writeHeaders,
|
|
1191
|
+
[status]: context
|
|
1192
|
+
};
|
|
1193
|
+
res.headersSent = true;
|
|
1194
|
+
res.statusCode = status;
|
|
1195
|
+
return res;
|
|
1196
|
+
},
|
|
1197
|
+
_writeHead(status, context) {
|
|
1198
|
+
const statusMessages = {
|
|
1199
|
+
100: 'Continue',
|
|
1200
|
+
101: 'Switching Protocols',
|
|
1201
|
+
102: 'Processing',
|
|
1202
|
+
200: 'OK',
|
|
1203
|
+
201: 'Created',
|
|
1204
|
+
202: 'Accepted',
|
|
1205
|
+
203: 'Non-Authoritative Information',
|
|
1206
|
+
204: 'No Content',
|
|
1207
|
+
205: 'Reset Content',
|
|
1208
|
+
206: 'Partial Content',
|
|
1209
|
+
207: 'Multi-Status',
|
|
1210
|
+
208: 'Already Reported',
|
|
1211
|
+
226: 'IM Used',
|
|
1212
|
+
300: 'Multiple Choices',
|
|
1213
|
+
301: 'Moved Permanently',
|
|
1214
|
+
302: 'Found',
|
|
1215
|
+
303: 'See Other',
|
|
1216
|
+
304: 'Not Modified',
|
|
1217
|
+
305: 'Use Proxy',
|
|
1218
|
+
306: '(Unused)',
|
|
1219
|
+
307: 'Temporary Redirect',
|
|
1220
|
+
308: 'Permanent Redirect',
|
|
1221
|
+
400: 'Bad Request',
|
|
1222
|
+
401: 'Unauthorized',
|
|
1223
|
+
402: 'Payment Required',
|
|
1224
|
+
403: 'Forbidden',
|
|
1225
|
+
404: 'Not Found',
|
|
1226
|
+
405: 'Method Not Allowed',
|
|
1227
|
+
406: 'Not Acceptable',
|
|
1228
|
+
407: 'Proxy Authentication Required',
|
|
1229
|
+
408: 'Request Timeout',
|
|
1230
|
+
409: 'Conflict',
|
|
1231
|
+
410: 'Gone',
|
|
1232
|
+
411: 'Length Required',
|
|
1233
|
+
412: 'Precondition Failed',
|
|
1234
|
+
413: 'Payload Too Large',
|
|
1235
|
+
414: 'URI Too Long',
|
|
1236
|
+
415: 'Unsupported Media Type',
|
|
1237
|
+
416: 'Range Not Satisfiable',
|
|
1238
|
+
417: 'Expectation Failed',
|
|
1239
|
+
418: 'I\'m a teapot',
|
|
1240
|
+
421: 'Misdirected Request',
|
|
1241
|
+
422: 'Unprocessable Entity',
|
|
1242
|
+
423: 'Locked',
|
|
1243
|
+
424: 'Failed Dependency',
|
|
1244
|
+
425: 'Too Early',
|
|
1245
|
+
426: 'Upgrade Required',
|
|
1246
|
+
428: 'Precondition Required',
|
|
1247
|
+
429: 'Too Many Requests',
|
|
1248
|
+
431: 'Request Header Fields Too Large',
|
|
1249
|
+
451: 'Unavailable For Legal Reasons',
|
|
1250
|
+
500: 'Internal Server Error',
|
|
1251
|
+
501: 'Not Implemented',
|
|
1252
|
+
502: 'Bad Gateway',
|
|
1253
|
+
503: 'Service Unavailable',
|
|
1254
|
+
504: 'Gateway Timeout',
|
|
1255
|
+
505: 'HTTP Version Not Supported',
|
|
1256
|
+
506: 'Variant Also Negotiates',
|
|
1257
|
+
507: 'Insufficient Storage',
|
|
1258
|
+
508: 'Loop Detected',
|
|
1259
|
+
510: 'Not Extended',
|
|
1260
|
+
511: 'Network Authentication Required'
|
|
1261
|
+
};
|
|
1262
|
+
const statusMessage = statusMessages[status] || statusMessages[500];
|
|
1263
|
+
res.uwsRes.writeStatus(`${status} ${statusMessage}`);
|
|
1264
|
+
res.uwsRes.writeHeader(Object.keys(context)[0], Object.values(context)[0]);
|
|
1265
|
+
return res;
|
|
1266
|
+
},
|
|
1267
|
+
writeStatus: (status) => {
|
|
1268
|
+
if (!res.aborted) {
|
|
1269
|
+
res.uwsRes.writeStatus(status);
|
|
1270
|
+
}
|
|
1271
|
+
return res;
|
|
1272
|
+
},
|
|
1273
|
+
end: (str) => {
|
|
1274
|
+
if (res.aborted) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
uwsRes.cork(() => {
|
|
1278
|
+
if (!res.aborted) {
|
|
1279
|
+
res.aborted = true;
|
|
1280
|
+
for (const h in res.writeHeaders) {
|
|
1281
|
+
//@ts-ignore
|
|
1282
|
+
res._writeHead(h, res.writeHeaders[h]);
|
|
1283
|
+
}
|
|
1284
|
+
uwsRes.end(str);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
},
|
|
1289
|
+
aborted: false,
|
|
1290
|
+
writeHeaders: {},
|
|
1291
|
+
headersSent: false,
|
|
1292
|
+
statusCode: 200,
|
|
1293
|
+
uwsRes,
|
|
1294
|
+
};
|
|
1295
|
+
uwsRes.onAborted(() => {
|
|
1296
|
+
res.aborted = true;
|
|
1297
|
+
});
|
|
1298
|
+
return { req, res };
|
|
1299
|
+
}
|
|
1107
1300
|
_normalizePath(...paths) {
|
|
1108
1301
|
const path = paths
|
|
1109
1302
|
.join('/')
|
|
1110
1303
|
.replace(/\/+/g, '/')
|
|
1111
1304
|
.replace(/\/+$/, '');
|
|
1112
1305
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
1113
|
-
return /\/api\/api/.test(normalizedPath)
|
|
1306
|
+
return /\/api\/api/.test(normalizedPath)
|
|
1307
|
+
? normalizedPath.replace(/\/api\/api\//, "/api/")
|
|
1308
|
+
: normalizedPath;
|
|
1114
1309
|
}
|
|
1115
1310
|
_swaggerHandler() {
|
|
1116
1311
|
const routes = this.routers
|
|
1117
|
-
.routes
|
|
1312
|
+
.routes
|
|
1313
|
+
.filter(r => {
|
|
1314
|
+
return [
|
|
1315
|
+
"GET", "POST",
|
|
1316
|
+
"PUT", "PATCH",
|
|
1317
|
+
"DELETE",
|
|
1318
|
+
"HEAD", "OPTIONS"
|
|
1319
|
+
].includes(r.method);
|
|
1320
|
+
});
|
|
1118
1321
|
const { path, html, staticSwaggerHandler, staticUrl } = this._parser.swagger({
|
|
1119
1322
|
...this._swagger,
|
|
1120
1323
|
specs: this._swaggerSpecs,
|
|
1121
1324
|
routes
|
|
1122
1325
|
});
|
|
1123
1326
|
this._router.get(staticUrl, staticSwaggerHandler);
|
|
1124
|
-
this._router.get(
|
|
1327
|
+
this._router.get(path, (_, res) => {
|
|
1125
1328
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1126
|
-
res.
|
|
1127
|
-
return
|
|
1329
|
+
res.end(html);
|
|
1330
|
+
return;
|
|
1128
1331
|
});
|
|
1129
1332
|
return;
|
|
1130
1333
|
}
|