@e22m4u/js-trie-router 0.6.4 → 0.6.5

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,56 @@ 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
+
197
+ 2. Поиск маршрута
198
+ \- Маршрутизатор ищет совпадение пути. Если маршрут не найден,
199
+ отправляется ответ `404`, а дальнейшая обработка прекращается.
200
+
201
+ 3. Создание `RequestContext` и парсинг
202
+ \- Создается экземпляр контекста, разбирается строка запроса, заголовки
203
+ и извлекается тело запроса.
204
+
205
+ 4. [Глобальные хуки `preHandler`](#prehandler-глобальный)
206
+ \- Вызываются последовательно. Если хук возвращает значение
207
+ или отправляет ответ, цикл прерывается.
208
+
209
+ 5. [Хуки маршрута `preHandler`](#prehandler-для-маршрута)
210
+ \- Вызываются последовательно для конкретного маршрута. Правила
211
+ прерывания такие же, как у одноименных глобальных хуков.
212
+
213
+ 6. Основной обработчик (функция `handler`)
214
+ \- Вызывается для формирования ответа сервера.
215
+
216
+ 7. [Хуки маршрута `postHandler`](#posthandler-для-маршрута)
217
+ \- Вызываются последовательно. Получают результат работы обработчика
218
+ (или из `preHandler`) и могут трансформировать его перед отправкой.
219
+
220
+ 8. [Глобальные хуки `postHandler`](#posthandler-глобальный)
221
+ \- Завершают цепочку трансформации ответа.
222
+
223
+ 9. Отправка ответа.
224
+ \- Маршрутизатор автоматически форматирует и отправляет итоговые данные
225
+ клиенту (если ответ не был отправлен ранее вручную).
226
+
227
+ Процесс обработки запроса обернут в глобальный `try/catch` блок. Любая ошибка,
228
+ выброшенная на любом этапе (в хуках, при разборе тела или в самом обработчике),
229
+ будет перехвачена и передана в `ErrorSender` для формирования ответа с ошибкой.
230
+
181
231
  ### Хуки маршрута
182
232
 
183
233
  Определение маршрута методом `defineRoute` позволяет задать хуки
@@ -367,6 +417,7 @@ router.defineRoute({
367
417
  которые выполняются на различных этапах жизненного цикла.
368
418
 
369
419
  - [`onDefineRoute`](#ondefineroute) выполняется перед регистрацией маршрута;
420
+ - [`onRequest`](#onrequest) выполняется при получении входящего HTTP-запроса;
370
421
  - [`preHandler`](#prehandler-глобальный) выполняется перед вызовом обработчика каждого маршрута;
371
422
  - [`postHandler`](#posthandler-глобальный) выполняется после вызова обработчика каждого маршрута;
372
423
 
@@ -413,6 +464,51 @@ router.addHook(RouterHookType.ON_DEFINE_ROUTE, (routeDef, container) => {
413
464
  // router.defineRoute(...)
414
465
  ```
415
466
 
467
+ #### onRequest
468
+
469
+ Глобальный хук `onRequest` выполняется самым первым при получении входящего
470
+ запроса. В этот момент маршрутизатор еще не начал поиск подходящего маршрута,
471
+ не разобрал тело запроса и не создавал `RequestContext` (контекст запроса).
472
+
473
+ Хук принимает три аргумента:
474
+
475
+ - `request: IncomingMessage` нативный экземпляр запроса;
476
+ - `response: ServerResponse` нативный экземпляр ответа;
477
+ - `container: ServiceContainer` сервис-контейнер приложения;
478
+
479
+ Это идеальное место для установки общих CORS-заголовков, раннего логирования
480
+ или блокировки нежелательных запросов (например, по IP).
481
+
482
+ ```js
483
+ router.addHook(RouterHookType.ON_REQUEST, (req, res, container) => {
484
+ // логирование входящего запроса до начала любой обработки
485
+ console.log(`[Incoming]: ${req.method} ${req.url}`);
486
+ // установка глобальных заголовков
487
+ res.setHeader('X-Powered-By', 'TrieRouter');
488
+ });
489
+ ```
490
+
491
+ Если хук отправляет ответ клиенту (например, вызывает `res.end()`) или явно
492
+ возвращает логическое значение `true`, маршрутизатор немедленно прерывает
493
+ обработку запроса. Поиск маршрута, чтение тела и вызов остальных хуков
494
+ выполнены не будут.
495
+
496
+ ```js
497
+ router.addHook(RouterHookType.ON_REQUEST, (req, res) => {
498
+ const clientIp = req.socket.remoteAddress;
499
+ // пример блокировки запроса на самом раннем этапе
500
+ if (clientIp === '192.168.0.100') {
501
+ res.statusCode = 403;
502
+ res.end('Access denied');
503
+ return true; // прерывает дальнейшее выполнение
504
+ }
505
+ });
506
+ ```
507
+
508
+ Если хук `onRequest` возвращает значение, оно обязано быть логическим типом
509
+ или `undefined`. Также допускается `Promise`, разрешающийся этими значениями.
510
+ Попытка вернуть строку или объект приведет к выбросу ошибки.
511
+
416
512
  #### preHandler (глобальный)
417
513
 
418
514
  Глобальный хук `preHandler` вызывается перед каждым обработчиком маршрута,
@@ -964,9 +964,10 @@ var import_js_format12 = require("@e22m4u/js-format");
964
964
  // src/hooks/router-hook-registry.js
965
965
  var import_js_format11 = require("@e22m4u/js-format");
966
966
  var RouterHookType = {
967
+ ON_DEFINE_ROUTE: "onDefineRoute",
968
+ ON_REQUEST: "onRequest",
967
969
  PRE_HANDLER: "preHandler",
968
- POST_HANDLER: "postHandler",
969
- ON_DEFINE_ROUTE: "onDefineRoute"
970
+ POST_HANDLER: "postHandler"
970
971
  };
971
972
  var ROUTER_HOOK_TYPES = Object.values(RouterHookType);
972
973
  var _RouterHookRegistry = class _RouterHookRegistry {
@@ -1057,6 +1058,110 @@ var RouterHookRegistry = _RouterHookRegistry;
1057
1058
 
1058
1059
  // src/hooks/router-hook-invoker.js
1059
1060
  var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
1061
+ /**
1062
+ * Invoke on-request hooks.
1063
+ *
1064
+ * @param {IncomingMessage} request
1065
+ * @param {ServerResponse} response
1066
+ * @returns {Promise<boolean|undefined>|boolean|undefined}
1067
+ */
1068
+ invokeOnRequestHooks(request, response) {
1069
+ if (!request || typeof request !== "object" || Array.isArray(request) || !isReadableStream(request)) {
1070
+ throw new import_js_format12.InvalidArgumentError(
1071
+ 'Parameter "request" must be an instance of IncomingMessage, but %v was given.',
1072
+ request
1073
+ );
1074
+ }
1075
+ if (!response || typeof response !== "object" || Array.isArray(response) || !isWritableStream(response)) {
1076
+ throw new import_js_format12.InvalidArgumentError(
1077
+ 'Parameter "response" must be an instance of ServerResponse, but %v was given.',
1078
+ response
1079
+ );
1080
+ }
1081
+ if (isResponseSent(response)) {
1082
+ return;
1083
+ }
1084
+ const hooks = this.getService(RouterHookRegistry).getHooks(
1085
+ RouterHookType.ON_REQUEST
1086
+ );
1087
+ let isInterrupted = void 0;
1088
+ for (let i = 0; i < hooks.length; i++) {
1089
+ const hook = hooks[i];
1090
+ const result = hook(request, response, this.container);
1091
+ if (isResponseSent(response)) {
1092
+ return;
1093
+ }
1094
+ if (result !== void 0) {
1095
+ if (isPromise(result)) {
1096
+ return this._continueOnRequestHooksInvocationAsync(
1097
+ hooks,
1098
+ i + 1,
1099
+ result,
1100
+ request,
1101
+ response
1102
+ );
1103
+ }
1104
+ if (result === true) {
1105
+ isInterrupted = result;
1106
+ break;
1107
+ }
1108
+ if (result !== false) {
1109
+ throw new import_js_format12.InvalidArgumentError(
1110
+ 'Hook "onRequest" must return undefined or a Boolean, but %v was given.',
1111
+ result
1112
+ );
1113
+ }
1114
+ }
1115
+ }
1116
+ return isInterrupted;
1117
+ }
1118
+ /**
1119
+ * Continue on-request hooks invocation async.
1120
+ *
1121
+ * @param {Function[]} hooks
1122
+ * @param {number} startIndex
1123
+ * @param {Promise} initialPromise
1124
+ * @param {IncomingMessage} request
1125
+ * @param {ServerResponse} response
1126
+ * @returns {Promise<boolean|undefined>}
1127
+ */
1128
+ async _continueOnRequestHooksInvocationAsync(hooks, startIndex, initialPromise, request, response) {
1129
+ let result = await initialPromise;
1130
+ if (isResponseSent(response)) {
1131
+ return;
1132
+ }
1133
+ if (result !== void 0) {
1134
+ if (result === true) {
1135
+ return result;
1136
+ }
1137
+ if (result !== false) {
1138
+ throw new import_js_format12.InvalidArgumentError(
1139
+ 'Hook "onRequest" must return undefined or a Boolean, but %v was given.',
1140
+ result
1141
+ );
1142
+ }
1143
+ }
1144
+ let isInterrupted = void 0;
1145
+ for (let i = startIndex; i < hooks.length; i++) {
1146
+ result = await hooks[i](request, response, this.container);
1147
+ if (isResponseSent(response)) {
1148
+ return;
1149
+ }
1150
+ if (result !== void 0) {
1151
+ if (result === true) {
1152
+ isInterrupted = result;
1153
+ break;
1154
+ }
1155
+ if (result !== false) {
1156
+ throw new import_js_format12.InvalidArgumentError(
1157
+ 'Hook "onRequest" must return undefined or a Boolean, but %v was given.',
1158
+ result
1159
+ );
1160
+ }
1161
+ }
1162
+ }
1163
+ return isInterrupted;
1164
+ }
1060
1165
  /**
1061
1166
  * Последовательно вызывает глобальные хуки и хуки маршрута типа "preHandler",
1062
1167
  * пока один из них не вернет отличное от undefined значение или не отправит
@@ -2318,40 +2423,66 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2318
2423
  async _handleRequest(request, response) {
2319
2424
  const debug = this.getDebuggerFor(this._handleRequest);
2320
2425
  const requestPath = getRequestPathname(request);
2321
- const routeRegistry = this.getService(RouteRegistry);
2322
2426
  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();
2427
+ try {
2428
+ if (isResponseSent(response)) {
2429
+ debug("Response has been sent before handling.");
2430
+ return;
2431
+ }
2432
+ const hookInvoker = this.getService(RouterHookInvoker);
2433
+ const onRequestHooks = this.getService(RouterHookRegistry).getHooks(
2434
+ RouterHookType.ON_REQUEST
2435
+ );
2436
+ if (onRequestHooks.length) {
2437
+ debug('Invoking "onRequest" hooks, %v hook(s) found.');
2438
+ let shouldIgnoreRequest = hookInvoker.invokeOnRequestHooks(
2439
+ request,
2440
+ response
2441
+ );
2442
+ if (isPromise(shouldIgnoreRequest)) {
2443
+ shouldIgnoreRequest = await shouldIgnoreRequest;
2444
+ }
2445
+ if (isResponseSent(response)) {
2446
+ debug('Response has been sent by "onRequest" hook.');
2447
+ return;
2448
+ }
2449
+ if (shouldIgnoreRequest === true) {
2450
+ debug('Response handling was interrupted by "onRequest" hook.');
2336
2451
  return;
2337
2452
  }
2338
2453
  }
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 {
2454
+ const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
2455
+ if (!resolved) {
2456
+ if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
2457
+ const allowedMethods = this.getService(RouteRegistry).getAllowedMethodsForRequestPath(
2458
+ requestPath
2459
+ );
2460
+ if (allowedMethods.length > 0) {
2461
+ debug("Auto-handling OPTIONS request.");
2462
+ if (!allowedMethods.includes("OPTIONS")) {
2463
+ allowedMethods.push("OPTIONS");
2464
+ }
2465
+ const allowHeader = allowedMethods.join(", ");
2466
+ response.statusCode = 204;
2467
+ response.setHeader("Allow", allowHeader);
2468
+ response.end();
2469
+ return;
2470
+ }
2471
+ }
2472
+ debug(
2473
+ "No route found for the request %s %v.",
2474
+ request.method,
2475
+ requestPath
2476
+ );
2477
+ this.getService(ErrorSender).send404(request, response);
2478
+ } else {
2479
+ const { route, params } = resolved;
2480
+ const container = new import_js_service4.ServiceContainer(this.container);
2481
+ const context = new RequestContext(container, request, response, route);
2482
+ container.set(RequestContext, context);
2483
+ container.set(import_http4.IncomingMessage, request);
2484
+ container.set(import_http4.ServerResponse, response);
2485
+ context.params = params;
2355
2486
  const reqDataOrPromise = this.getService(RequestParser).parse(request);
2356
2487
  if (isPromise(reqDataOrPromise)) {
2357
2488
  const reqData = await reqDataOrPromise;
@@ -2359,8 +2490,7 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2359
2490
  } else {
2360
2491
  Object.assign(context, reqDataOrPromise);
2361
2492
  }
2362
- const hookInvoker = this.getService(RouterHookInvoker);
2363
- data = hookInvoker.invokePreHandlerHooks(context);
2493
+ let data = hookInvoker.invokePreHandlerHooks(context);
2364
2494
  if (isPromise(data)) {
2365
2495
  data = await data;
2366
2496
  }
@@ -2371,6 +2501,10 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2371
2501
  data = await data;
2372
2502
  }
2373
2503
  }
2504
+ if (isResponseSent(response)) {
2505
+ debug("Response has been sent by the route handler.");
2506
+ return;
2507
+ }
2374
2508
  let postHandlerData = hookInvoker.invokePostHandlerHooks(
2375
2509
  context,
2376
2510
  data
@@ -2378,17 +2512,24 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
2378
2512
  if (isPromise(postHandlerData)) {
2379
2513
  postHandlerData = await postHandlerData;
2380
2514
  }
2515
+ if (isResponseSent(response)) {
2516
+ debug('Response has been sent by "postHandler" hook.');
2517
+ return;
2518
+ }
2381
2519
  if (postHandlerData !== void 0) {
2382
2520
  data = postHandlerData;
2383
2521
  }
2522
+ } else {
2523
+ debug('Response has been sent by "preHandler" hook.');
2524
+ return;
2525
+ }
2526
+ if (!isResponseSent(response)) {
2527
+ this.getService(DataSender).send(response, data);
2384
2528
  }
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
2529
  }
2530
+ } catch (error) {
2531
+ this.getService(ErrorSender).send(request, response, error);
2532
+ return;
2392
2533
  }
2393
2534
  }
2394
2535
  /**
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.5",
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
  *
@@ -1,13 +1,197 @@
1
1
  import {RequestContext} from '../request-context.js';
2
+ import {IncomingMessage, ServerResponse} from 'http';
2
3
  import {InvalidArgumentError} from '@e22m4u/js-format';
3
4
  import {DebuggableService} from '../debuggable-service.js';
4
- import {isPromise, isResponseSent} from '../utils/index.js';
5
5
  import {RouterHookRegistry, RouterHookType} from './router-hook-registry.js';
6
6
 
7
+ import {
8
+ isPromise,
9
+ isResponseSent,
10
+ isReadableStream,
11
+ isWritableStream,
12
+ } from '../utils/index.js';
13
+
7
14
  /**
8
15
  * Router hook invoker.
9
16
  */
10
17
  export class RouterHookInvoker extends DebuggableService {
18
+ /**
19
+ * Invoke on-request hooks.
20
+ *
21
+ * @param {IncomingMessage} request
22
+ * @param {ServerResponse} response
23
+ * @returns {Promise<boolean|undefined>|boolean|undefined}
24
+ */
25
+ invokeOnRequestHooks(request, response) {
26
+ if (
27
+ !request ||
28
+ typeof request !== 'object' ||
29
+ Array.isArray(request) ||
30
+ !isReadableStream(request)
31
+ ) {
32
+ throw new InvalidArgumentError(
33
+ 'Parameter "request" must be an instance of IncomingMessage, ' +
34
+ 'but %v was given.',
35
+ request,
36
+ );
37
+ }
38
+ if (
39
+ !response ||
40
+ typeof response !== 'object' ||
41
+ Array.isArray(response) ||
42
+ !isWritableStream(response)
43
+ ) {
44
+ throw new InvalidArgumentError(
45
+ 'Parameter "response" must be an instance of ServerResponse, ' +
46
+ 'but %v was given.',
47
+ response,
48
+ );
49
+ }
50
+ // если ответ уже отправлен,
51
+ // то вызов хуков прерывается
52
+ if (isResponseSent(response)) {
53
+ return;
54
+ }
55
+ // итерация по хукам выполняется по индексу,
56
+ // чтобы знать, с какого места продолжать
57
+ // в асинхронном режиме
58
+ const hooks = this.getService(RouterHookRegistry).getHooks(
59
+ RouterHookType.ON_REQUEST,
60
+ );
61
+ let isInterrupted = undefined;
62
+ for (let i = 0; i < hooks.length; i++) {
63
+ const hook = hooks[i];
64
+ // вызов хука выполняется
65
+ // в синхронном режиме
66
+ const result = hook(request, response, this.container);
67
+ // если ответ уже отправлен,
68
+ // то вызов хуков прерывается
69
+ if (isResponseSent(response)) {
70
+ return;
71
+ }
72
+ // если синхронный вызов хука вернул значение отличное
73
+ // от undefined, то требуется проверить данное значение
74
+ // для коррекции режима вызова оставшихся хуков
75
+ if (result !== undefined) {
76
+ // если синхронный вызов хука вернул Promise, то дальнейшее
77
+ // выполнение переключается в асинхронный режим, начиная
78
+ // с индекса следующего хука
79
+ if (isPromise(result)) {
80
+ return this._continueOnRequestHooksInvocationAsync(
81
+ hooks,
82
+ i + 1,
83
+ result,
84
+ request,
85
+ response,
86
+ );
87
+ }
88
+ // если синхронный хук вернул значение true,
89
+ // то выполняется установка флага прерывания
90
+ // обработки запроса и выход из цикла
91
+ if (result === true) {
92
+ isInterrupted = result;
93
+ break;
94
+ }
95
+ // если синхронный хук вернул значение, отличное
96
+ // от логического, то выбрасывается ошибка
97
+ if (result !== false) {
98
+ throw new InvalidArgumentError(
99
+ 'Hook "onRequest" must return undefined or a Boolean, ' +
100
+ 'but %v was given.',
101
+ result,
102
+ );
103
+ }
104
+ }
105
+ }
106
+ // если все хуки были синхронными, то возвращается
107
+ // значение флага прерывания обработки запроса
108
+ // в результат вызова
109
+ return isInterrupted;
110
+ }
111
+
112
+ /**
113
+ * Continue on-request hooks invocation async.
114
+ *
115
+ * @param {Function[]} hooks
116
+ * @param {number} startIndex
117
+ * @param {Promise} initialPromise
118
+ * @param {IncomingMessage} request
119
+ * @param {ServerResponse} response
120
+ * @returns {Promise<boolean|undefined>}
121
+ */
122
+ async _continueOnRequestHooksInvocationAsync(
123
+ hooks,
124
+ startIndex,
125
+ initialPromise,
126
+ request,
127
+ response,
128
+ ) {
129
+ // ожидание Promise, который был получен
130
+ // на предыдущем шаге (в синхронном режиме)
131
+ let result = await initialPromise;
132
+ // если ответ уже отправлен,
133
+ // то вызов хуков прерывается
134
+ if (isResponseSent(response)) {
135
+ return;
136
+ }
137
+ // если Promise разрешился значением, отличным
138
+ // от undefined, то в зависимости от значения,
139
+ // цикл вызова хуков прерывается
140
+ if (result !== undefined) {
141
+ // если Promise разрешился значением true,
142
+ // то данное значение немедленно возвращается
143
+ if (result === true) {
144
+ return result;
145
+ }
146
+ // если Promise вернул значение, отличное
147
+ // от логического, то выбрасывается ошибка
148
+ if (result !== false) {
149
+ throw new InvalidArgumentError(
150
+ 'Hook "onRequest" must return undefined or a Boolean, ' +
151
+ 'but %v was given.',
152
+ result,
153
+ );
154
+ }
155
+ }
156
+ // продолжение вызова хуков начиная
157
+ // со следующего индекса (асинхронно)
158
+ let isInterrupted = undefined;
159
+ for (let i = startIndex; i < hooks.length; i++) {
160
+ // с этого момента все синхронные
161
+ // хуки выполняются как асинхронные
162
+ result = await hooks[i](request, response, this.container);
163
+ // если ответ уже отправлен,
164
+ // то вызов хуков прерывается
165
+ if (isResponseSent(response)) {
166
+ return;
167
+ }
168
+ // если вызов хука вернул значение отличное
169
+ // от undefined, то выполняется проверка
170
+ // необходимости выхода из цикла
171
+ if (result !== undefined) {
172
+ // если хук вернул значение true, то выполняется
173
+ // установка флага прерывания обработки запроса
174
+ // и выход из цикла
175
+ if (result === true) {
176
+ isInterrupted = result;
177
+ break;
178
+ }
179
+ // если хук вернул значение, отличное
180
+ // от логического, то выбрасывается ошибка
181
+ if (result !== false) {
182
+ throw new InvalidArgumentError(
183
+ 'Hook "onRequest" must return undefined or a Boolean, ' +
184
+ 'but %v was given.',
185
+ result,
186
+ );
187
+ }
188
+ }
189
+ }
190
+ // передача флага прерывания обработки
191
+ // запроса в результат вызова
192
+ return isInterrupted;
193
+ }
194
+
11
195
  /**
12
196
  * Последовательно вызывает глобальные хуки и хуки маршрута типа "preHandler",
13
197
  * пока один из них не вернет отличное от undefined значение или не отправит