@e22m4u/js-trie-router 0.3.4 → 0.3.6
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 +60 -7
- package/dist/cjs/index.cjs +243 -162
- package/package.json +13 -13
- package/src/request-context.d.ts +6 -0
- package/src/request-context.js +7 -0
- package/src/request-context.spec.js +8 -0
- package/src/route.d.ts +11 -0
- package/src/route.js +30 -5
- package/src/route.spec.js +289 -204
- package/src/trie-router.d.ts +14 -0
- package/src/trie-router.js +31 -9
- package/src/trie-router.spec.js +46 -0
- package/src/utils/clone-deep.d.ts +6 -0
- package/src/utils/clone-deep.js +33 -0
- package/src/utils/clone-deep.spec.js +194 -0
- package/src/utils/create-response-mock.js +9 -1
- package/src/utils/create-response-mock.spec.js +2 -5
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
package/README.md
CHANGED
|
@@ -9,6 +9,21 @@ HTTP маршрутизатор для Node.js на основе
|
|
|
9
9
|
- Поддержка `preHandler` и `postHandler` хуков.
|
|
10
10
|
- Позволяет использовать асинхронные обработчики.
|
|
11
11
|
|
|
12
|
+
## Содержание
|
|
13
|
+
|
|
14
|
+
- [Установка](#установка)
|
|
15
|
+
- [Обзор](#обзор)
|
|
16
|
+
- [Контекст запроса](#контекст-запроса)
|
|
17
|
+
- [Отправка ответа](#отправка-ответа)
|
|
18
|
+
- [Хуки маршрута](#хуки-маршрута)
|
|
19
|
+
- [preHandler](#prehandler)
|
|
20
|
+
- [postHandler](#posthandler)
|
|
21
|
+
- [Глобальные хуки](#глобальные-хуки)
|
|
22
|
+
- [Метаданные](#метаданные)
|
|
23
|
+
- [Отладка](#отладка)
|
|
24
|
+
- [Тестирование](#тестирование)
|
|
25
|
+
- [Лицензия](#лицензия)
|
|
26
|
+
|
|
12
27
|
## Установка
|
|
13
28
|
|
|
14
29
|
Требуется Node.js 16 и выше.
|
|
@@ -74,6 +89,7 @@ server.listen(3000, 'localhost'); // прослушивание за
|
|
|
74
89
|
- `path: string` путь включающий строку запроса, например `/myPath?foo=bar`
|
|
75
90
|
- `pathname: string` путь запроса, например `/myPath`
|
|
76
91
|
- `body: unknown` тело запроса
|
|
92
|
+
- `meta: object` мета-данные из определения маршрута
|
|
77
93
|
|
|
78
94
|
Пример доступа к контексту из обработчика маршрута.
|
|
79
95
|
|
|
@@ -81,6 +97,7 @@ server.listen(3000, 'localhost'); // прослушивание за
|
|
|
81
97
|
router.defineRoute({
|
|
82
98
|
method: 'GET',
|
|
83
99
|
path: '/users/:id',
|
|
100
|
+
meta: {prop: 'value'},
|
|
84
101
|
handler(ctx) {
|
|
85
102
|
// GET /users/10?include=city
|
|
86
103
|
// Cookie: foo=bar; baz=qux;
|
|
@@ -93,6 +110,7 @@ router.defineRoute({
|
|
|
93
110
|
console.log(ctx.method); // "GET"
|
|
94
111
|
console.log(ctx.path); // "/users/10?include=city"
|
|
95
112
|
console.log(ctx.pathname); // "/users/10"
|
|
113
|
+
console.log(ctx.meta); // {prop: 'value'}
|
|
96
114
|
// ...
|
|
97
115
|
},
|
|
98
116
|
});
|
|
@@ -213,18 +231,16 @@ router.defineRoute({
|
|
|
213
231
|
имеют более высокий приоритет перед хуками маршрута, и вызываются
|
|
214
232
|
в первую очередь.
|
|
215
233
|
|
|
216
|
-
- `preHandler` выполняется перед вызовом обработчика каждого
|
|
217
|
-
- `postHandler` выполняется после вызова обработчика каждого
|
|
234
|
+
- `preHandler` выполняется перед вызовом обработчика каждого маршрута;
|
|
235
|
+
- `postHandler` выполняется после вызова обработчика каждого маршрута;
|
|
218
236
|
|
|
219
|
-
Добавить глобальные хуки можно
|
|
220
|
-
где первым параметром передается тип хука, а вторым его функция.
|
|
237
|
+
Добавить глобальные хуки можно методами экземпляра `TrieRouter`.
|
|
221
238
|
|
|
222
239
|
```js
|
|
223
|
-
router.
|
|
240
|
+
router.addPreHandler((ctx) => {
|
|
224
241
|
// перед обработчиком маршрута
|
|
225
242
|
});
|
|
226
|
-
|
|
227
|
-
router.addHook('postHandler', (ctx, data) => {
|
|
243
|
+
router.addPostHandler((ctx, data) => {
|
|
228
244
|
// после обработчика маршрута
|
|
229
245
|
});
|
|
230
246
|
```
|
|
@@ -233,6 +249,43 @@ router.addHook('postHandler', (ctx, data) => {
|
|
|
233
249
|
отличное от `undefined` и `null`, то такое значение будет использовано
|
|
234
250
|
как ответ сервера.
|
|
235
251
|
|
|
252
|
+
### Метаданные
|
|
253
|
+
|
|
254
|
+
Иногда требуется связать с маршрутом дополнительные, статические данные, которые
|
|
255
|
+
могут быть использованы хуками для расширения функционала. Например, это могут
|
|
256
|
+
быть схемы для валидации данных, правила доступа или настройки кэширования.
|
|
257
|
+
Для этой цели определение маршрута поддерживает необязательное свойство `meta`.
|
|
258
|
+
|
|
259
|
+
Маршрутизатор лишь обеспечивает передачу мета-данных в контекст запроса,
|
|
260
|
+
откуда его могут прочитать обработчики или хуки.
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
import http from 'http';
|
|
264
|
+
import {TrieRouter} from '@e22m4u/js-trie-router';
|
|
265
|
+
|
|
266
|
+
const server = new http.Server();
|
|
267
|
+
const router = new TrieRouter();
|
|
268
|
+
|
|
269
|
+
// глобальный pre-handler хук, который срабатывает
|
|
270
|
+
// перед основным обработчиком каждого маршрута
|
|
271
|
+
router.addPreHandler((ctx) => {
|
|
272
|
+
// доступ к метаданным текущего маршрута
|
|
273
|
+
console.log(ctx.meta); // {foo: 'bar'}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
router.defineRoute({
|
|
277
|
+
method: 'GET',
|
|
278
|
+
path: '/',
|
|
279
|
+
meta: {foo: 'bar'}, // <= мета-данные
|
|
280
|
+
handler(ctx) {
|
|
281
|
+
return 'Hello World!';
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
server.on('request', router.requestListener);
|
|
286
|
+
server.listen(3000, 'localhost');
|
|
287
|
+
```
|
|
288
|
+
|
|
236
289
|
## Отладка
|
|
237
290
|
|
|
238
291
|
Установка переменной `DEBUG` включает вывод логов.
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(index_exports, {
|
|
|
50
50
|
RouterOptions: () => RouterOptions,
|
|
51
51
|
TrieRouter: () => TrieRouter,
|
|
52
52
|
UNPARSABLE_MEDIA_TYPES: () => UNPARSABLE_MEDIA_TYPES,
|
|
53
|
+
cloneDeep: () => cloneDeep,
|
|
53
54
|
createCookiesString: () => createCookiesString,
|
|
54
55
|
createDebugger: () => createDebugger,
|
|
55
56
|
createError: () => createError,
|
|
@@ -75,6 +76,31 @@ var import_js_debug = require("@e22m4u/js-debug");
|
|
|
75
76
|
// src/hooks/hook-invoker.js
|
|
76
77
|
var import_js_format13 = require("@e22m4u/js-format");
|
|
77
78
|
|
|
79
|
+
// src/utils/clone-deep.js
|
|
80
|
+
function cloneDeep(value) {
|
|
81
|
+
if (value == null || typeof value !== "object") {
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
if (value instanceof Date) {
|
|
85
|
+
return new Date(value.getTime());
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
return value.map((item) => cloneDeep(item));
|
|
89
|
+
}
|
|
90
|
+
const proto = Object.getPrototypeOf(value);
|
|
91
|
+
if (proto === Object.prototype || proto === null) {
|
|
92
|
+
const newObj = {};
|
|
93
|
+
for (const key in value) {
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
95
|
+
newObj[key] = cloneDeep(value[key]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return newObj;
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
__name(cloneDeep, "cloneDeep");
|
|
103
|
+
|
|
78
104
|
// src/utils/is-promise.js
|
|
79
105
|
function isPromise(value) {
|
|
80
106
|
if (!value) return false;
|
|
@@ -605,9 +631,13 @@ function patchBody(res) {
|
|
|
605
631
|
res.on("data", (c) => data.push(c));
|
|
606
632
|
res.on("error", (e) => reject(e));
|
|
607
633
|
res.on("end", () => {
|
|
608
|
-
res._headersSent = true;
|
|
609
634
|
resolve(Buffer.concat(data));
|
|
610
635
|
});
|
|
636
|
+
const originalEnd = res.end.bind(res);
|
|
637
|
+
res.end = function(...args) {
|
|
638
|
+
this._headersSent = true;
|
|
639
|
+
return originalEnd(...args);
|
|
640
|
+
};
|
|
611
641
|
Object.defineProperty(res, "getBody", {
|
|
612
642
|
configurable: true,
|
|
613
643
|
value: /* @__PURE__ */ __name(function() {
|
|
@@ -827,6 +857,20 @@ var _Route = class _Route extends import_js_debug.Debuggable {
|
|
|
827
857
|
get path() {
|
|
828
858
|
return this._path;
|
|
829
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Meta.
|
|
862
|
+
*
|
|
863
|
+
* @type {object}
|
|
864
|
+
*/
|
|
865
|
+
_meta = {};
|
|
866
|
+
/**
|
|
867
|
+
* Getter of the meta.
|
|
868
|
+
*
|
|
869
|
+
* @returns {object}
|
|
870
|
+
*/
|
|
871
|
+
get meta() {
|
|
872
|
+
return this._meta;
|
|
873
|
+
}
|
|
830
874
|
/**
|
|
831
875
|
* Handler.
|
|
832
876
|
*
|
|
@@ -890,6 +934,14 @@ var _Route = class _Route extends import_js_debug.Debuggable {
|
|
|
890
934
|
'The option "handler" of the Route should be a Function, but %v was given.',
|
|
891
935
|
routeDef.handler
|
|
892
936
|
);
|
|
937
|
+
if (routeDef.meta != null) {
|
|
938
|
+
if (typeof routeDef.meta !== "object" || Array.isArray(routeDef.meta))
|
|
939
|
+
throw new import_js_format14.Errorf(
|
|
940
|
+
'The option "meta" of the Route should be a plain Object, but %v was given.',
|
|
941
|
+
routeDef.meta
|
|
942
|
+
);
|
|
943
|
+
this._meta = cloneDeep(routeDef.meta);
|
|
944
|
+
}
|
|
893
945
|
this._handler = routeDef.handler;
|
|
894
946
|
if (routeDef.preHandler != null) {
|
|
895
947
|
const preHandlerHooks = Array.isArray(routeDef.preHandler) ? routeDef.preHandler : [routeDef.preHandler];
|
|
@@ -925,156 +977,12 @@ var _Route = class _Route extends import_js_debug.Debuggable {
|
|
|
925
977
|
__name(_Route, "Route");
|
|
926
978
|
var Route = _Route;
|
|
927
979
|
|
|
928
|
-
// src/trie-router.js
|
|
929
|
-
var import_http4 = require("http");
|
|
930
|
-
var import_http5 = require("http");
|
|
931
|
-
|
|
932
|
-
// src/senders/data-sender.js
|
|
933
|
-
var import_js_format15 = require("@e22m4u/js-format");
|
|
934
|
-
var _DataSender = class _DataSender extends DebuggableService {
|
|
935
|
-
/**
|
|
936
|
-
* Send.
|
|
937
|
-
*
|
|
938
|
-
* @param {import('http').ServerResponse} res
|
|
939
|
-
* @param {*} data
|
|
940
|
-
* @returns {undefined}
|
|
941
|
-
*/
|
|
942
|
-
send(res, data) {
|
|
943
|
-
const debug = this.getDebuggerFor(this.send);
|
|
944
|
-
if (data === res || res.headersSent) {
|
|
945
|
-
debug(
|
|
946
|
-
"Response sending was skipped because its headers where sent already."
|
|
947
|
-
);
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
if (data == null) {
|
|
951
|
-
res.statusCode = 204;
|
|
952
|
-
res.end();
|
|
953
|
-
debug("The empty response was sent.");
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
if (isReadableStream(data)) {
|
|
957
|
-
res.setHeader("Content-Type", "application/octet-stream");
|
|
958
|
-
data.pipe(res);
|
|
959
|
-
debug("The stream response was sent.");
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
let debugMsg;
|
|
963
|
-
switch (typeof data) {
|
|
964
|
-
case "object":
|
|
965
|
-
case "boolean":
|
|
966
|
-
case "number":
|
|
967
|
-
if (Buffer.isBuffer(data)) {
|
|
968
|
-
res.setHeader("content-type", "application/octet-stream");
|
|
969
|
-
debugMsg = "The Buffer was sent as binary data.";
|
|
970
|
-
} else {
|
|
971
|
-
res.setHeader("content-type", "application/json");
|
|
972
|
-
debugMsg = (0, import_js_format15.format)("The %v was sent as JSON.", typeof data);
|
|
973
|
-
data = JSON.stringify(data);
|
|
974
|
-
}
|
|
975
|
-
break;
|
|
976
|
-
default:
|
|
977
|
-
res.setHeader("content-type", "text/plain");
|
|
978
|
-
debugMsg = "The response data was sent as plain text.";
|
|
979
|
-
data = String(data);
|
|
980
|
-
break;
|
|
981
|
-
}
|
|
982
|
-
res.end(data);
|
|
983
|
-
debug(debugMsg);
|
|
984
|
-
}
|
|
985
|
-
};
|
|
986
|
-
__name(_DataSender, "DataSender");
|
|
987
|
-
var DataSender = _DataSender;
|
|
988
|
-
|
|
989
|
-
// src/senders/error-sender.js
|
|
990
|
-
var import_util = require("util");
|
|
991
|
-
var import_statuses = __toESM(require("statuses"), 1);
|
|
992
|
-
var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
|
|
993
|
-
var _ErrorSender = class _ErrorSender extends DebuggableService {
|
|
994
|
-
/**
|
|
995
|
-
* Handle.
|
|
996
|
-
*
|
|
997
|
-
* @param {import('http').IncomingMessage} req
|
|
998
|
-
* @param {import('http').ServerResponse} res
|
|
999
|
-
* @param {Error} error
|
|
1000
|
-
* @returns {undefined}
|
|
1001
|
-
*/
|
|
1002
|
-
send(req, res, error) {
|
|
1003
|
-
const debug = this.getDebuggerFor(this.send);
|
|
1004
|
-
let safeError = {};
|
|
1005
|
-
if (error) {
|
|
1006
|
-
if (typeof error === "object") {
|
|
1007
|
-
safeError = error;
|
|
1008
|
-
} else {
|
|
1009
|
-
safeError = { message: String(error) };
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
const statusCode = error.statusCode || error.status || 500;
|
|
1013
|
-
const body = { error: {} };
|
|
1014
|
-
if (safeError.message && typeof safeError.message === "string") {
|
|
1015
|
-
body.error.message = safeError.message;
|
|
1016
|
-
} else {
|
|
1017
|
-
body.error.message = (0, import_statuses.default)(statusCode);
|
|
1018
|
-
}
|
|
1019
|
-
EXPOSED_ERROR_PROPERTIES.forEach((name) => {
|
|
1020
|
-
if (name in safeError) body.error[name] = safeError[name];
|
|
1021
|
-
});
|
|
1022
|
-
const requestData = {
|
|
1023
|
-
url: req.url,
|
|
1024
|
-
method: req.method,
|
|
1025
|
-
headers: req.headers
|
|
1026
|
-
};
|
|
1027
|
-
const inspectOptions = {
|
|
1028
|
-
showHidden: false,
|
|
1029
|
-
depth: null,
|
|
1030
|
-
colors: true,
|
|
1031
|
-
compact: false
|
|
1032
|
-
};
|
|
1033
|
-
console.warn((0, import_util.inspect)(requestData, inspectOptions));
|
|
1034
|
-
console.warn((0, import_util.inspect)(body, inspectOptions));
|
|
1035
|
-
if (error.stack) {
|
|
1036
|
-
console.log(error.stack);
|
|
1037
|
-
} else {
|
|
1038
|
-
console.error(error);
|
|
1039
|
-
}
|
|
1040
|
-
res.statusCode = statusCode;
|
|
1041
|
-
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1042
|
-
res.end(JSON.stringify(body, null, 2), "utf-8");
|
|
1043
|
-
debug(
|
|
1044
|
-
"The %s error was sent for the request %s %v.",
|
|
1045
|
-
statusCode,
|
|
1046
|
-
req.method,
|
|
1047
|
-
getRequestPathname(req)
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Send 404.
|
|
1052
|
-
*
|
|
1053
|
-
* @param {import('http').IncomingMessage} req
|
|
1054
|
-
* @param {import('http').ServerResponse} res
|
|
1055
|
-
* @returns {undefined}
|
|
1056
|
-
*/
|
|
1057
|
-
send404(req, res) {
|
|
1058
|
-
const debug = this.getDebuggerFor(this.send404);
|
|
1059
|
-
res.statusCode = 404;
|
|
1060
|
-
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
1061
|
-
res.end("404 Not Found", "utf-8");
|
|
1062
|
-
debug(
|
|
1063
|
-
"The 404 error was sent for the request %s %v.",
|
|
1064
|
-
req.method,
|
|
1065
|
-
getRequestPathname(req)
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
__name(_ErrorSender, "ErrorSender");
|
|
1070
|
-
var ErrorSender = _ErrorSender;
|
|
1071
|
-
|
|
1072
980
|
// src/parsers/body-parser.js
|
|
1073
981
|
var import_http_errors2 = __toESM(require("http-errors"), 1);
|
|
1074
|
-
var
|
|
982
|
+
var import_js_format16 = require("@e22m4u/js-format");
|
|
1075
983
|
|
|
1076
984
|
// src/router-options.js
|
|
1077
|
-
var
|
|
985
|
+
var import_js_format15 = require("@e22m4u/js-format");
|
|
1078
986
|
var _RouterOptions = class _RouterOptions extends DebuggableService {
|
|
1079
987
|
/**
|
|
1080
988
|
* Request body bytes limit.
|
|
@@ -1100,7 +1008,7 @@ var _RouterOptions = class _RouterOptions extends DebuggableService {
|
|
|
1100
1008
|
*/
|
|
1101
1009
|
setRequestBodyBytesLimit(input) {
|
|
1102
1010
|
if (typeof input !== "number" || input < 0)
|
|
1103
|
-
throw new
|
|
1011
|
+
throw new import_js_format15.Errorf(
|
|
1104
1012
|
'The option "requestBodyBytesLimit" must be a positive Number or 0, but %v was given.',
|
|
1105
1013
|
input
|
|
1106
1014
|
);
|
|
@@ -1133,12 +1041,12 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1133
1041
|
*/
|
|
1134
1042
|
defineParser(mediaType, parser) {
|
|
1135
1043
|
if (!mediaType || typeof mediaType !== "string")
|
|
1136
|
-
throw new
|
|
1044
|
+
throw new import_js_format16.Errorf(
|
|
1137
1045
|
'The parameter "mediaType" of BodyParser.defineParser should be a non-empty String, but %v was given.',
|
|
1138
1046
|
mediaType
|
|
1139
1047
|
);
|
|
1140
1048
|
if (!parser || typeof parser !== "function")
|
|
1141
|
-
throw new
|
|
1049
|
+
throw new import_js_format16.Errorf(
|
|
1142
1050
|
'The parameter "parser" of BodyParser.defineParser should be a Function, but %v was given.',
|
|
1143
1051
|
parser
|
|
1144
1052
|
);
|
|
@@ -1153,7 +1061,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1153
1061
|
*/
|
|
1154
1062
|
hasParser(mediaType) {
|
|
1155
1063
|
if (!mediaType || typeof mediaType !== "string")
|
|
1156
|
-
throw new
|
|
1064
|
+
throw new import_js_format16.Errorf(
|
|
1157
1065
|
'The parameter "mediaType" of BodyParser.hasParser should be a non-empty String, but %v was given.',
|
|
1158
1066
|
mediaType
|
|
1159
1067
|
);
|
|
@@ -1167,12 +1075,12 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1167
1075
|
*/
|
|
1168
1076
|
deleteParser(mediaType) {
|
|
1169
1077
|
if (!mediaType || typeof mediaType !== "string")
|
|
1170
|
-
throw new
|
|
1078
|
+
throw new import_js_format16.Errorf(
|
|
1171
1079
|
'The parameter "mediaType" of BodyParser.deleteParser should be a non-empty String, but %v was given.',
|
|
1172
1080
|
mediaType
|
|
1173
1081
|
);
|
|
1174
1082
|
const parser = this._parsers[mediaType];
|
|
1175
|
-
if (!parser) throw new
|
|
1083
|
+
if (!parser) throw new import_js_format16.Errorf("The parser of %v is not found.", mediaType);
|
|
1176
1084
|
delete this._parsers[mediaType];
|
|
1177
1085
|
return this;
|
|
1178
1086
|
}
|
|
@@ -1301,7 +1209,7 @@ var CookiesParser = _CookiesParser;
|
|
|
1301
1209
|
|
|
1302
1210
|
// src/parsers/request-parser.js
|
|
1303
1211
|
var import_http3 = require("http");
|
|
1304
|
-
var
|
|
1212
|
+
var import_js_format17 = require("@e22m4u/js-format");
|
|
1305
1213
|
var _RequestParser = class _RequestParser extends DebuggableService {
|
|
1306
1214
|
/**
|
|
1307
1215
|
* Parse.
|
|
@@ -1311,7 +1219,7 @@ var _RequestParser = class _RequestParser extends DebuggableService {
|
|
|
1311
1219
|
*/
|
|
1312
1220
|
parse(req) {
|
|
1313
1221
|
if (!(req instanceof import_http3.IncomingMessage))
|
|
1314
|
-
throw new
|
|
1222
|
+
throw new import_js_format17.Errorf(
|
|
1315
1223
|
"The first argument of RequestParser.parse should be an instance of IncomingMessage, but %v was given.",
|
|
1316
1224
|
req
|
|
1317
1225
|
);
|
|
@@ -1343,7 +1251,7 @@ __name(_RequestParser, "RequestParser");
|
|
|
1343
1251
|
var RequestParser = _RequestParser;
|
|
1344
1252
|
|
|
1345
1253
|
// src/route-registry.js
|
|
1346
|
-
var
|
|
1254
|
+
var import_js_format18 = require("@e22m4u/js-format");
|
|
1347
1255
|
var import_js_path_trie = require("@e22m4u/js-path-trie");
|
|
1348
1256
|
var import_js_service2 = require("@e22m4u/js-service");
|
|
1349
1257
|
var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
@@ -1365,7 +1273,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
|
1365
1273
|
defineRoute(routeDef) {
|
|
1366
1274
|
const debug = this.getDebuggerFor(this.defineRoute);
|
|
1367
1275
|
if (!routeDef || typeof routeDef !== "object" || Array.isArray(routeDef))
|
|
1368
|
-
throw new
|
|
1276
|
+
throw new import_js_format18.Errorf(
|
|
1369
1277
|
"The route definition should be an Object, but %v was given.",
|
|
1370
1278
|
routeDef
|
|
1371
1279
|
);
|
|
@@ -1428,7 +1336,7 @@ __name(_RouteRegistry, "RouteRegistry");
|
|
|
1428
1336
|
var RouteRegistry = _RouteRegistry;
|
|
1429
1337
|
|
|
1430
1338
|
// src/request-context.js
|
|
1431
|
-
var
|
|
1339
|
+
var import_js_format19 = require("@e22m4u/js-format");
|
|
1432
1340
|
var import_js_service3 = require("@e22m4u/js-service");
|
|
1433
1341
|
var import_js_service4 = require("@e22m4u/js-service");
|
|
1434
1342
|
var _RequestContext = class _RequestContext {
|
|
@@ -1480,6 +1388,12 @@ var _RequestContext = class _RequestContext {
|
|
|
1480
1388
|
* @type {*}
|
|
1481
1389
|
*/
|
|
1482
1390
|
body;
|
|
1391
|
+
/**
|
|
1392
|
+
* Route meta.
|
|
1393
|
+
*
|
|
1394
|
+
* @type {object}
|
|
1395
|
+
*/
|
|
1396
|
+
meta = {};
|
|
1483
1397
|
/**
|
|
1484
1398
|
* Method.
|
|
1485
1399
|
*
|
|
@@ -1522,20 +1436,20 @@ var _RequestContext = class _RequestContext {
|
|
|
1522
1436
|
*/
|
|
1523
1437
|
constructor(container, request, response) {
|
|
1524
1438
|
if (!(0, import_js_service4.isServiceContainer)(container))
|
|
1525
|
-
throw new
|
|
1439
|
+
throw new import_js_format19.Errorf(
|
|
1526
1440
|
'The parameter "container" of RequestContext.constructor should be an instance of ServiceContainer, but %v was given.',
|
|
1527
1441
|
container
|
|
1528
1442
|
);
|
|
1529
1443
|
this.container = container;
|
|
1530
1444
|
if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
|
|
1531
|
-
throw new
|
|
1445
|
+
throw new import_js_format19.Errorf(
|
|
1532
1446
|
'The parameter "request" of RequestContext.constructor should be an instance of IncomingMessage, but %v was given.',
|
|
1533
1447
|
request
|
|
1534
1448
|
);
|
|
1535
1449
|
}
|
|
1536
1450
|
this.req = request;
|
|
1537
1451
|
if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
|
|
1538
|
-
throw new
|
|
1452
|
+
throw new import_js_format19.Errorf(
|
|
1539
1453
|
'The parameter "response" of RequestContext.constructor should be an instance of ServerResponse, but %v was given.',
|
|
1540
1454
|
response
|
|
1541
1455
|
);
|
|
@@ -1548,6 +1462,149 @@ var RequestContext = _RequestContext;
|
|
|
1548
1462
|
|
|
1549
1463
|
// src/trie-router.js
|
|
1550
1464
|
var import_js_service5 = require("@e22m4u/js-service");
|
|
1465
|
+
var import_http4 = require("http");
|
|
1466
|
+
|
|
1467
|
+
// src/senders/data-sender.js
|
|
1468
|
+
var import_js_format20 = require("@e22m4u/js-format");
|
|
1469
|
+
var _DataSender = class _DataSender extends DebuggableService {
|
|
1470
|
+
/**
|
|
1471
|
+
* Send.
|
|
1472
|
+
*
|
|
1473
|
+
* @param {import('http').ServerResponse} res
|
|
1474
|
+
* @param {*} data
|
|
1475
|
+
* @returns {undefined}
|
|
1476
|
+
*/
|
|
1477
|
+
send(res, data) {
|
|
1478
|
+
const debug = this.getDebuggerFor(this.send);
|
|
1479
|
+
if (data === res || res.headersSent) {
|
|
1480
|
+
debug(
|
|
1481
|
+
"Response sending was skipped because its headers where sent already."
|
|
1482
|
+
);
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
if (data == null) {
|
|
1486
|
+
res.statusCode = 204;
|
|
1487
|
+
res.end();
|
|
1488
|
+
debug("The empty response was sent.");
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (isReadableStream(data)) {
|
|
1492
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
1493
|
+
data.pipe(res);
|
|
1494
|
+
debug("The stream response was sent.");
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
let debugMsg;
|
|
1498
|
+
switch (typeof data) {
|
|
1499
|
+
case "object":
|
|
1500
|
+
case "boolean":
|
|
1501
|
+
case "number":
|
|
1502
|
+
if (Buffer.isBuffer(data)) {
|
|
1503
|
+
res.setHeader("content-type", "application/octet-stream");
|
|
1504
|
+
debugMsg = "The Buffer was sent as binary data.";
|
|
1505
|
+
} else {
|
|
1506
|
+
res.setHeader("content-type", "application/json");
|
|
1507
|
+
debugMsg = (0, import_js_format20.format)("The %v was sent as JSON.", typeof data);
|
|
1508
|
+
data = JSON.stringify(data);
|
|
1509
|
+
}
|
|
1510
|
+
break;
|
|
1511
|
+
default:
|
|
1512
|
+
res.setHeader("content-type", "text/plain");
|
|
1513
|
+
debugMsg = "The response data was sent as plain text.";
|
|
1514
|
+
data = String(data);
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
res.end(data);
|
|
1518
|
+
debug(debugMsg);
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
__name(_DataSender, "DataSender");
|
|
1522
|
+
var DataSender = _DataSender;
|
|
1523
|
+
|
|
1524
|
+
// src/senders/error-sender.js
|
|
1525
|
+
var import_util = require("util");
|
|
1526
|
+
var import_statuses = __toESM(require("statuses"), 1);
|
|
1527
|
+
var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
|
|
1528
|
+
var _ErrorSender = class _ErrorSender extends DebuggableService {
|
|
1529
|
+
/**
|
|
1530
|
+
* Handle.
|
|
1531
|
+
*
|
|
1532
|
+
* @param {import('http').IncomingMessage} req
|
|
1533
|
+
* @param {import('http').ServerResponse} res
|
|
1534
|
+
* @param {Error} error
|
|
1535
|
+
* @returns {undefined}
|
|
1536
|
+
*/
|
|
1537
|
+
send(req, res, error) {
|
|
1538
|
+
const debug = this.getDebuggerFor(this.send);
|
|
1539
|
+
let safeError = {};
|
|
1540
|
+
if (error) {
|
|
1541
|
+
if (typeof error === "object") {
|
|
1542
|
+
safeError = error;
|
|
1543
|
+
} else {
|
|
1544
|
+
safeError = { message: String(error) };
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const statusCode = error.statusCode || error.status || 500;
|
|
1548
|
+
const body = { error: {} };
|
|
1549
|
+
if (safeError.message && typeof safeError.message === "string") {
|
|
1550
|
+
body.error.message = safeError.message;
|
|
1551
|
+
} else {
|
|
1552
|
+
body.error.message = (0, import_statuses.default)(statusCode);
|
|
1553
|
+
}
|
|
1554
|
+
EXPOSED_ERROR_PROPERTIES.forEach((name) => {
|
|
1555
|
+
if (name in safeError) body.error[name] = safeError[name];
|
|
1556
|
+
});
|
|
1557
|
+
const requestData = {
|
|
1558
|
+
url: req.url,
|
|
1559
|
+
method: req.method,
|
|
1560
|
+
headers: req.headers
|
|
1561
|
+
};
|
|
1562
|
+
const inspectOptions = {
|
|
1563
|
+
showHidden: false,
|
|
1564
|
+
depth: null,
|
|
1565
|
+
colors: true,
|
|
1566
|
+
compact: false
|
|
1567
|
+
};
|
|
1568
|
+
console.warn((0, import_util.inspect)(requestData, inspectOptions));
|
|
1569
|
+
console.warn((0, import_util.inspect)(body, inspectOptions));
|
|
1570
|
+
if (error.stack) {
|
|
1571
|
+
console.log(error.stack);
|
|
1572
|
+
} else {
|
|
1573
|
+
console.error(error);
|
|
1574
|
+
}
|
|
1575
|
+
res.statusCode = statusCode;
|
|
1576
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1577
|
+
res.end(JSON.stringify(body, null, 2), "utf-8");
|
|
1578
|
+
debug(
|
|
1579
|
+
"The %s error was sent for the request %s %v.",
|
|
1580
|
+
statusCode,
|
|
1581
|
+
req.method,
|
|
1582
|
+
getRequestPathname(req)
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Send 404.
|
|
1587
|
+
*
|
|
1588
|
+
* @param {import('http').IncomingMessage} req
|
|
1589
|
+
* @param {import('http').ServerResponse} res
|
|
1590
|
+
* @returns {undefined}
|
|
1591
|
+
*/
|
|
1592
|
+
send404(req, res) {
|
|
1593
|
+
const debug = this.getDebuggerFor(this.send404);
|
|
1594
|
+
res.statusCode = 404;
|
|
1595
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
1596
|
+
res.end("404 Not Found", "utf-8");
|
|
1597
|
+
debug(
|
|
1598
|
+
"The 404 error was sent for the request %s %v.",
|
|
1599
|
+
req.method,
|
|
1600
|
+
getRequestPathname(req)
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
__name(_ErrorSender, "ErrorSender");
|
|
1605
|
+
var ErrorSender = _ErrorSender;
|
|
1606
|
+
|
|
1607
|
+
// src/trie-router.js
|
|
1551
1608
|
var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
1552
1609
|
/**
|
|
1553
1610
|
* Define route.
|
|
@@ -1623,8 +1680,11 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
|
1623
1680
|
const { route, params } = resolved;
|
|
1624
1681
|
const container = new import_js_service5.ServiceContainer(this.container);
|
|
1625
1682
|
const context = new RequestContext(container, req, res);
|
|
1683
|
+
if (route.meta != null) {
|
|
1684
|
+
context.meta = cloneDeep(route.meta);
|
|
1685
|
+
}
|
|
1626
1686
|
container.set(RequestContext, context);
|
|
1627
|
-
container.set(
|
|
1687
|
+
container.set(import_http4.IncomingMessage, req);
|
|
1628
1688
|
container.set(import_http4.ServerResponse, res);
|
|
1629
1689
|
context.params = params;
|
|
1630
1690
|
let data;
|
|
@@ -1699,6 +1759,26 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
|
1699
1759
|
this.getService(HookRegistry).addHook(type, hook);
|
|
1700
1760
|
return this;
|
|
1701
1761
|
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Add pre-handler hook.
|
|
1764
|
+
*
|
|
1765
|
+
* @param {Function} hook
|
|
1766
|
+
* @returns {this}
|
|
1767
|
+
*/
|
|
1768
|
+
addPreHandler(hook) {
|
|
1769
|
+
this.getService(HookRegistry).addHook(RouterHookType.PRE_HANDLER, hook);
|
|
1770
|
+
return this;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Add post-handler hook.
|
|
1774
|
+
*
|
|
1775
|
+
* @param {Function} hook
|
|
1776
|
+
* @returns {this}
|
|
1777
|
+
*/
|
|
1778
|
+
addPostHandler(hook) {
|
|
1779
|
+
this.getService(HookRegistry).addHook(RouterHookType.POST_HANDLER, hook);
|
|
1780
|
+
return this;
|
|
1781
|
+
}
|
|
1702
1782
|
};
|
|
1703
1783
|
__name(_TrieRouter, "TrieRouter");
|
|
1704
1784
|
var TrieRouter = _TrieRouter;
|
|
@@ -1723,6 +1803,7 @@ var TrieRouter = _TrieRouter;
|
|
|
1723
1803
|
RouterOptions,
|
|
1724
1804
|
TrieRouter,
|
|
1725
1805
|
UNPARSABLE_MEDIA_TYPES,
|
|
1806
|
+
cloneDeep,
|
|
1726
1807
|
createCookiesString,
|
|
1727
1808
|
createDebugger,
|
|
1728
1809
|
createError,
|