@e22m4u/js-trie-router 0.6.4 → 0.6.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 CHANGED
@@ -16,10 +16,10 @@ HTTP маршрутизатор для Node.js на основе
16
16
  ## Содержание
17
17
 
18
18
  - [Установка](#установка)
19
- - [Расширения](#расширения)
20
19
  - [Использование](#использование)
21
20
  - [Контекст запроса](#контекст-запроса)
22
21
  - [Отправка ответа](#отправка-ответа)
22
+ - [Жизненный цикл](#жизненный-цикл)
23
23
  - [Хуки маршрута](#хуки-маршрута)
24
24
  - [Глобальные хуки](#глобальные-хуки)
25
25
  - [Метаданные маршрута](#метаданные-маршрута)
@@ -178,6 +178,55 @@ router.defineRoute({
178
178
  });
179
179
  ```
180
180
 
181
+ ### Жизненный цикл
182
+
183
+ Для понимания того, как маршрутизатор обрабатывает входящий запрос, ниже
184
+ представлен порядок выполнения внутреннего конвейера и всех доступных хуков.
185
+
186
+ **На этапе запуска приложения**
187
+
188
+ 1. [Глобальные хуки `onDefineRoute`](#ondefineroute).
189
+ \- Вызываются при регистрации маршрута.
190
+
191
+ **При получении входящего HTTP-запроса**
192
+
193
+ 1. [Глобальные хуки `onRequest`](#onrequest).
194
+ \- Вызываются до поиска маршрута и разбора входящих данных.
195
+
196
+ 2. Поиск маршрута.
197
+ \- Если маршрут не найден, отправляется ответ `404`, а дальнейшая обработка
198
+ прерывается.
199
+
200
+ 3. Создание `RequestContext` и парсинг.
201
+ \- Создается экземпляр контекста, разбирается строка запроса, заголовки
202
+ и извлекается тело запроса.
203
+
204
+ 4. [Глобальные хуки `preHandler`](#prehandler-глобальный).
205
+ \- Вызываются последовательно. Если хук возвращает значение или отправляет
206
+ ответ, цикл прерывается.
207
+
208
+ 5. [Хуки маршрута `preHandler`](#prehandler-для-маршрута).
209
+ \- Вызываются для конкретного маршрута. Правила прерывания такие же,
210
+ как у глобальных хуков.
211
+
212
+ 6. Основной обработчик (функция `handler`).
213
+ \- Обработчик запроса вызывается для формирования ответа сервера.
214
+
215
+ 7. [Хуки маршрута `postHandler`](#posthandler-для-маршрута).
216
+ \- Получают результат обработчика и могут трансформировать его перед
217
+ отправкой.
218
+
219
+ 8. [Глобальные хуки `postHandler`](#posthandler-глобальный).
220
+ \- Завершают цепочку трансформации ответа.
221
+
222
+ 9. Отправка ответа.
223
+ \- Маршрутизатор сериализует итоговые данные, устанавливает заголовки
224
+ и отправляет клиенту.
225
+
226
+ Процесс обработки запроса обернут в глобальный `try/catch` блок. Любая ошибка,
227
+ выброшенная на любом этапе (в хуках, при разборе тела или в самом обработчике),
228
+ будет перехвачена и передана в `ErrorSender` для формирования ответа с ошибкой.
229
+
181
230
  ### Хуки маршрута
182
231
 
183
232
  Определение маршрута методом `defineRoute` позволяет задать хуки
@@ -367,6 +416,7 @@ router.defineRoute({
367
416
  которые выполняются на различных этапах жизненного цикла.
368
417
 
369
418
  - [`onDefineRoute`](#ondefineroute) выполняется перед регистрацией маршрута;
419
+ - [`onRequest`](#onrequest) выполняется при получении входящего HTTP-запроса;
370
420
  - [`preHandler`](#prehandler-глобальный) выполняется перед вызовом обработчика каждого маршрута;
371
421
  - [`postHandler`](#posthandler-глобальный) выполняется после вызова обработчика каждого маршрута;
372
422
 
@@ -413,6 +463,51 @@ router.addHook(RouterHookType.ON_DEFINE_ROUTE, (routeDef, container) => {
413
463
  // router.defineRoute(...)
414
464
  ```
415
465
 
466
+ #### onRequest
467
+
468
+ Глобальный хук `onRequest` выполняется самым первым при получении входящего
469
+ запроса. В этот момент маршрутизатор еще не начал поиск подходящего маршрута,
470
+ не разобрал тело запроса и не создавал `RequestContext` (контекст запроса).
471
+
472
+ Хук принимает три аргумента:
473
+
474
+ - `request: IncomingMessage` нативный экземпляр запроса;
475
+ - `response: ServerResponse` нативный экземпляр ответа;
476
+ - `container: ServiceContainer` сервис-контейнер приложения;
477
+
478
+ Это идеальное место для установки общих CORS-заголовков, раннего логирования
479
+ или блокировки нежелательных запросов (например, по IP).
480
+
481
+ ```js
482
+ router.addHook(RouterHookType.ON_REQUEST, (req, res, container) => {
483
+ // логирование входящего запроса до начала любой обработки
484
+ console.log(`[Incoming]: ${req.method} ${req.url}`);
485
+ // установка глобальных заголовков
486
+ res.setHeader('X-Powered-By', 'TrieRouter');
487
+ });
488
+ ```
489
+
490
+ Если хук отправляет ответ клиенту (например, вызывает `res.end()`) или явно
491
+ возвращает логическое значение `true`, маршрутизатор немедленно прерывает
492
+ обработку запроса. Поиск маршрута, чтение тела и вызов остальных хуков
493
+ выполнены не будут.
494
+
495
+ ```js
496
+ router.addHook(RouterHookType.ON_REQUEST, (req, res) => {
497
+ const clientIp = req.socket.remoteAddress;
498
+ // пример блокировки запроса на самом раннем этапе
499
+ if (clientIp === '192.168.0.100') {
500
+ res.statusCode = 403;
501
+ res.end('Access denied');
502
+ return true; // прерывает дальнейшее выполнение
503
+ }
504
+ });
505
+ ```
506
+
507
+ Если хук `onRequest` возвращает значение, оно обязано быть логическим типом
508
+ или `undefined`. Также допускается `Promise`, разрешающийся этими значениями.
509
+ Попытка вернуть строку или объект приведет к выбросу ошибки.
510
+
416
511
  #### preHandler (глобальный)
417
512
 
418
513
  Глобальный хук `preHandler` вызывается перед каждым обработчиком маршрута,
@@ -38,7 +38,6 @@ __export(index_exports, {
38
38
  EXPOSED_ERROR_PROPERTIES: () => EXPOSED_ERROR_PROPERTIES,
39
39
  ErrorSender: () => ErrorSender,
40
40
  HttpMethod: () => HttpMethod,
41
- METHODS_WITH_BODY: () => METHODS_WITH_BODY,
42
41
  QueryParser: () => QueryParser,
43
42
  ROOT_PATH: () => ROOT_PATH,
44
43
  ROUTER_HOOK_TYPES: () => ROUTER_HOOK_TYPES,
@@ -51,7 +50,6 @@ __export(index_exports, {
51
50
  RouterHookType: () => RouterHookType,
52
51
  RouterOptions: () => RouterOptions,
53
52
  TrieRouter: () => TrieRouter,
54
- UNPARSABLE_MEDIA_TYPES: () => UNPARSABLE_MEDIA_TYPES,
55
53
  cloneDeep: () => cloneDeep,
56
54
  createCookieString: () => createCookieString,
57
55
  createError: () => createError,
@@ -60,6 +58,7 @@ __export(index_exports, {
60
58
  createRouteMock: () => createRouteMock,
61
59
  fetchRequestBody: () => fetchRequestBody,
62
60
  getRequestPathname: () => getRequestPathname,
61
+ hasRequestBody: () => hasRequestBody,
63
62
  isPromise: () => isPromise,
64
63
  isReadableStream: () => isReadableStream,
65
64
  isResponseSent: () => isResponseSent,
@@ -219,6 +218,18 @@ function isResponseSent(response) {
219
218
  }
220
219
  __name(isResponseSent, "isResponseSent");
221
220
 
221
+ // src/utils/has-request-body.js
222
+ function hasRequestBody(request) {
223
+ if (request.headers["transfer-encoding"] !== void 0) {
224
+ return true;
225
+ }
226
+ if (!isNaN(request.headers["content-length"]) && request.headers["content-length"] !== "0") {
227
+ return true;
228
+ }
229
+ return false;
230
+ }
231
+ __name(hasRequestBody, "hasRequestBody");
232
+
222
233
  // src/utils/create-route-mock.js
223
234
  function createRouteMock(options = {}) {
224
235
  return new Route({
@@ -641,7 +652,7 @@ function createRequestHeaders(host, secure, body, cookies, encoding, headers) {
641
652
  obj["content-type"] = "application/json";
642
653
  }
643
654
  }
644
- if (body != null && obj["content-length"] == null) {
655
+ if (body != null && obj["transfer-encoding"] == null && obj["content-length"] == null) {
645
656
  if (typeof body === "string") {
646
657
  const length = Buffer.byteLength(body, encoding);
647
658
  obj["content-length"] = String(length);
@@ -964,9 +975,10 @@ var import_js_format12 = require("@e22m4u/js-format");
964
975
  // src/hooks/router-hook-registry.js
965
976
  var import_js_format11 = require("@e22m4u/js-format");
966
977
  var RouterHookType = {
978
+ ON_DEFINE_ROUTE: "onDefineRoute",
979
+ ON_REQUEST: "onRequest",
967
980
  PRE_HANDLER: "preHandler",
968
- POST_HANDLER: "postHandler",
969
- ON_DEFINE_ROUTE: "onDefineRoute"
981
+ POST_HANDLER: "postHandler"
970
982
  };
971
983
  var ROUTER_HOOK_TYPES = Object.values(RouterHookType);
972
984
  var _RouterHookRegistry = class _RouterHookRegistry {
@@ -1057,6 +1069,110 @@ var RouterHookRegistry = _RouterHookRegistry;
1057
1069
 
1058
1070
  // src/hooks/router-hook-invoker.js
1059
1071
  var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
1072
+ /**
1073
+ * Invoke on-request hooks.
1074
+ *
1075
+ * @param {IncomingMessage} request
1076
+ * @param {ServerResponse} response
1077
+ * @returns {Promise<boolean|undefined>|boolean|undefined}
1078
+ */
1079
+ invokeOnRequestHooks(request, response) {
1080
+ if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
1081
+ throw new import_js_format12.InvalidArgumentError(
1082
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
1083
+ request
1084
+ );
1085
+ }
1086
+ if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
1087
+ throw new import_js_format12.InvalidArgumentError(
1088
+ 'Parameter "response" must be an instance of ServerResponse, but %v was given.',
1089
+ response
1090
+ );
1091
+ }
1092
+ if (isResponseSent(response)) {
1093
+ return;
1094
+ }
1095
+ const hooks = this.getService(RouterHookRegistry).getHooks(
1096
+ RouterHookType.ON_REQUEST
1097
+ );
1098
+ let isInterrupted = void 0;
1099
+ for (let i = 0; i < hooks.length; i++) {
1100
+ const hook = hooks[i];
1101
+ const result = hook(request, response, this.container);
1102
+ if (isResponseSent(response)) {
1103
+ return;
1104
+ }
1105
+ if (result !== void 0) {
1106
+ if (isPromise(result)) {
1107
+ return this._continueOnRequestHooksInvocationAsync(
1108
+ hooks,
1109
+ i + 1,
1110
+ result,
1111
+ request,
1112
+ response
1113
+ );
1114
+ }
1115
+ if (result === true) {
1116
+ isInterrupted = result;
1117
+ break;
1118
+ }
1119
+ if (result !== false) {
1120
+ throw new import_js_format12.InvalidArgumentError(
1121
+ 'Hook "onRequest" must return undefined or a Boolean, but %v was given.',
1122
+ result
1123
+ );
1124
+ }
1125
+ }
1126
+ }
1127
+ return isInterrupted;
1128
+ }
1129
+ /**
1130
+ * Continue on-request hooks invocation async.
1131
+ *
1132
+ * @param {Function[]} hooks
1133
+ * @param {number} startIndex
1134
+ * @param {Promise} initialPromise
1135
+ * @param {IncomingMessage} request
1136
+ * @param {ServerResponse} response
1137
+ * @returns {Promise<boolean|undefined>}
1138
+ */
1139
+ async _continueOnRequestHooksInvocationAsync(hooks, startIndex, initialPromise, request, response) {
1140
+ let result = await initialPromise;
1141
+ if (isResponseSent(response)) {
1142
+ return;
1143
+ }
1144
+ if (result !== void 0) {
1145
+ if (result === true) {
1146
+ return result;
1147
+ }
1148
+ if (result !== false) {
1149
+ throw new import_js_format12.InvalidArgumentError(
1150
+ 'Hook "onRequest" must return undefined or a Boolean, but %v was given.',
1151
+ result
1152
+ );
1153
+ }
1154
+ }
1155
+ let isInterrupted = void 0;
1156
+ for (let i = startIndex; i < hooks.length; i++) {
1157
+ result = await hooks[i](request, response, this.container);
1158
+ if (isResponseSent(response)) {
1159
+ return;
1160
+ }
1161
+ if (result !== void 0) {
1162
+ if (result === true) {
1163
+ isInterrupted = result;
1164
+ break;
1165
+ }
1166
+ if (result !== false) {
1167
+ throw new import_js_format12.InvalidArgumentError(
1168
+ 'Hook "onRequest" must return undefined or a Boolean, but %v was given.',
1169
+ result
1170
+ );
1171
+ }
1172
+ }
1173
+ }
1174
+ return isInterrupted;
1175
+ }
1060
1176
  /**
1061
1177
  * Последовательно вызывает глобальные хуки и хуки маршрута типа "preHandler",
1062
1178
  * пока один из них не вернет отличное от undefined значение или не отправит
@@ -1453,8 +1569,6 @@ var RouterOptions = _RouterOptions;
1453
1569
 
1454
1570
  // src/parsers/body-parser.js
1455
1571
  var import_js_format15 = require("@e22m4u/js-format");
1456
- var METHODS_WITH_BODY = ["POST", "PUT", "PATCH", "DELETE"];
1457
- var UNPARSABLE_MEDIA_TYPES = ["multipart/form-data"];
1458
1572
  var _BodyParser = class _BodyParser extends DebuggableService {
1459
1573
  /**
1460
1574
  * Parsers.
@@ -1485,7 +1599,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1485
1599
  parser
1486
1600
  );
1487
1601
  }
1488
- this._parsers[mediaType] = parser;
1602
+ this._parsers[mediaType.toLowerCase()] = parser;
1489
1603
  return this;
1490
1604
  }
1491
1605
  /**
@@ -1501,7 +1615,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1501
1615
  mediaType
1502
1616
  );
1503
1617
  }
1504
- return Boolean(this._parsers[mediaType]);
1618
+ return Boolean(this._parsers[mediaType.toLowerCase()]);
1505
1619
  }
1506
1620
  /**
1507
1621
  * Get parser.
@@ -1516,7 +1630,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1516
1630
  mediaType
1517
1631
  );
1518
1632
  }
1519
- const parser = this._parsers[mediaType];
1633
+ const parser = this._parsers[mediaType.toLowerCase()];
1520
1634
  if (!parser) {
1521
1635
  throw new import_js_format15.InvalidArgumentError(
1522
1636
  "Media type %v does not have a parser.",
@@ -1538,7 +1652,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1538
1652
  mediaType
1539
1653
  );
1540
1654
  }
1541
- delete this._parsers[mediaType];
1655
+ delete this._parsers[mediaType.toLowerCase()];
1542
1656
  return this;
1543
1657
  }
1544
1658
  /**
@@ -1554,17 +1668,11 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1554
1668
  request.method.toUpperCase(),
1555
1669
  getRequestPathname(request)
1556
1670
  );
1557
- if (!METHODS_WITH_BODY.includes(request.method.toUpperCase())) {
1558
- debug(
1559
- "Skipping body parsing for the %s method.",
1560
- request.method.toUpperCase()
1561
- );
1671
+ if (!hasRequestBody(request)) {
1672
+ debug("Skipping body parsing because no body is provided.");
1562
1673
  return;
1563
1674
  }
1564
- const contentType = (request.headers["content-type"] || "").replace(
1565
- /^([^;]+);.*$/,
1566
- "$1"
1567
- );
1675
+ const contentType = request.headers["content-type"];
1568
1676
  if (!contentType) {
1569
1677
  debug("Skipping body parsing because no content type is provided.");
1570
1678
  return;
@@ -1576,17 +1684,10 @@ var _BodyParser = class _BodyParser extends DebuggableService {
1576
1684
  'Unable to parse the "content-type" header.'
1577
1685
  );
1578
1686
  }
1579
- const parser = this._parsers[mediaType];
1687
+ const parser = this._parsers[mediaType.toLowerCase()];
1580
1688
  if (!parser) {
1581
- if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
1582
- debug("Skipping body parsing for the media type %v.", mediaType);
1583
- return;
1584
- }
1585
- throw createError(
1586
- import_http_errors2.default.UnsupportedMediaType,
1587
- "Media type %v is not supported.",
1588
- mediaType
1589
- );
1689
+ debug("No body parser for the media type %v.", mediaType);
1690
+ return;
1590
1691
  }
1591
1692
  const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
1592
1693
  debug("Fetching a request body.");
@@ -1610,7 +1711,7 @@ function parseJsonBody(input) {
1610
1711
  try {
1611
1712
  return JSON.parse(input);
1612
1713
  } catch (error) {
1613
- throw createError(import_http_errors2.default.BadRequest, error.message);
1714
+ throw new import_http_errors2.default.BadRequest(error.message);
1614
1715
  }
1615
1716
  }
1616
1717
  __name(parseJsonBody, "parseJsonBody");
@@ -2318,40 +2419,66 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2318
2419
  async _handleRequest(request, response) {
2319
2420
  const debug = this.getDebuggerFor(this._handleRequest);
2320
2421
  const requestPath = getRequestPathname(request);
2321
- const routeRegistry = this.getService(RouteRegistry);
2322
2422
  debug("Handling an incoming request %s %v.", request.method, requestPath);
2323
- const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
2324
- if (!resolved) {
2325
- if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
2326
- const allowedMethods = routeRegistry.getAllowedMethodsForRequestPath(requestPath);
2327
- if (allowedMethods.length > 0) {
2328
- debug("Auto-handling OPTIONS request.");
2329
- if (!allowedMethods.includes("OPTIONS")) {
2330
- allowedMethods.push("OPTIONS");
2331
- }
2332
- const allowHeader = allowedMethods.join(", ");
2333
- response.statusCode = 204;
2334
- response.setHeader("Allow", allowHeader);
2335
- response.end();
2423
+ try {
2424
+ if (isResponseSent(response)) {
2425
+ debug("Response has been sent before handling.");
2426
+ return;
2427
+ }
2428
+ const hookInvoker = this.getService(RouterHookInvoker);
2429
+ const onRequestHooks = this.getService(RouterHookRegistry).getHooks(
2430
+ RouterHookType.ON_REQUEST
2431
+ );
2432
+ if (onRequestHooks.length) {
2433
+ debug('Invoking "onRequest" hooks, %v hook(s) found.');
2434
+ let shouldIgnoreRequest = hookInvoker.invokeOnRequestHooks(
2435
+ request,
2436
+ response
2437
+ );
2438
+ if (isPromise(shouldIgnoreRequest)) {
2439
+ shouldIgnoreRequest = await shouldIgnoreRequest;
2440
+ }
2441
+ if (isResponseSent(response)) {
2442
+ debug('Response has been sent by "onRequest" hook.');
2443
+ return;
2444
+ }
2445
+ if (shouldIgnoreRequest === true) {
2446
+ debug('Response handling was interrupted by "onRequest" hook.');
2336
2447
  return;
2337
2448
  }
2338
2449
  }
2339
- debug(
2340
- "No route found for the request %s %v.",
2341
- request.method,
2342
- requestPath
2343
- );
2344
- this.getService(ErrorSender).send404(request, response);
2345
- } else {
2346
- const { route, params } = resolved;
2347
- const container = new import_js_service4.ServiceContainer(this.container);
2348
- const context = new RequestContext(container, request, response, route);
2349
- container.set(RequestContext, context);
2350
- container.set(import_http4.IncomingMessage, request);
2351
- container.set(import_http4.ServerResponse, response);
2352
- context.params = params;
2353
- let data;
2354
- try {
2450
+ const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
2451
+ if (!resolved) {
2452
+ if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
2453
+ const allowedMethods = this.getService(RouteRegistry).getAllowedMethodsForRequestPath(
2454
+ requestPath
2455
+ );
2456
+ if (allowedMethods.length > 0) {
2457
+ debug("Auto-handling OPTIONS request.");
2458
+ if (!allowedMethods.includes("OPTIONS")) {
2459
+ allowedMethods.push("OPTIONS");
2460
+ }
2461
+ const allowHeader = allowedMethods.join(", ");
2462
+ response.statusCode = 204;
2463
+ response.setHeader("Allow", allowHeader);
2464
+ response.end();
2465
+ return;
2466
+ }
2467
+ }
2468
+ debug(
2469
+ "No route found for the request %s %v.",
2470
+ request.method,
2471
+ requestPath
2472
+ );
2473
+ this.getService(ErrorSender).send404(request, response);
2474
+ } else {
2475
+ const { route, params } = resolved;
2476
+ const container = new import_js_service4.ServiceContainer(this.container);
2477
+ const context = new RequestContext(container, request, response, route);
2478
+ container.set(RequestContext, context);
2479
+ container.set(import_http4.IncomingMessage, request);
2480
+ container.set(import_http4.ServerResponse, response);
2481
+ context.params = params;
2355
2482
  const reqDataOrPromise = this.getService(RequestParser).parse(request);
2356
2483
  if (isPromise(reqDataOrPromise)) {
2357
2484
  const reqData = await reqDataOrPromise;
@@ -2359,8 +2486,7 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2359
2486
  } else {
2360
2487
  Object.assign(context, reqDataOrPromise);
2361
2488
  }
2362
- const hookInvoker = this.getService(RouterHookInvoker);
2363
- data = hookInvoker.invokePreHandlerHooks(context);
2489
+ let data = hookInvoker.invokePreHandlerHooks(context);
2364
2490
  if (isPromise(data)) {
2365
2491
  data = await data;
2366
2492
  }
@@ -2371,6 +2497,10 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2371
2497
  data = await data;
2372
2498
  }
2373
2499
  }
2500
+ if (isResponseSent(response)) {
2501
+ debug("Response has been sent by the route handler.");
2502
+ return;
2503
+ }
2374
2504
  let postHandlerData = hookInvoker.invokePostHandlerHooks(
2375
2505
  context,
2376
2506
  data
@@ -2378,17 +2508,24 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2378
2508
  if (isPromise(postHandlerData)) {
2379
2509
  postHandlerData = await postHandlerData;
2380
2510
  }
2511
+ if (isResponseSent(response)) {
2512
+ debug('Response has been sent by "postHandler" hook.');
2513
+ return;
2514
+ }
2381
2515
  if (postHandlerData !== void 0) {
2382
2516
  data = postHandlerData;
2383
2517
  }
2518
+ } else {
2519
+ debug('Response has been sent by "preHandler" hook.');
2520
+ return;
2521
+ }
2522
+ if (!isResponseSent(response)) {
2523
+ this.getService(DataSender).send(response, data);
2384
2524
  }
2385
- } catch (error) {
2386
- this.getService(ErrorSender).send(request, response, error);
2387
- return;
2388
- }
2389
- if (!isResponseSent(response)) {
2390
- this.getService(DataSender).send(response, data);
2391
2525
  }
2526
+ } catch (error) {
2527
+ this.getService(ErrorSender).send(request, response, error);
2528
+ return;
2392
2529
  }
2393
2530
  }
2394
2531
  /**
@@ -2424,7 +2561,6 @@ var TrieRouter = _TrieRouter;
2424
2561
  EXPOSED_ERROR_PROPERTIES,
2425
2562
  ErrorSender,
2426
2563
  HttpMethod,
2427
- METHODS_WITH_BODY,
2428
2564
  QueryParser,
2429
2565
  ROOT_PATH,
2430
2566
  ROUTER_HOOK_TYPES,
@@ -2437,7 +2573,6 @@ var TrieRouter = _TrieRouter;
2437
2573
  RouterHookType,
2438
2574
  RouterOptions,
2439
2575
  TrieRouter,
2440
- UNPARSABLE_MEDIA_TYPES,
2441
2576
  cloneDeep,
2442
2577
  createCookieString,
2443
2578
  createError,
@@ -2446,6 +2581,7 @@ var TrieRouter = _TrieRouter;
2446
2581
  createRouteMock,
2447
2582
  fetchRequestBody,
2448
2583
  getRequestPathname,
2584
+ hasRequestBody,
2449
2585
  isPromise,
2450
2586
  isReadableStream,
2451
2587
  isResponseSent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/js-trie-router",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
5
5
  "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
6
6
  "license": "MIT",
@@ -1,11 +1,24 @@
1
1
  import {ValueOrPromise} from '../types.js';
2
2
  import {RequestContext} from '../request-context.js';
3
+ import {IncomingMessage, ServerResponse} from 'http';
3
4
  import {DebuggableService} from '../debuggable-service.js';
4
5
 
5
6
  /**
6
7
  * Router hook invoker.
7
8
  */
8
9
  export declare class RouterHookInvoker extends DebuggableService {
10
+ /**
11
+ * Invoke on-request hooks.
12
+ *
13
+ * @param {IncomingMessage} request
14
+ * @param {ServerResponse} response
15
+ * @returns {Promise<boolean|undefined>|boolean|undefined}
16
+ */
17
+ invokeOnRequestHooks(
18
+ request: IncomingMessage,
19
+ response: ServerResponse,
20
+ ): Promise<boolean | undefined> | boolean | undefined;
21
+
9
22
  /**
10
23
  * Invoke pre-handler hooks.
11
24
  *