@e22m4u/js-trie-router 0.7.5 → 0.7.7
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 +10 -0
- package/build-cjs.js +1 -1
- package/dist/cjs/index.cjs +80 -103
- package/package.json +10 -10
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/route/route-registry.d.ts +0 -7
- package/src/route/route-registry.js +1 -31
- package/src/route/route-registry.spec.js +0 -92
- package/src/route/route.d.ts +1 -0
- package/src/route/route.js +2 -0
- package/src/senders/router-data-sender.js +11 -11
- package/src/senders/router-data-sender.spec.js +24 -24
- package/src/senders/router-error-sender.js +2 -2
- package/src/senders/router-error-sender.spec.js +3 -3
- package/src/trie-router.js +2 -23
- package/src/trie-router.spec.js +0 -58
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ HTTP маршрутизатор для Node.js на основе
|
|
|
16
16
|
## Содержание
|
|
17
17
|
|
|
18
18
|
- [Установка](#установка)
|
|
19
|
+
- [Расширения](#расширения)
|
|
19
20
|
- [Использование](#использование)
|
|
20
21
|
- [Параметры маршрутизатора](#параметры-маршрутизатора)
|
|
21
22
|
- [Контекст запроса](#контекст-запроса)
|
|
@@ -54,6 +55,15 @@ import {TrieRouter} from '@e22m4u/js-trie-router';
|
|
|
54
55
|
const {TrieRouter} = require('@e22m4u/js-trie-router');
|
|
55
56
|
```
|
|
56
57
|
|
|
58
|
+
## Расширения
|
|
59
|
+
|
|
60
|
+
Расширение функционала выполняется с помощью NPM модулей.
|
|
61
|
+
|
|
62
|
+
| модуль | описание |
|
|
63
|
+
|------------------------------------------------------------------------------------------------|------------------------------------------------|
|
|
64
|
+
| [@e22m4u/js-trie-router-cors](https://www.npmjs.com/package/@e22m4u/js-trie-router-cors) | Модуль поддержки CORS (кросс-доменные запросы) |
|
|
65
|
+
| [@e22m4u/js-trie-router-openapi](https://www.npmjs.com/package/@e22m4u/js-trie-router-openapi) | Генерация OpenAPI документа и валидация данных |
|
|
66
|
+
|
|
57
67
|
## Использование
|
|
58
68
|
|
|
59
69
|
Базовый пример создания экземпляра роутера, объявления маршрута
|
package/build-cjs.js
CHANGED
package/dist/cjs/index.cjs
CHANGED
|
@@ -43,6 +43,7 @@ __export(index_exports, {
|
|
|
43
43
|
RequestQueryParser: () => RequestQueryParser,
|
|
44
44
|
Route: () => Route,
|
|
45
45
|
RouteRegistry: () => RouteRegistry,
|
|
46
|
+
RouterBranch: () => RouterBranch,
|
|
46
47
|
RouterDataSender: () => RouterDataSender,
|
|
47
48
|
RouterErrorSender: () => RouterErrorSender,
|
|
48
49
|
RouterHookInvoker: () => RouterHookInvoker,
|
|
@@ -65,12 +66,14 @@ __export(index_exports, {
|
|
|
65
66
|
isResponseSent: () => isResponseSent,
|
|
66
67
|
isWritableStream: () => isWritableStream,
|
|
67
68
|
mergeDeep: () => mergeDeep,
|
|
69
|
+
mergeRouterBranchDefinitions: () => mergeRouterBranchDefinitions,
|
|
68
70
|
parseContentType: () => parseContentType,
|
|
69
71
|
parseCookieString: () => parseCookieString,
|
|
70
72
|
parseJsonBody: () => parseJsonBody,
|
|
71
73
|
toCamelCase: () => toCamelCase,
|
|
72
74
|
toPascalCase: () => toPascalCase,
|
|
73
|
-
validateRouteDefinition: () => validateRouteDefinition
|
|
75
|
+
validateRouteDefinition: () => validateRouteDefinition,
|
|
76
|
+
validateRouterBranchDefinition: () => validateRouterBranchDefinition
|
|
74
77
|
});
|
|
75
78
|
module.exports = __toCommonJS(index_exports);
|
|
76
79
|
|
|
@@ -83,7 +86,10 @@ var import_js_debug = require("@e22m4u/js-debug");
|
|
|
83
86
|
// src/debuggable-service.js
|
|
84
87
|
var import_js_service = require("@e22m4u/js-service");
|
|
85
88
|
var MODULE_DEBUG_NAMESPACE = "jsTrieRouter";
|
|
86
|
-
var
|
|
89
|
+
var DebuggableService = class extends import_js_service.DebuggableService {
|
|
90
|
+
static {
|
|
91
|
+
__name(this, "DebuggableService");
|
|
92
|
+
}
|
|
87
93
|
/**
|
|
88
94
|
* Constructor.
|
|
89
95
|
*
|
|
@@ -96,8 +102,6 @@ var _DebuggableService = class _DebuggableService extends import_js_service.Debu
|
|
|
96
102
|
});
|
|
97
103
|
}
|
|
98
104
|
};
|
|
99
|
-
__name(_DebuggableService, "DebuggableService");
|
|
100
|
-
var DebuggableService = _DebuggableService;
|
|
101
105
|
|
|
102
106
|
// src/utils/clone-deep.js
|
|
103
107
|
function cloneDeep(value) {
|
|
@@ -950,7 +954,10 @@ __name(getRequestPathname, "getRequestPathname");
|
|
|
950
954
|
// src/request-context.js
|
|
951
955
|
var import_js_format11 = require("@e22m4u/js-format");
|
|
952
956
|
var import_js_service2 = require("@e22m4u/js-service");
|
|
953
|
-
var
|
|
957
|
+
var RequestContext = class {
|
|
958
|
+
static {
|
|
959
|
+
__name(this, "RequestContext");
|
|
960
|
+
}
|
|
954
961
|
/**
|
|
955
962
|
* Service container.
|
|
956
963
|
*
|
|
@@ -1136,8 +1143,6 @@ var _RequestContext = class _RequestContext {
|
|
|
1136
1143
|
this._route = route;
|
|
1137
1144
|
}
|
|
1138
1145
|
};
|
|
1139
|
-
__name(_RequestContext, "RequestContext");
|
|
1140
|
-
var RequestContext = _RequestContext;
|
|
1141
1146
|
|
|
1142
1147
|
// src/hooks/router-hook-invoker.js
|
|
1143
1148
|
var import_js_format13 = require("@e22m4u/js-format");
|
|
@@ -1151,7 +1156,10 @@ var RouterHookType = {
|
|
|
1151
1156
|
POST_HANDLER: "postHandler"
|
|
1152
1157
|
};
|
|
1153
1158
|
var ROUTER_HOOK_TYPES = Object.values(RouterHookType);
|
|
1154
|
-
var
|
|
1159
|
+
var RouterHookRegistry = class {
|
|
1160
|
+
static {
|
|
1161
|
+
__name(this, "RouterHookRegistry");
|
|
1162
|
+
}
|
|
1155
1163
|
/**
|
|
1156
1164
|
* Hooks.
|
|
1157
1165
|
*
|
|
@@ -1234,11 +1242,12 @@ var _RouterHookRegistry = class _RouterHookRegistry {
|
|
|
1234
1242
|
return this._hooks.get(type) || [];
|
|
1235
1243
|
}
|
|
1236
1244
|
};
|
|
1237
|
-
__name(_RouterHookRegistry, "RouterHookRegistry");
|
|
1238
|
-
var RouterHookRegistry = _RouterHookRegistry;
|
|
1239
1245
|
|
|
1240
1246
|
// src/hooks/router-hook-invoker.js
|
|
1241
|
-
var
|
|
1247
|
+
var RouterHookInvoker = class extends DebuggableService {
|
|
1248
|
+
static {
|
|
1249
|
+
__name(this, "RouterHookInvoker");
|
|
1250
|
+
}
|
|
1242
1251
|
/**
|
|
1243
1252
|
* Invoke on-request hooks.
|
|
1244
1253
|
*
|
|
@@ -1499,8 +1508,6 @@ var _RouterHookInvoker = class _RouterHookInvoker extends DebuggableService {
|
|
|
1499
1508
|
return currentData;
|
|
1500
1509
|
}
|
|
1501
1510
|
};
|
|
1502
|
-
__name(_RouterHookInvoker, "RouterHookInvoker");
|
|
1503
|
-
var RouterHookInvoker = _RouterHookInvoker;
|
|
1504
1511
|
|
|
1505
1512
|
// src/route/validate-route-definition.js
|
|
1506
1513
|
var import_js_format14 = require("@e22m4u/js-format");
|
|
@@ -1583,6 +1590,7 @@ __name(validateRouteDefinition, "validateRouteDefinition");
|
|
|
1583
1590
|
// src/route/route.js
|
|
1584
1591
|
var HttpMethod = {
|
|
1585
1592
|
GET: "GET",
|
|
1593
|
+
HEAD: "HEAD",
|
|
1586
1594
|
POST: "POST",
|
|
1587
1595
|
PUT: "PUT",
|
|
1588
1596
|
PATCH: "PATCH",
|
|
@@ -1590,7 +1598,10 @@ var HttpMethod = {
|
|
|
1590
1598
|
OPTIONS: "OPTIONS"
|
|
1591
1599
|
};
|
|
1592
1600
|
var DEFAULT_META = Object.freeze({});
|
|
1593
|
-
var
|
|
1601
|
+
var Route = class extends import_js_debug.Debuggable {
|
|
1602
|
+
static {
|
|
1603
|
+
__name(this, "Route");
|
|
1604
|
+
}
|
|
1594
1605
|
/**
|
|
1595
1606
|
* Definition.
|
|
1596
1607
|
*
|
|
@@ -1692,14 +1703,15 @@ var _Route = class _Route extends import_js_debug.Debuggable {
|
|
|
1692
1703
|
return this.handler(context);
|
|
1693
1704
|
}
|
|
1694
1705
|
};
|
|
1695
|
-
__name(_Route, "Route");
|
|
1696
|
-
var Route = _Route;
|
|
1697
1706
|
|
|
1698
1707
|
// src/route/route-registry.js
|
|
1699
1708
|
var import_js_path_trie = require("@e22m4u/js-path-trie");
|
|
1700
1709
|
var import_js_service3 = require("@e22m4u/js-service");
|
|
1701
1710
|
var import_js_format15 = require("@e22m4u/js-format");
|
|
1702
|
-
var
|
|
1711
|
+
var RouteRegistry = class extends DebuggableService {
|
|
1712
|
+
static {
|
|
1713
|
+
__name(this, "RouteRegistry");
|
|
1714
|
+
}
|
|
1703
1715
|
/**
|
|
1704
1716
|
* Constructor.
|
|
1705
1717
|
*
|
|
@@ -1789,38 +1801,7 @@ var _RouteRegistry = class _RouteRegistry extends DebuggableService {
|
|
|
1789
1801
|
requestPath
|
|
1790
1802
|
);
|
|
1791
1803
|
}
|
|
1792
|
-
/**
|
|
1793
|
-
* Get allowed methods for request path.
|
|
1794
|
-
*
|
|
1795
|
-
* @param {string} requestPath
|
|
1796
|
-
* @returns {string[]}
|
|
1797
|
-
*/
|
|
1798
|
-
getAllowedMethodsForRequestPath(requestPath) {
|
|
1799
|
-
if (typeof requestPath !== "string") {
|
|
1800
|
-
throw new import_js_format15.InvalidArgumentError(
|
|
1801
|
-
'Parameter "requestPath" must be a String, but %v was given.',
|
|
1802
|
-
requestPath
|
|
1803
|
-
);
|
|
1804
|
-
}
|
|
1805
|
-
const debug = this.getDebuggerFor(this.getAllowedMethodsForRequestPath);
|
|
1806
|
-
const allowedMethods = [];
|
|
1807
|
-
for (const method of Object.values(HttpMethod)) {
|
|
1808
|
-
const rawTriePath = `${method}/${requestPath}`;
|
|
1809
|
-
const triePath = rawTriePath.replace(/\/+/g, "/");
|
|
1810
|
-
if (this._trie.match(triePath)) {
|
|
1811
|
-
allowedMethods.push(method);
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
if (allowedMethods.length) {
|
|
1815
|
-
debug("Allowed methods for %v are: %l.", requestPath, allowedMethods);
|
|
1816
|
-
} else {
|
|
1817
|
-
debug("Path %v does not have allowed methods.", requestPath);
|
|
1818
|
-
}
|
|
1819
|
-
return allowedMethods;
|
|
1820
|
-
}
|
|
1821
1804
|
};
|
|
1822
|
-
__name(_RouteRegistry, "RouteRegistry");
|
|
1823
|
-
var RouteRegistry = _RouteRegistry;
|
|
1824
1805
|
|
|
1825
1806
|
// src/branch/router-branch.js
|
|
1826
1807
|
var import_js_format17 = require("@e22m4u/js-format");
|
|
@@ -1931,7 +1912,10 @@ function mergeRouterBranchDefinitions(firstDef, secondDef) {
|
|
|
1931
1912
|
__name(mergeRouterBranchDefinitions, "mergeRouterBranchDefinitions");
|
|
1932
1913
|
|
|
1933
1914
|
// src/branch/router-branch.js
|
|
1934
|
-
var
|
|
1915
|
+
var RouterBranch = class _RouterBranch extends DebuggableService {
|
|
1916
|
+
static {
|
|
1917
|
+
__name(this, "RouterBranch");
|
|
1918
|
+
}
|
|
1935
1919
|
/**
|
|
1936
1920
|
* Router.
|
|
1937
1921
|
*
|
|
@@ -2050,8 +2034,6 @@ var _RouterBranch = class _RouterBranch extends DebuggableService {
|
|
|
2050
2034
|
return new _RouterBranch(this._router, branchDef, this);
|
|
2051
2035
|
}
|
|
2052
2036
|
};
|
|
2053
|
-
__name(_RouterBranch, "RouterBranch");
|
|
2054
|
-
var RouterBranch = _RouterBranch;
|
|
2055
2037
|
|
|
2056
2038
|
// src/parsers/request-parser.js
|
|
2057
2039
|
var import_http3 = require("http");
|
|
@@ -2063,7 +2045,10 @@ var import_js_format19 = require("@e22m4u/js-format");
|
|
|
2063
2045
|
|
|
2064
2046
|
// src/trie-router-options.js
|
|
2065
2047
|
var import_js_format18 = require("@e22m4u/js-format");
|
|
2066
|
-
var
|
|
2048
|
+
var TrieRouterOptions = class {
|
|
2049
|
+
static {
|
|
2050
|
+
__name(this, "TrieRouterOptions");
|
|
2051
|
+
}
|
|
2067
2052
|
/**
|
|
2068
2053
|
* Request body bytes limit.
|
|
2069
2054
|
*
|
|
@@ -2138,11 +2123,12 @@ var _TrieRouterOptions = class _TrieRouterOptions {
|
|
|
2138
2123
|
}
|
|
2139
2124
|
}
|
|
2140
2125
|
};
|
|
2141
|
-
__name(_TrieRouterOptions, "TrieRouterOptions");
|
|
2142
|
-
var TrieRouterOptions = _TrieRouterOptions;
|
|
2143
2126
|
|
|
2144
2127
|
// src/parsers/request-body-parser.js
|
|
2145
|
-
var
|
|
2128
|
+
var RequestBodyParser = class extends DebuggableService {
|
|
2129
|
+
static {
|
|
2130
|
+
__name(this, "RequestBodyParser");
|
|
2131
|
+
}
|
|
2146
2132
|
/**
|
|
2147
2133
|
* Parsers.
|
|
2148
2134
|
*
|
|
@@ -2285,8 +2271,6 @@ var _RequestBodyParser = class _RequestBodyParser extends DebuggableService {
|
|
|
2285
2271
|
});
|
|
2286
2272
|
}
|
|
2287
2273
|
};
|
|
2288
|
-
__name(_RequestBodyParser, "RequestBodyParser");
|
|
2289
|
-
var RequestBodyParser = _RequestBodyParser;
|
|
2290
2274
|
function parseJsonBody(input) {
|
|
2291
2275
|
if (typeof input !== "string") {
|
|
2292
2276
|
return void 0;
|
|
@@ -2301,7 +2285,10 @@ __name(parseJsonBody, "parseJsonBody");
|
|
|
2301
2285
|
|
|
2302
2286
|
// src/parsers/request-query-parser.js
|
|
2303
2287
|
var import_querystring2 = __toESM(require("querystring"), 1);
|
|
2304
|
-
var
|
|
2288
|
+
var RequestQueryParser = class extends DebuggableService {
|
|
2289
|
+
static {
|
|
2290
|
+
__name(this, "RequestQueryParser");
|
|
2291
|
+
}
|
|
2305
2292
|
/**
|
|
2306
2293
|
* Parse
|
|
2307
2294
|
*
|
|
@@ -2327,11 +2314,12 @@ var _RequestQueryParser = class _RequestQueryParser extends DebuggableService {
|
|
|
2327
2314
|
return query;
|
|
2328
2315
|
}
|
|
2329
2316
|
};
|
|
2330
|
-
__name(_RequestQueryParser, "RequestQueryParser");
|
|
2331
|
-
var RequestQueryParser = _RequestQueryParser;
|
|
2332
2317
|
|
|
2333
2318
|
// src/parsers/request-cookies-parser.js
|
|
2334
|
-
var
|
|
2319
|
+
var RequestCookiesParser = class extends DebuggableService {
|
|
2320
|
+
static {
|
|
2321
|
+
__name(this, "RequestCookiesParser");
|
|
2322
|
+
}
|
|
2335
2323
|
/**
|
|
2336
2324
|
* Parse
|
|
2337
2325
|
*
|
|
@@ -2357,11 +2345,12 @@ var _RequestCookiesParser = class _RequestCookiesParser extends DebuggableServic
|
|
|
2357
2345
|
return cookies;
|
|
2358
2346
|
}
|
|
2359
2347
|
};
|
|
2360
|
-
__name(_RequestCookiesParser, "RequestCookiesParser");
|
|
2361
|
-
var RequestCookiesParser = _RequestCookiesParser;
|
|
2362
2348
|
|
|
2363
2349
|
// src/parsers/request-parser.js
|
|
2364
|
-
var
|
|
2350
|
+
var RequestParser = class extends DebuggableService {
|
|
2351
|
+
static {
|
|
2352
|
+
__name(this, "RequestParser");
|
|
2353
|
+
}
|
|
2365
2354
|
/**
|
|
2366
2355
|
* Parse.
|
|
2367
2356
|
*
|
|
@@ -2399,15 +2388,16 @@ var _RequestParser = class _RequestParser extends DebuggableService {
|
|
|
2399
2388
|
return promises.length ? Promise.all(promises).then(() => data) : data;
|
|
2400
2389
|
}
|
|
2401
2390
|
};
|
|
2402
|
-
__name(_RequestParser, "RequestParser");
|
|
2403
|
-
var RequestParser = _RequestParser;
|
|
2404
2391
|
|
|
2405
2392
|
// src/trie-router.js
|
|
2406
2393
|
var import_http4 = require("http");
|
|
2407
2394
|
|
|
2408
2395
|
// src/senders/router-data-sender.js
|
|
2409
2396
|
var import_js_format21 = require("@e22m4u/js-format");
|
|
2410
|
-
var
|
|
2397
|
+
var RouterDataSender = class extends DebuggableService {
|
|
2398
|
+
static {
|
|
2399
|
+
__name(this, "RouterDataSender");
|
|
2400
|
+
}
|
|
2411
2401
|
/**
|
|
2412
2402
|
* Send.
|
|
2413
2403
|
*
|
|
@@ -2428,8 +2418,8 @@ var _RouterDataSender = class _RouterDataSender extends DebuggableService {
|
|
|
2428
2418
|
return;
|
|
2429
2419
|
}
|
|
2430
2420
|
if (isReadableStream(data)) {
|
|
2431
|
-
if (!response.getHeader("
|
|
2432
|
-
response.setHeader("
|
|
2421
|
+
if (!response.getHeader("Content-Type")) {
|
|
2422
|
+
response.setHeader("Content-Type", "application/octet-stream");
|
|
2433
2423
|
}
|
|
2434
2424
|
data.pipe(response);
|
|
2435
2425
|
debug("Sending response with a Stream.");
|
|
@@ -2441,13 +2431,13 @@ var _RouterDataSender = class _RouterDataSender extends DebuggableService {
|
|
|
2441
2431
|
case "boolean":
|
|
2442
2432
|
case "object":
|
|
2443
2433
|
if (Buffer.isBuffer(data)) {
|
|
2444
|
-
if (!response.getHeader("
|
|
2445
|
-
response.setHeader("
|
|
2434
|
+
if (!response.getHeader("Content-Type")) {
|
|
2435
|
+
response.setHeader("Content-Type", "application/octet-stream");
|
|
2446
2436
|
}
|
|
2447
2437
|
debugMsg = "Buffer has been sent as binary data.";
|
|
2448
2438
|
} else {
|
|
2449
|
-
if (!response.getHeader("
|
|
2450
|
-
response.setHeader("
|
|
2439
|
+
if (!response.getHeader("Content-Type")) {
|
|
2440
|
+
response.setHeader("Content-Type", "application/json");
|
|
2451
2441
|
}
|
|
2452
2442
|
debugMsg = (0, import_js_format21.format)(
|
|
2453
2443
|
"%v has been sent as JSON.",
|
|
@@ -2457,8 +2447,8 @@ var _RouterDataSender = class _RouterDataSender extends DebuggableService {
|
|
|
2457
2447
|
}
|
|
2458
2448
|
break;
|
|
2459
2449
|
default:
|
|
2460
|
-
if (!response.getHeader("
|
|
2461
|
-
response.setHeader("
|
|
2450
|
+
if (!response.getHeader("Content-Type")) {
|
|
2451
|
+
response.setHeader("Content-Type", "text/plain");
|
|
2462
2452
|
}
|
|
2463
2453
|
debugMsg = "Response data has been sent as plain text.";
|
|
2464
2454
|
data = String(data);
|
|
@@ -2468,14 +2458,15 @@ var _RouterDataSender = class _RouterDataSender extends DebuggableService {
|
|
|
2468
2458
|
debug(debugMsg);
|
|
2469
2459
|
}
|
|
2470
2460
|
};
|
|
2471
|
-
__name(_RouterDataSender, "RouterDataSender");
|
|
2472
|
-
var RouterDataSender = _RouterDataSender;
|
|
2473
2461
|
|
|
2474
2462
|
// src/senders/router-error-sender.js
|
|
2475
2463
|
var import_util = require("util");
|
|
2476
2464
|
var import_statuses = __toESM(require("statuses"), 1);
|
|
2477
2465
|
var EXPOSED_ERROR_PROPERTIES = ["code", "details"];
|
|
2478
|
-
var
|
|
2466
|
+
var RouterErrorSender = class extends DebuggableService {
|
|
2467
|
+
static {
|
|
2468
|
+
__name(this, "RouterErrorSender");
|
|
2469
|
+
}
|
|
2479
2470
|
/**
|
|
2480
2471
|
* Handle.
|
|
2481
2472
|
*
|
|
@@ -2525,7 +2516,7 @@ var _RouterErrorSender = class _RouterErrorSender extends DebuggableService {
|
|
|
2525
2516
|
console.error(error);
|
|
2526
2517
|
}
|
|
2527
2518
|
response.statusCode = statusCode;
|
|
2528
|
-
response.setHeader("
|
|
2519
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
2529
2520
|
response.end(JSON.stringify(body, null, 2), "utf-8");
|
|
2530
2521
|
debug(
|
|
2531
2522
|
"%s error has been sent for the request %s %v.",
|
|
@@ -2544,7 +2535,7 @@ var _RouterErrorSender = class _RouterErrorSender extends DebuggableService {
|
|
|
2544
2535
|
send404(request, response) {
|
|
2545
2536
|
const debug = this.getDebuggerFor(this.send404);
|
|
2546
2537
|
response.statusCode = 404;
|
|
2547
|
-
response.setHeader("
|
|
2538
|
+
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
2548
2539
|
response.end("404 Not Found", "utf-8");
|
|
2549
2540
|
debug(
|
|
2550
2541
|
"404 error has been sent for the request %s %v.",
|
|
@@ -2553,12 +2544,13 @@ var _RouterErrorSender = class _RouterErrorSender extends DebuggableService {
|
|
|
2553
2544
|
);
|
|
2554
2545
|
}
|
|
2555
2546
|
};
|
|
2556
|
-
__name(_RouterErrorSender, "RouterErrorSender");
|
|
2557
|
-
var RouterErrorSender = _RouterErrorSender;
|
|
2558
2547
|
|
|
2559
2548
|
// src/trie-router.js
|
|
2560
2549
|
var import_js_service4 = require("@e22m4u/js-service");
|
|
2561
|
-
var
|
|
2550
|
+
var TrieRouter = class extends DebuggableService {
|
|
2551
|
+
static {
|
|
2552
|
+
__name(this, "TrieRouter");
|
|
2553
|
+
}
|
|
2562
2554
|
/**
|
|
2563
2555
|
* Constructor.
|
|
2564
2556
|
*
|
|
@@ -2613,7 +2605,7 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
|
2613
2605
|
* Example:
|
|
2614
2606
|
* ```js
|
|
2615
2607
|
* const router = new TrieRouter();
|
|
2616
|
-
* const apiBranch = router.createBranch({path: 'api'});
|
|
2608
|
+
* const apiBranch = router.createBranch({path: '/api'});
|
|
2617
2609
|
*
|
|
2618
2610
|
* // GET /api/hello
|
|
2619
2611
|
* apiBranch.defineRoute({
|
|
@@ -2680,22 +2672,6 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
|
2680
2672
|
}
|
|
2681
2673
|
const resolved = this.getService(RouteRegistry).matchRouteByRequest(request);
|
|
2682
2674
|
if (!resolved) {
|
|
2683
|
-
if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
|
|
2684
|
-
const allowedMethods = this.getService(RouteRegistry).getAllowedMethodsForRequestPath(
|
|
2685
|
-
requestPath
|
|
2686
|
-
);
|
|
2687
|
-
if (allowedMethods.length > 0) {
|
|
2688
|
-
debug("Auto-handling OPTIONS request.");
|
|
2689
|
-
if (!allowedMethods.includes("OPTIONS")) {
|
|
2690
|
-
allowedMethods.push("OPTIONS");
|
|
2691
|
-
}
|
|
2692
|
-
const allowHeader = allowedMethods.join(", ");
|
|
2693
|
-
response.statusCode = 204;
|
|
2694
|
-
response.setHeader("Allow", allowHeader);
|
|
2695
|
-
response.end();
|
|
2696
|
-
return;
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
2675
|
debug(
|
|
2700
2676
|
"No route found for the request %s %v.",
|
|
2701
2677
|
request.method,
|
|
@@ -2781,8 +2757,6 @@ var _TrieRouter = class _TrieRouter extends DebuggableService {
|
|
|
2781
2757
|
return this.getService(RouterHookRegistry).hasHook(type, hook);
|
|
2782
2758
|
}
|
|
2783
2759
|
};
|
|
2784
|
-
__name(_TrieRouter, "TrieRouter");
|
|
2785
|
-
var TrieRouter = _TrieRouter;
|
|
2786
2760
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2787
2761
|
0 && (module.exports = {
|
|
2788
2762
|
CHARACTER_ENCODING_LIST,
|
|
@@ -2797,6 +2771,7 @@ var TrieRouter = _TrieRouter;
|
|
|
2797
2771
|
RequestQueryParser,
|
|
2798
2772
|
Route,
|
|
2799
2773
|
RouteRegistry,
|
|
2774
|
+
RouterBranch,
|
|
2800
2775
|
RouterDataSender,
|
|
2801
2776
|
RouterErrorSender,
|
|
2802
2777
|
RouterHookInvoker,
|
|
@@ -2819,10 +2794,12 @@ var TrieRouter = _TrieRouter;
|
|
|
2819
2794
|
isResponseSent,
|
|
2820
2795
|
isWritableStream,
|
|
2821
2796
|
mergeDeep,
|
|
2797
|
+
mergeRouterBranchDefinitions,
|
|
2822
2798
|
parseContentType,
|
|
2823
2799
|
parseCookieString,
|
|
2824
2800
|
parseJsonBody,
|
|
2825
2801
|
toCamelCase,
|
|
2826
2802
|
toPascalCase,
|
|
2827
|
-
validateRouteDefinition
|
|
2803
|
+
validateRouteDefinition,
|
|
2804
|
+
validateRouterBranchDefinition
|
|
2828
2805
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e22m4u/js-trie-router",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "HTTP маршрутизатор для Node.js на основе префиксного дерева",
|
|
5
5
|
"author": "Mikhail Evstropov <e22m4u@yandex.ru>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
"server",
|
|
12
12
|
"nodejs"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://
|
|
14
|
+
"homepage": "https://gitverse.ru/e22m4u/js-trie-router",
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://
|
|
17
|
+
"url": "git+https://gitverse.ru/e22m4u/js-trie-router.git"
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
20
|
"types": "./src/index.d.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"require": "./dist/cjs/index.cjs"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"lint": "tsc && eslint ./src",
|
|
@@ -41,19 +41,19 @@
|
|
|
41
41
|
"@e22m4u/js-debug": "~0.4.1",
|
|
42
42
|
"@e22m4u/js-format": "~0.4.0",
|
|
43
43
|
"@e22m4u/js-path-trie": "~0.2.0",
|
|
44
|
-
"@e22m4u/js-service": "~0.
|
|
44
|
+
"@e22m4u/js-service": "~0.6.1",
|
|
45
45
|
"debug": "~4.4.3",
|
|
46
46
|
"http-errors": "~2.0.1",
|
|
47
47
|
"statuses": "~2.0.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@commitlint/cli": "~20.4.
|
|
51
|
-
"@commitlint/config-conventional": "~20.4.
|
|
50
|
+
"@commitlint/cli": "~20.4.3",
|
|
51
|
+
"@commitlint/config-conventional": "~20.4.3",
|
|
52
52
|
"@eslint/js": "~9.39.2",
|
|
53
53
|
"@types/chai": "~5.2.3",
|
|
54
54
|
"@types/chai-as-promised": "~8.0.2",
|
|
55
55
|
"@types/mocha": "~10.0.10",
|
|
56
|
-
"c8": "~
|
|
56
|
+
"c8": "~11.0.0",
|
|
57
57
|
"chai": "~6.2.2",
|
|
58
58
|
"chai-as-promised": "~8.0.2",
|
|
59
59
|
"esbuild": "~0.27.3",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"eslint-config-prettier": "~10.1.8",
|
|
62
62
|
"eslint-plugin-chai-expect": "~3.1.0",
|
|
63
63
|
"eslint-plugin-import": "~2.32.0",
|
|
64
|
-
"eslint-plugin-jsdoc": "~62.7.
|
|
64
|
+
"eslint-plugin-jsdoc": "~62.7.1",
|
|
65
65
|
"eslint-plugin-mocha": "~11.2.0",
|
|
66
|
-
"globals": "~17.
|
|
66
|
+
"globals": "~17.4.0",
|
|
67
67
|
"husky": "~9.1.7",
|
|
68
68
|
"mocha": "~11.7.5",
|
|
69
69
|
"prettier": "~3.8.1",
|
package/src/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './route/index.js';
|
|
|
3
3
|
export * from './utils/index.js';
|
|
4
4
|
export * from './hooks/index.js';
|
|
5
5
|
export * from './trie-router.js';
|
|
6
|
+
export * from './branch/index.js';
|
|
6
7
|
export * from './parsers/index.js';
|
|
7
8
|
export * from './senders/index.js';
|
|
8
9
|
export * from './request-context.js';
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './route/index.js';
|
|
|
3
3
|
export * from './utils/index.js';
|
|
4
4
|
export * from './hooks/index.js';
|
|
5
5
|
export * from './trie-router.js';
|
|
6
|
+
export * from './branch/index.js';
|
|
6
7
|
export * from './parsers/index.js';
|
|
7
8
|
export * from './senders/index.js';
|
|
8
9
|
export * from './request-context.js';
|
|
@@ -36,11 +36,4 @@ export declare class RouteRegistry extends DebuggableService {
|
|
|
36
36
|
* @param request
|
|
37
37
|
*/
|
|
38
38
|
matchRouteByRequest(request: IncomingMessage): ResolvedRoute | undefined;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get allowed methods for request path.
|
|
42
|
-
*
|
|
43
|
-
* @param requestPath
|
|
44
|
-
*/
|
|
45
|
-
getAllowedMethodsForRequestPath(requestPath: string): string[];
|
|
46
39
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Route} from './route.js';
|
|
2
2
|
import {PathTrie} from '@e22m4u/js-path-trie';
|
|
3
3
|
import {ServiceContainer} from '@e22m4u/js-service';
|
|
4
4
|
import {getRequestPathname} from '../utils/index.js';
|
|
@@ -124,34 +124,4 @@ export class RouteRegistry extends DebuggableService {
|
|
|
124
124
|
requestPath,
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Get allowed methods for request path.
|
|
130
|
-
*
|
|
131
|
-
* @param {string} requestPath
|
|
132
|
-
* @returns {string[]}
|
|
133
|
-
*/
|
|
134
|
-
getAllowedMethodsForRequestPath(requestPath) {
|
|
135
|
-
if (typeof requestPath !== 'string') {
|
|
136
|
-
throw new InvalidArgumentError(
|
|
137
|
-
'Parameter "requestPath" must be a String, but %v was given.',
|
|
138
|
-
requestPath,
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
const debug = this.getDebuggerFor(this.getAllowedMethodsForRequestPath);
|
|
142
|
-
const allowedMethods = [];
|
|
143
|
-
for (const method of Object.values(HttpMethod)) {
|
|
144
|
-
const rawTriePath = `${method}/${requestPath}`;
|
|
145
|
-
const triePath = rawTriePath.replace(/\/+/g, '/');
|
|
146
|
-
if (this._trie.match(triePath)) {
|
|
147
|
-
allowedMethods.push(method);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (allowedMethods.length) {
|
|
151
|
-
debug('Allowed methods for %v are: %l.', requestPath, allowedMethods);
|
|
152
|
-
} else {
|
|
153
|
-
debug('Path %v does not have allowed methods.', requestPath);
|
|
154
|
-
}
|
|
155
|
-
return allowedMethods;
|
|
156
|
-
}
|
|
157
127
|
}
|
|
@@ -2,7 +2,6 @@ import {expect} from 'chai';
|
|
|
2
2
|
import {format} from '@e22m4u/js-format';
|
|
3
3
|
import {Route, HttpMethod} from './route.js';
|
|
4
4
|
import {RouteRegistry} from './route-registry.js';
|
|
5
|
-
import {ServiceContainer} from '@e22m4u/js-service';
|
|
6
5
|
import {RouterHookRegistry, RouterHookType} from '../hooks/index.js';
|
|
7
6
|
|
|
8
7
|
describe('RouteRegistry', function () {
|
|
@@ -153,95 +152,4 @@ describe('RouteRegistry', function () {
|
|
|
153
152
|
expect(res.params).to.be.eql({p1: 'baz', p2: 'qux'});
|
|
154
153
|
});
|
|
155
154
|
});
|
|
156
|
-
|
|
157
|
-
describe('getAllowedMethodsForRequestPath', function () {
|
|
158
|
-
it('should require the parameter "requestPath" to be a String', function () {
|
|
159
|
-
const S = new RouteRegistry();
|
|
160
|
-
const throwable = v => () => S.getAllowedMethodsForRequestPath(v);
|
|
161
|
-
const error = v =>
|
|
162
|
-
format(
|
|
163
|
-
'Parameter "requestPath" must be a String, but %s was given.',
|
|
164
|
-
v,
|
|
165
|
-
);
|
|
166
|
-
expect(throwable(10)).to.throw(error('10'));
|
|
167
|
-
expect(throwable(0)).to.throw(error('0'));
|
|
168
|
-
expect(throwable(true)).to.throw(error('true'));
|
|
169
|
-
expect(throwable(false)).to.throw(error('false'));
|
|
170
|
-
expect(throwable([])).to.throw(error('Array'));
|
|
171
|
-
expect(throwable({})).to.throw(error('Object'));
|
|
172
|
-
expect(throwable(undefined)).to.throw(error('undefined'));
|
|
173
|
-
expect(throwable(null)).to.throw(error('null'));
|
|
174
|
-
expect(throwable(() => undefined)).to.throw(error('Function'));
|
|
175
|
-
throwable('str')();
|
|
176
|
-
throwable('')();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should return an empty array if no routes match the path', function () {
|
|
180
|
-
const S = new RouteRegistry(new ServiceContainer());
|
|
181
|
-
S.defineRoute({
|
|
182
|
-
method: HttpMethod.GET,
|
|
183
|
-
path: '/foo',
|
|
184
|
-
handler: () => undefined,
|
|
185
|
-
});
|
|
186
|
-
const res = S.getAllowedMethodsForRequestPath('/bar');
|
|
187
|
-
expect(res).to.be.eql([]);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should return an array with a single method if only one matches', function () {
|
|
191
|
-
const S = new RouteRegistry(new ServiceContainer());
|
|
192
|
-
S.defineRoute({
|
|
193
|
-
method: HttpMethod.POST,
|
|
194
|
-
path: '/foo',
|
|
195
|
-
handler: () => undefined,
|
|
196
|
-
});
|
|
197
|
-
const res = S.getAllowedMethodsForRequestPath('/foo');
|
|
198
|
-
expect(res).to.be.eql([HttpMethod.POST]);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('should return an array with multiple methods if several routes match the path', function () {
|
|
202
|
-
const S = new RouteRegistry(new ServiceContainer());
|
|
203
|
-
const handler = () => undefined;
|
|
204
|
-
S.defineRoute({method: HttpMethod.GET, path: '/foo', handler});
|
|
205
|
-
S.defineRoute({method: HttpMethod.POST, path: '/foo', handler});
|
|
206
|
-
S.defineRoute({method: HttpMethod.DELETE, path: '/foo', handler});
|
|
207
|
-
const res = S.getAllowedMethodsForRequestPath('/foo');
|
|
208
|
-
expect(res).to.be.eql([
|
|
209
|
-
HttpMethod.GET,
|
|
210
|
-
HttpMethod.POST,
|
|
211
|
-
HttpMethod.DELETE,
|
|
212
|
-
]);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('should correctly resolve allowed methods for paths with parameters', function () {
|
|
216
|
-
const S = new RouteRegistry(new ServiceContainer());
|
|
217
|
-
const handler = () => undefined;
|
|
218
|
-
S.defineRoute({method: HttpMethod.GET, path: '/users/:id', handler});
|
|
219
|
-
S.defineRoute({method: HttpMethod.PUT, path: '/users/:id', handler});
|
|
220
|
-
S.defineRoute({method: HttpMethod.POST, path: '/users', handler});
|
|
221
|
-
const res = S.getAllowedMethodsForRequestPath('/users/123');
|
|
222
|
-
expect(res).to.be.eql([HttpMethod.GET, HttpMethod.PUT]);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('should distinguish between paths with and without trailing slash', function () {
|
|
226
|
-
const S = new RouteRegistry(new ServiceContainer());
|
|
227
|
-
const handler = () => undefined;
|
|
228
|
-
S.defineRoute({method: HttpMethod.GET, path: '/foo', handler});
|
|
229
|
-
S.defineRoute({method: HttpMethod.POST, path: '/foo/', handler});
|
|
230
|
-
const res1 = S.getAllowedMethodsForRequestPath('/foo');
|
|
231
|
-
expect(res1).to.be.eql([HttpMethod.GET]);
|
|
232
|
-
const res2 = S.getAllowedMethodsForRequestPath('/foo/');
|
|
233
|
-
expect(res2).to.be.eql([HttpMethod.POST]);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('should check for the explicitly defined OPTIONS method', function () {
|
|
237
|
-
const S = new RouteRegistry(new ServiceContainer());
|
|
238
|
-
S.defineRoute({
|
|
239
|
-
method: HttpMethod.OPTIONS,
|
|
240
|
-
path: '/api',
|
|
241
|
-
handler: () => undefined,
|
|
242
|
-
});
|
|
243
|
-
const res = S.getAllowedMethodsForRequestPath('/api');
|
|
244
|
-
expect(res).to.be.eql([HttpMethod.OPTIONS]);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
155
|
});
|
package/src/route/route.d.ts
CHANGED
package/src/route/route.js
CHANGED
|
@@ -24,6 +24,7 @@ import {validateRouteDefinition} from './validate-route-definition.js';
|
|
|
24
24
|
*
|
|
25
25
|
* @type {{
|
|
26
26
|
* GET: 'GET',
|
|
27
|
+
* HEAD: 'HEAD',
|
|
27
28
|
* POST: 'POST',
|
|
28
29
|
* PUT: 'PUT',
|
|
29
30
|
* PATCH: 'PATCH',
|
|
@@ -33,6 +34,7 @@ import {validateRouteDefinition} from './validate-route-definition.js';
|
|
|
33
34
|
*/
|
|
34
35
|
export const HttpMethod = {
|
|
35
36
|
GET: 'GET',
|
|
37
|
+
HEAD: 'HEAD',
|
|
36
38
|
POST: 'POST',
|
|
37
39
|
PUT: 'PUT',
|
|
38
40
|
PATCH: 'PATCH',
|
|
@@ -33,10 +33,10 @@ export class RouterDataSender extends DebuggableService {
|
|
|
33
33
|
// если ответ контроллера является стримом,
|
|
34
34
|
// то поток отправляет бинарные данные
|
|
35
35
|
if (isReadableStream(data)) {
|
|
36
|
-
// если заголовок "
|
|
36
|
+
// если заголовок "Content-Type" не определен ранее,
|
|
37
37
|
// то устанавливается заголовок потоковых данных
|
|
38
|
-
if (!response.getHeader('
|
|
39
|
-
response.setHeader('
|
|
38
|
+
if (!response.getHeader('Content-Type')) {
|
|
39
|
+
response.setHeader('Content-Type', 'application/octet-stream');
|
|
40
40
|
}
|
|
41
41
|
data.pipe(response);
|
|
42
42
|
debug('Sending response with a Stream.');
|
|
@@ -49,21 +49,21 @@ export class RouterDataSender extends DebuggableService {
|
|
|
49
49
|
case 'number':
|
|
50
50
|
case 'boolean':
|
|
51
51
|
case 'object':
|
|
52
|
-
// для бинарных данных предусмотрен специальный "
|
|
52
|
+
// для бинарных данных предусмотрен специальный "Content-Type",
|
|
53
53
|
// который устанавливается автоматически, если не был определен
|
|
54
54
|
// ранее (к примеру, в обработчике маршрута)
|
|
55
55
|
if (Buffer.isBuffer(data)) {
|
|
56
|
-
if (!response.getHeader('
|
|
57
|
-
response.setHeader('
|
|
56
|
+
if (!response.getHeader('Content-Type')) {
|
|
57
|
+
response.setHeader('Content-Type', 'application/octet-stream');
|
|
58
58
|
}
|
|
59
59
|
debugMsg = 'Buffer has been sent as binary data.';
|
|
60
60
|
}
|
|
61
61
|
// объекты, массивы, числа и логические значения
|
|
62
62
|
// отправляются в виде JSON строки, с соответствующим
|
|
63
|
-
// заголовком "
|
|
63
|
+
// заголовком "Content-Type" (если не был определен)
|
|
64
64
|
else {
|
|
65
|
-
if (!response.getHeader('
|
|
66
|
-
response.setHeader('
|
|
65
|
+
if (!response.getHeader('Content-Type')) {
|
|
66
|
+
response.setHeader('Content-Type', 'application/json');
|
|
67
67
|
}
|
|
68
68
|
debugMsg = format(
|
|
69
69
|
'%v has been sent as JSON.',
|
|
@@ -73,8 +73,8 @@ export class RouterDataSender extends DebuggableService {
|
|
|
73
73
|
}
|
|
74
74
|
break;
|
|
75
75
|
default:
|
|
76
|
-
if (!response.getHeader('
|
|
77
|
-
response.setHeader('
|
|
76
|
+
if (!response.getHeader('Content-Type')) {
|
|
77
|
+
response.setHeader('Content-Type', 'text/plain');
|
|
78
78
|
}
|
|
79
79
|
debugMsg = 'Response data has been sent as plain text.';
|
|
80
80
|
data = String(data);
|
|
@@ -80,7 +80,7 @@ describe('RouterDataSender', function () {
|
|
|
80
80
|
writable._final = function (callback) {
|
|
81
81
|
const sentData = Buffer.concat(chunks).toString('utf-8');
|
|
82
82
|
expect(sentData).to.be.eq(data);
|
|
83
|
-
const ct = res.getHeader('
|
|
83
|
+
const ct = res.getHeader('Content-Type');
|
|
84
84
|
expect(ct).to.be.eq('application/octet-stream');
|
|
85
85
|
callback();
|
|
86
86
|
done();
|
|
@@ -90,7 +90,7 @@ describe('RouterDataSender', function () {
|
|
|
90
90
|
S.send(res, stream);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
it('should allow override the "
|
|
93
|
+
it('should allow override the "Content-Type" header for a readable stream', function (done) {
|
|
94
94
|
const data = 'text';
|
|
95
95
|
const stream = new Readable();
|
|
96
96
|
stream._read = () => {};
|
|
@@ -98,7 +98,7 @@ describe('RouterDataSender', function () {
|
|
|
98
98
|
stream.push(null);
|
|
99
99
|
const res = createResponseMock();
|
|
100
100
|
const contentType = 'custom/type';
|
|
101
|
-
res.setHeader('
|
|
101
|
+
res.setHeader('Content-Type', contentType);
|
|
102
102
|
const writable = new Writable();
|
|
103
103
|
const chunks = [];
|
|
104
104
|
writable._write = function (chunk, encoding, done) {
|
|
@@ -108,7 +108,7 @@ describe('RouterDataSender', function () {
|
|
|
108
108
|
writable._final = function (callback) {
|
|
109
109
|
const sentData = Buffer.concat(chunks).toString('utf-8');
|
|
110
110
|
expect(sentData).to.be.eq(data);
|
|
111
|
-
const ct = res.getHeader('
|
|
111
|
+
const ct = res.getHeader('Content-Type');
|
|
112
112
|
expect(ct).to.be.eq(contentType);
|
|
113
113
|
callback();
|
|
114
114
|
done();
|
|
@@ -131,7 +131,7 @@ describe('RouterDataSender', function () {
|
|
|
131
131
|
const sentJson = Buffer.concat(chunks).toString('utf-8');
|
|
132
132
|
const sentData = JSON.parse(sentJson);
|
|
133
133
|
expect(sentData).to.be.eql(data);
|
|
134
|
-
const ct = res.getHeader('
|
|
134
|
+
const ct = res.getHeader('Content-Type');
|
|
135
135
|
expect(ct).to.be.eq('application/json');
|
|
136
136
|
callback();
|
|
137
137
|
done();
|
|
@@ -141,11 +141,11 @@ describe('RouterDataSender', function () {
|
|
|
141
141
|
S.send(res, data);
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
-
it('should allow override the "
|
|
144
|
+
it('should allow override the "Content-Type" header for a number value', function (done) {
|
|
145
145
|
const data = 10;
|
|
146
146
|
const res = createResponseMock();
|
|
147
147
|
const contentType = 'custom/type';
|
|
148
|
-
res.setHeader('
|
|
148
|
+
res.setHeader('Content-Type', contentType);
|
|
149
149
|
const writable = new Writable();
|
|
150
150
|
const chunks = [];
|
|
151
151
|
writable._write = function (chunk, encoding, done) {
|
|
@@ -156,7 +156,7 @@ describe('RouterDataSender', function () {
|
|
|
156
156
|
const sentJson = Buffer.concat(chunks).toString('utf-8');
|
|
157
157
|
const sentData = JSON.parse(sentJson);
|
|
158
158
|
expect(sentData).to.be.eql(data);
|
|
159
|
-
const ct = res.getHeader('
|
|
159
|
+
const ct = res.getHeader('Content-Type');
|
|
160
160
|
expect(ct).to.be.eq(contentType);
|
|
161
161
|
callback();
|
|
162
162
|
done();
|
|
@@ -179,7 +179,7 @@ describe('RouterDataSender', function () {
|
|
|
179
179
|
const sentJson = Buffer.concat(chunks).toString('utf-8');
|
|
180
180
|
const sentData = JSON.parse(sentJson);
|
|
181
181
|
expect(sentData).to.be.eql(data);
|
|
182
|
-
const ct = res.getHeader('
|
|
182
|
+
const ct = res.getHeader('Content-Type');
|
|
183
183
|
expect(ct).to.be.eq('application/json');
|
|
184
184
|
callback();
|
|
185
185
|
done();
|
|
@@ -189,11 +189,11 @@ describe('RouterDataSender', function () {
|
|
|
189
189
|
S.send(res, data);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
it('should allow override the "
|
|
192
|
+
it('should allow override the "Content-Type" header for a boolean value', function (done) {
|
|
193
193
|
const data = true;
|
|
194
194
|
const res = createResponseMock();
|
|
195
195
|
const contentType = 'custom/type';
|
|
196
|
-
res.setHeader('
|
|
196
|
+
res.setHeader('Content-Type', contentType);
|
|
197
197
|
const writable = new Writable();
|
|
198
198
|
const chunks = [];
|
|
199
199
|
writable._write = function (chunk, encoding, done) {
|
|
@@ -204,7 +204,7 @@ describe('RouterDataSender', function () {
|
|
|
204
204
|
const sentJson = Buffer.concat(chunks).toString('utf-8');
|
|
205
205
|
const sentData = JSON.parse(sentJson);
|
|
206
206
|
expect(sentData).to.be.eql(data);
|
|
207
|
-
const ct = res.getHeader('
|
|
207
|
+
const ct = res.getHeader('Content-Type');
|
|
208
208
|
expect(ct).to.be.eq(contentType);
|
|
209
209
|
callback();
|
|
210
210
|
done();
|
|
@@ -226,7 +226,7 @@ describe('RouterDataSender', function () {
|
|
|
226
226
|
writable._final = function (callback) {
|
|
227
227
|
const sentData = Buffer.concat(chunks);
|
|
228
228
|
expect(sentData).to.be.eql(sentData);
|
|
229
|
-
const ct = res.getHeader('
|
|
229
|
+
const ct = res.getHeader('Content-Type');
|
|
230
230
|
expect(ct).to.be.eq('application/octet-stream');
|
|
231
231
|
callback();
|
|
232
232
|
done();
|
|
@@ -236,11 +236,11 @@ describe('RouterDataSender', function () {
|
|
|
236
236
|
S.send(res, data);
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
-
it('should allow override the "
|
|
239
|
+
it('should allow override the "Content-Type" header for a Buffer', function (done) {
|
|
240
240
|
const data = Buffer.from('text');
|
|
241
241
|
const res = createResponseMock();
|
|
242
242
|
const contentType = 'custom/type';
|
|
243
|
-
res.setHeader('
|
|
243
|
+
res.setHeader('Content-Type', contentType);
|
|
244
244
|
const writable = new Writable();
|
|
245
245
|
const chunks = [];
|
|
246
246
|
writable._write = function (chunk, encoding, done) {
|
|
@@ -250,7 +250,7 @@ describe('RouterDataSender', function () {
|
|
|
250
250
|
writable._final = function (callback) {
|
|
251
251
|
const sentData = Buffer.concat(chunks);
|
|
252
252
|
expect(sentData).to.be.eql(sentData);
|
|
253
|
-
const ct = res.getHeader('
|
|
253
|
+
const ct = res.getHeader('Content-Type');
|
|
254
254
|
expect(ct).to.be.eq(contentType);
|
|
255
255
|
callback();
|
|
256
256
|
done();
|
|
@@ -273,7 +273,7 @@ describe('RouterDataSender', function () {
|
|
|
273
273
|
const sentJson = Buffer.concat(chunks).toString('utf-8');
|
|
274
274
|
const sentData = JSON.parse(sentJson);
|
|
275
275
|
expect(sentData).to.be.eql(data);
|
|
276
|
-
const ct = res.getHeader('
|
|
276
|
+
const ct = res.getHeader('Content-Type');
|
|
277
277
|
expect(ct).to.be.eq('application/json');
|
|
278
278
|
callback();
|
|
279
279
|
done();
|
|
@@ -283,11 +283,11 @@ describe('RouterDataSender', function () {
|
|
|
283
283
|
S.send(res, data);
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
it('should allow override the "
|
|
286
|
+
it('should allow override the "Content-Type" header for an object value', function (done) {
|
|
287
287
|
const data = {foo: 'bar'};
|
|
288
288
|
const res = createResponseMock();
|
|
289
289
|
const contentType = 'custom/type';
|
|
290
|
-
res.setHeader('
|
|
290
|
+
res.setHeader('Content-Type', contentType);
|
|
291
291
|
const writable = new Writable();
|
|
292
292
|
const chunks = [];
|
|
293
293
|
writable._write = function (chunk, encoding, done) {
|
|
@@ -298,7 +298,7 @@ describe('RouterDataSender', function () {
|
|
|
298
298
|
const sentJson = Buffer.concat(chunks).toString('utf-8');
|
|
299
299
|
const sentData = JSON.parse(sentJson);
|
|
300
300
|
expect(sentData).to.be.eql(data);
|
|
301
|
-
const ct = res.getHeader('
|
|
301
|
+
const ct = res.getHeader('Content-Type');
|
|
302
302
|
expect(ct).to.be.eq(contentType);
|
|
303
303
|
callback();
|
|
304
304
|
done();
|
|
@@ -320,7 +320,7 @@ describe('RouterDataSender', function () {
|
|
|
320
320
|
writable._final = function (callback) {
|
|
321
321
|
const sentData = Buffer.concat(chunks).toString('utf-8');
|
|
322
322
|
expect(sentData).to.be.eq(data);
|
|
323
|
-
const ct = res.getHeader('
|
|
323
|
+
const ct = res.getHeader('Content-Type');
|
|
324
324
|
expect(ct).to.be.eq('text/plain');
|
|
325
325
|
callback();
|
|
326
326
|
done();
|
|
@@ -330,11 +330,11 @@ describe('RouterDataSender', function () {
|
|
|
330
330
|
S.send(res, data);
|
|
331
331
|
});
|
|
332
332
|
|
|
333
|
-
it('should allow override the "
|
|
333
|
+
it('should allow override the "Content-Type" header for a string value', function (done) {
|
|
334
334
|
const data = 'text';
|
|
335
335
|
const res = createResponseMock();
|
|
336
336
|
const contentType = 'custom/type';
|
|
337
|
-
res.setHeader('
|
|
337
|
+
res.setHeader('Content-Type', contentType);
|
|
338
338
|
const writable = new Writable();
|
|
339
339
|
const chunks = [];
|
|
340
340
|
writable._write = function (chunk, encoding, done) {
|
|
@@ -344,7 +344,7 @@ describe('RouterDataSender', function () {
|
|
|
344
344
|
writable._final = function (callback) {
|
|
345
345
|
const sentData = Buffer.concat(chunks).toString('utf-8');
|
|
346
346
|
expect(sentData).to.be.eq(data);
|
|
347
|
-
const ct = res.getHeader('
|
|
347
|
+
const ct = res.getHeader('Content-Type');
|
|
348
348
|
expect(ct).to.be.eq(contentType);
|
|
349
349
|
callback();
|
|
350
350
|
done();
|
|
@@ -63,7 +63,7 @@ export class RouterErrorSender extends DebuggableService {
|
|
|
63
63
|
console.error(error);
|
|
64
64
|
}
|
|
65
65
|
response.statusCode = statusCode;
|
|
66
|
-
response.setHeader('
|
|
66
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
67
67
|
response.end(JSON.stringify(body, null, 2), 'utf-8');
|
|
68
68
|
debug(
|
|
69
69
|
'%s error has been sent for the request %s %v.',
|
|
@@ -83,7 +83,7 @@ export class RouterErrorSender extends DebuggableService {
|
|
|
83
83
|
send404(request, response) {
|
|
84
84
|
const debug = this.getDebuggerFor(this.send404);
|
|
85
85
|
response.statusCode = 404;
|
|
86
|
-
response.setHeader('
|
|
86
|
+
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
87
87
|
response.end('404 Not Found', 'utf-8');
|
|
88
88
|
debug(
|
|
89
89
|
'404 error has been sent for the request %s %v.',
|
|
@@ -25,7 +25,7 @@ describe('RouterErrorSender', function () {
|
|
|
25
25
|
const data = JSON.parse(json);
|
|
26
26
|
expect(data).to.be.eql({error: {message: 'Unauthorized'}});
|
|
27
27
|
expect(res.statusCode).to.be.eq(401);
|
|
28
|
-
const ct = res.getHeader('
|
|
28
|
+
const ct = res.getHeader('Content-Type');
|
|
29
29
|
expect(ct).to.be.eq('application/json; charset=utf-8');
|
|
30
30
|
callback();
|
|
31
31
|
done();
|
|
@@ -57,7 +57,7 @@ describe('RouterErrorSender', function () {
|
|
|
57
57
|
expect(data.error).not.to.have.property('shouldNotBeExposedProp');
|
|
58
58
|
expect(data).to.be.eql(expectedData);
|
|
59
59
|
expect(res.statusCode).to.be.eq(401);
|
|
60
|
-
const ct = res.getHeader('
|
|
60
|
+
const ct = res.getHeader('Content-Type');
|
|
61
61
|
expect(ct).to.be.eq('application/json; charset=utf-8');
|
|
62
62
|
callback();
|
|
63
63
|
done();
|
|
@@ -82,7 +82,7 @@ describe('RouterErrorSender', function () {
|
|
|
82
82
|
const body = Buffer.concat(chunks).toString('utf-8');
|
|
83
83
|
expect(body).to.be.eql('404 Not Found');
|
|
84
84
|
expect(res.statusCode).to.be.eq(404);
|
|
85
|
-
const ct = res.getHeader('
|
|
85
|
+
const ct = res.getHeader('Content-Type');
|
|
86
86
|
expect(ct).to.be.eq('text/plain; charset=utf-8');
|
|
87
87
|
callback();
|
|
88
88
|
done();
|
package/src/trie-router.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {RouterBranch} from './branch/index.js';
|
|
2
|
+
import {RouteRegistry} from './route/index.js';
|
|
2
3
|
import {RequestParser} from './parsers/index.js';
|
|
3
4
|
import {RequestContext} from './request-context.js';
|
|
4
5
|
import {ServerResponse, IncomingMessage} from 'http';
|
|
5
6
|
import {DebuggableService} from './debuggable-service.js';
|
|
6
7
|
import {TrieRouterOptions} from './trie-router-options.js';
|
|
7
|
-
import {HttpMethod, RouteRegistry} from './route/index.js';
|
|
8
8
|
import {RouterDataSender, RouterErrorSender} from './senders/index.js';
|
|
9
9
|
import {isServiceContainer, ServiceContainer} from '@e22m4u/js-service';
|
|
10
10
|
import {isPromise, isResponseSent, getRequestPathname} from './utils/index.js';
|
|
@@ -83,7 +83,7 @@ export class TrieRouter extends DebuggableService {
|
|
|
83
83
|
* Example:
|
|
84
84
|
* ```js
|
|
85
85
|
* const router = new TrieRouter();
|
|
86
|
-
* const apiBranch = router.createBranch({path: 'api'});
|
|
86
|
+
* const apiBranch = router.createBranch({path: '/api'});
|
|
87
87
|
*
|
|
88
88
|
* // GET /api/hello
|
|
89
89
|
* apiBranch.defineRoute({
|
|
@@ -165,27 +165,6 @@ export class TrieRouter extends DebuggableService {
|
|
|
165
165
|
const resolved =
|
|
166
166
|
this.getService(RouteRegistry).matchRouteByRequest(request);
|
|
167
167
|
if (!resolved) {
|
|
168
|
-
// обработка метода OPTIONS выполняется автоматически
|
|
169
|
-
// перед отправкой ошибки 404, если для пути запроса
|
|
170
|
-
// имеются другие методы, то вместо ошибки 404 будет
|
|
171
|
-
// отправлен ответ с "Allow*" заголовками
|
|
172
|
-
if (request.method.toUpperCase() === HttpMethod.OPTIONS) {
|
|
173
|
-
const allowedMethods =
|
|
174
|
-
this.getService(RouteRegistry).getAllowedMethodsForRequestPath(
|
|
175
|
-
requestPath,
|
|
176
|
-
);
|
|
177
|
-
if (allowedMethods.length > 0) {
|
|
178
|
-
debug('Auto-handling OPTIONS request.');
|
|
179
|
-
if (!allowedMethods.includes('OPTIONS')) {
|
|
180
|
-
allowedMethods.push('OPTIONS');
|
|
181
|
-
}
|
|
182
|
-
const allowHeader = allowedMethods.join(', ');
|
|
183
|
-
response.statusCode = 204;
|
|
184
|
-
response.setHeader('Allow', allowHeader);
|
|
185
|
-
response.end();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
168
|
debug(
|
|
190
169
|
'No route found for the request %s %v.',
|
|
191
170
|
request.method,
|
package/src/trie-router.spec.js
CHANGED
|
@@ -1625,64 +1625,6 @@ describe('TrieRouter', function () {
|
|
|
1625
1625
|
});
|
|
1626
1626
|
});
|
|
1627
1627
|
});
|
|
1628
|
-
|
|
1629
|
-
describe('OPTIONS method handling', function () {
|
|
1630
|
-
it('should automatically return 204 with specific headers for an unhandled OPTIONS request', async function () {
|
|
1631
|
-
const router = new TrieRouter();
|
|
1632
|
-
router.defineRoute({
|
|
1633
|
-
method: HttpMethod.GET,
|
|
1634
|
-
path: '/api/resource',
|
|
1635
|
-
handler: () => 'OK',
|
|
1636
|
-
});
|
|
1637
|
-
router.defineRoute({
|
|
1638
|
-
method: HttpMethod.POST,
|
|
1639
|
-
path: '/api/resource',
|
|
1640
|
-
handler: () => 'OK',
|
|
1641
|
-
});
|
|
1642
|
-
const req = createRequestMock({
|
|
1643
|
-
method: HttpMethod.OPTIONS,
|
|
1644
|
-
path: '/api/resource',
|
|
1645
|
-
});
|
|
1646
|
-
const res = createResponseMock();
|
|
1647
|
-
await router.handleRequest(req, res);
|
|
1648
|
-
expect(res.statusCode).to.be.eq(204);
|
|
1649
|
-
expect(res.getHeader('Allow')).to.be.eq('GET, POST, OPTIONS');
|
|
1650
|
-
});
|
|
1651
|
-
|
|
1652
|
-
it('should execute a custom OPTIONS handler when it is explicitly defined', async function () {
|
|
1653
|
-
const router = new TrieRouter();
|
|
1654
|
-
let customOptionsCalled = false;
|
|
1655
|
-
router.defineRoute({
|
|
1656
|
-
method: HttpMethod.OPTIONS,
|
|
1657
|
-
path: '/api/resource',
|
|
1658
|
-
handler: () => {
|
|
1659
|
-
customOptionsCalled = true;
|
|
1660
|
-
return 'Custom OPTIONS response';
|
|
1661
|
-
},
|
|
1662
|
-
});
|
|
1663
|
-
const req = createRequestMock({
|
|
1664
|
-
method: HttpMethod.OPTIONS,
|
|
1665
|
-
path: '/api/resource',
|
|
1666
|
-
});
|
|
1667
|
-
const res = createResponseMock();
|
|
1668
|
-
await router.handleRequest(req, res);
|
|
1669
|
-
const body = await res.getBody();
|
|
1670
|
-
expect(customOptionsCalled).to.be.true;
|
|
1671
|
-
expect(res.statusCode).to.be.eq(200);
|
|
1672
|
-
expect(body).to.be.eq('Custom OPTIONS response');
|
|
1673
|
-
});
|
|
1674
|
-
|
|
1675
|
-
it('should return 404 for an OPTIONS request if the path does not exist for any method', async function () {
|
|
1676
|
-
const router = new TrieRouter();
|
|
1677
|
-
const req = createRequestMock({
|
|
1678
|
-
method: HttpMethod.OPTIONS,
|
|
1679
|
-
path: '/unknown',
|
|
1680
|
-
});
|
|
1681
|
-
const res = createResponseMock();
|
|
1682
|
-
await router.handleRequest(req, res);
|
|
1683
|
-
expect(res.statusCode).to.be.eq(404);
|
|
1684
|
-
});
|
|
1685
|
-
});
|
|
1686
1628
|
});
|
|
1687
1629
|
|
|
1688
1630
|
describe('addHook', function () {
|