@ndiinginc/router 4.2.0 → 4.4.0
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/package.json +1 -1
- package/src/app.js +76 -76
- package/src/cache.js +24 -24
- package/src/error.js +26 -26
- package/src/index.js +11 -9
- package/src/middleware/cors.js +139 -0
- package/src/{json.js → middleware/json.js} +55 -55
- package/src/node.js +88 -81
- package/src/pattern.js +34 -34
- package/src/request.js +37 -37
- package/src/response.js +13 -13
- package/src/router.js +165 -164
- package/src/util.js +27 -27
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
const { Router } = require("./router.js");
|
|
3
|
-
const { Request } = require("./request.js");
|
|
4
|
-
const { Response } = require("./response.js");
|
|
5
|
-
const { ErrorResponse } = require("./error.js");
|
|
6
|
-
|
|
7
|
-
/**@type {import('http').ServerOptions}*/
|
|
8
|
-
const SERVER_OPTIONS = {
|
|
9
|
-
IncomingMessage: Request,
|
|
10
|
-
ServerResponse: Response,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
class App extends Router {
|
|
14
|
-
async withTimeout(handlerPromise, delay) {
|
|
15
|
-
let timeout;
|
|
16
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
17
|
-
timeout = setTimeout(() => reject(ErrorResponse.status(504)), delay);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
await Promise.race([handlerPromise, timeoutPromise]).finally(() => clearTimeout(timeout));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {import('http').IncomingMessage} req
|
|
25
|
-
* @param {import('http').ServerResponse} res
|
|
26
|
-
*/
|
|
27
|
-
async handle(req, res) {
|
|
28
|
-
const { params, handlers, errorHandlers } = this.find(req.method, req.path);
|
|
29
|
-
req.params = params;
|
|
30
|
-
|
|
31
|
-
let err;
|
|
32
|
-
try {
|
|
33
|
-
const handlerPromise = (async () => {
|
|
34
|
-
for (const handler of handlers) {
|
|
35
|
-
if (res.writableEnded) break;
|
|
36
|
-
await handler(req, res);
|
|
37
|
-
}
|
|
38
|
-
})();
|
|
39
|
-
|
|
40
|
-
if (req.timeout) await this.withTimeout(handlerPromise, req.timeout);
|
|
41
|
-
else await handlerPromise;
|
|
42
|
-
} catch (error) {
|
|
43
|
-
err = error;
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
for (const handler of errorHandlers) {
|
|
47
|
-
if (res.writableEnded) break;
|
|
48
|
-
await handler(err, req, res);
|
|
49
|
-
}
|
|
50
|
-
} catch (error) {
|
|
51
|
-
err = new Error(error.message, { cause: err });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!res.writableEnded) {
|
|
56
|
-
if (!err) err = ErrorResponse.status(404);
|
|
57
|
-
|
|
58
|
-
if (!res.statusCode || res.statusCode < 400) {
|
|
59
|
-
res.statusCode = err.statusCode || 500;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
res.json({ error: { message: err.message, stack: err.stack } });
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
listen(...params) {
|
|
67
|
-
const requestListener = (req, res) => this.handle(req, res);
|
|
68
|
-
const server = http.createServer(SERVER_OPTIONS, requestListener);
|
|
69
|
-
server.listen(...params);
|
|
70
|
-
return server;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
App.SERVER_OPTIONS = SERVER_OPTIONS;
|
|
75
|
-
|
|
76
|
-
exports.App = App;
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
const { Router } = require("./router.js");
|
|
3
|
+
const { Request } = require("./request.js");
|
|
4
|
+
const { Response } = require("./response.js");
|
|
5
|
+
const { ErrorResponse } = require("./error.js");
|
|
6
|
+
|
|
7
|
+
/**@type {import('http').ServerOptions}*/
|
|
8
|
+
const SERVER_OPTIONS = {
|
|
9
|
+
IncomingMessage: Request,
|
|
10
|
+
ServerResponse: Response,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
class App extends Router {
|
|
14
|
+
async withTimeout(handlerPromise, delay) {
|
|
15
|
+
let timeout;
|
|
16
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
17
|
+
timeout = setTimeout(() => reject(ErrorResponse.status(504)), delay);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await Promise.race([handlerPromise, timeoutPromise]).finally(() => clearTimeout(timeout));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {import('http').IncomingMessage} req
|
|
25
|
+
* @param {import('http').ServerResponse} res
|
|
26
|
+
*/
|
|
27
|
+
async handle(req, res) {
|
|
28
|
+
const { params, handlers, errorHandlers } = this.find(req.method, req.path);
|
|
29
|
+
req.params = params;
|
|
30
|
+
|
|
31
|
+
let err;
|
|
32
|
+
try {
|
|
33
|
+
const handlerPromise = (async () => {
|
|
34
|
+
for (const handler of handlers) {
|
|
35
|
+
if (res.writableEnded) break;
|
|
36
|
+
await handler(req, res);
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
if (req.timeout) await this.withTimeout(handlerPromise, req.timeout);
|
|
41
|
+
else await handlerPromise;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
err = error;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
for (const handler of errorHandlers) {
|
|
47
|
+
if (res.writableEnded) break;
|
|
48
|
+
await handler(err, req, res);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
err = new Error(error.message, { cause: err });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!res.writableEnded) {
|
|
56
|
+
if (!err) err = ErrorResponse.status(404);
|
|
57
|
+
|
|
58
|
+
if (!res.statusCode || res.statusCode < 400) {
|
|
59
|
+
res.statusCode = err.statusCode || 500;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
res.json({ error: { message: err.message, stack: err.stack } });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
listen(...params) {
|
|
67
|
+
const requestListener = (req, res) => this.handle(req, res);
|
|
68
|
+
const server = http.createServer(SERVER_OPTIONS, requestListener);
|
|
69
|
+
server.listen(...params);
|
|
70
|
+
return server;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
App.SERVER_OPTIONS = SERVER_OPTIONS;
|
|
75
|
+
|
|
76
|
+
exports.App = App;
|
package/src/cache.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
class Cache {
|
|
2
|
-
constructor(size) {
|
|
3
|
-
this.size = size;
|
|
4
|
-
this.map = new Map();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
get(key) {
|
|
8
|
-
if (!this.map.has(key)) return undefined;
|
|
9
|
-
const value = this.map.get(key);
|
|
10
|
-
this.map.delete(key);
|
|
11
|
-
this.map.set(key, value);
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
set(key, value) {
|
|
16
|
-
if (this.map.has(key)) this.map.delete(key);
|
|
17
|
-
else if (this.map.size >= this.size) {
|
|
18
|
-
this.map.delete(this.map.keys().next().value);
|
|
19
|
-
}
|
|
20
|
-
this.map.set(key, value);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
exports.Cache = Cache;
|
|
1
|
+
class Cache {
|
|
2
|
+
constructor(size) {
|
|
3
|
+
this.size = size;
|
|
4
|
+
this.map = new Map();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
get(key) {
|
|
8
|
+
if (!this.map.has(key)) return undefined;
|
|
9
|
+
const value = this.map.get(key);
|
|
10
|
+
this.map.delete(key);
|
|
11
|
+
this.map.set(key, value);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
set(key, value) {
|
|
16
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
17
|
+
else if (this.map.size >= this.size) {
|
|
18
|
+
this.map.delete(this.map.keys().next().value);
|
|
19
|
+
}
|
|
20
|
+
this.map.set(key, value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.Cache = Cache;
|
package/src/error.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
|
|
3
|
-
// Client error responses (400 – 499)
|
|
4
|
-
// Server error responses (500 – 599)
|
|
5
|
-
class ErrorResponse extends Error {
|
|
6
|
-
constructor({ statusCode, message } = {}, options) {
|
|
7
|
-
super(message, options);
|
|
8
|
-
|
|
9
|
-
this.name = this.constructor.name;
|
|
10
|
-
|
|
11
|
-
if (Error.captureStackTrace) {
|
|
12
|
-
Error.captureStackTrace(this, this.constructor);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
this.statusCode = statusCode;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
static status(statusCode) {
|
|
19
|
-
return new ErrorResponse({
|
|
20
|
-
statusCode,
|
|
21
|
-
message: http.STATUS_CODES[statusCode],
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.ErrorResponse = ErrorResponse;
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
|
|
3
|
+
// Client error responses (400 – 499)
|
|
4
|
+
// Server error responses (500 – 599)
|
|
5
|
+
class ErrorResponse extends Error {
|
|
6
|
+
constructor({ statusCode, message } = {}, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
|
|
11
|
+
if (Error.captureStackTrace) {
|
|
12
|
+
Error.captureStackTrace(this, this.constructor);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static status(statusCode) {
|
|
19
|
+
return new ErrorResponse({
|
|
20
|
+
statusCode,
|
|
21
|
+
message: http.STATUS_CODES[statusCode],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
exports.ErrorResponse = ErrorResponse;
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
const { inspect } = require("node:util");
|
|
2
|
-
const { json } = require("./json.js");
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
App.
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
const { inspect } = require("node:util");
|
|
2
|
+
const { json } = require("./middleware/json.js");
|
|
3
|
+
const { cors } = require("./middleware/cors.js");
|
|
4
|
+
const { App } = require("./app.js");
|
|
5
|
+
const { Router } = require("./router.js");
|
|
6
|
+
|
|
7
|
+
App.Router = Router;
|
|
8
|
+
App.json = json;
|
|
9
|
+
App.cors = cors;
|
|
10
|
+
|
|
11
|
+
module.exports = App;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
function pattern(string) {
|
|
2
|
+
return new RegExp(
|
|
3
|
+
`(${String(string)
|
|
4
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
5
|
+
.replace(/ /g, "")
|
|
6
|
+
.replace(/,/g, "|")
|
|
7
|
+
.replace(/\\\*/g, ".*")})`,
|
|
8
|
+
"i",
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef CORSOptions
|
|
14
|
+
* @property {String} [origin="*"]
|
|
15
|
+
* @property {Boolean} [credentials=false]
|
|
16
|
+
* @property {String|Array} headers
|
|
17
|
+
* @property {Number} [maxAge=0]
|
|
18
|
+
* @property {String|Array} methods
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {CORSOptions} options
|
|
23
|
+
*/
|
|
24
|
+
function cors({ origin = "*", credentials = false, headers, maxAge = 0, methods } = {}) {
|
|
25
|
+
const HEADERS_PATTERN = headers && pattern(headers);
|
|
26
|
+
const METHODS_PATTERN = methods && pattern(methods);
|
|
27
|
+
const ORIGIN_PATTERN = pattern(origin);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('http').ServerResponse} res
|
|
31
|
+
*/
|
|
32
|
+
function setAllowCredentials(res) {
|
|
33
|
+
if (credentials) {
|
|
34
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {import('http').IncomingMessage} req
|
|
40
|
+
* @param {import('http').ServerResponse} res
|
|
41
|
+
*/
|
|
42
|
+
function setAllowHeaders(req, res) {
|
|
43
|
+
const reqHeaders = req.headers["access-control-request-headers"];
|
|
44
|
+
if (!reqHeaders) return;
|
|
45
|
+
|
|
46
|
+
let value;
|
|
47
|
+
if (headers === "*" && !credentials) {
|
|
48
|
+
value = "*";
|
|
49
|
+
} else if (Array.isArray(headers) && HEADERS_PATTERN && HEADERS_PATTERN.test(reqHeaders)) {
|
|
50
|
+
value = headers;
|
|
51
|
+
}
|
|
52
|
+
if (value) res.setHeader("Access-Control-Allow-Headers", value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('http').IncomingMessage} req
|
|
57
|
+
* @param {import('http').ServerResponse} res
|
|
58
|
+
*/
|
|
59
|
+
function setAllowMethods(req, res) {
|
|
60
|
+
const reqMethod = req.headers["access-control-request-method"];
|
|
61
|
+
if (!reqMethod) return;
|
|
62
|
+
|
|
63
|
+
let value;
|
|
64
|
+
if (methods === "*" && !credentials) {
|
|
65
|
+
value = "*";
|
|
66
|
+
} else if (Array.isArray(methods) && METHODS_PATTERN && METHODS_PATTERN.test(reqMethod)) {
|
|
67
|
+
value = methods;
|
|
68
|
+
}
|
|
69
|
+
if (value) res.setHeader("Access-Control-Allow-Methods", value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {import('http').IncomingMessage} req
|
|
74
|
+
* @param {import('http').ServerResponse} res
|
|
75
|
+
*/
|
|
76
|
+
function setAllowOrigin(req, res) {
|
|
77
|
+
const reqOrigin = req.headers["origin"];
|
|
78
|
+
|
|
79
|
+
let value;
|
|
80
|
+
let explicit;
|
|
81
|
+
if (origin === "*") {
|
|
82
|
+
if (!credentials) value = "*";
|
|
83
|
+
else {
|
|
84
|
+
value = reqOrigin;
|
|
85
|
+
explicit = true;
|
|
86
|
+
}
|
|
87
|
+
} else if (origin === null) {
|
|
88
|
+
value = "null";
|
|
89
|
+
} else if (typeof origin === "string" && ORIGIN_PATTERN.test(reqOrigin)) {
|
|
90
|
+
value = origin;
|
|
91
|
+
explicit = true;
|
|
92
|
+
}
|
|
93
|
+
if (value) res.setHeader("Access-Control-Allow-Origin", value);
|
|
94
|
+
if (explicit) res.appendHeader("Vary", "Origin");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {import('http').ServerResponse} res
|
|
99
|
+
*/
|
|
100
|
+
function setExposeHeaders(res) {
|
|
101
|
+
let value;
|
|
102
|
+
if (headers === "*" && !credentials) {
|
|
103
|
+
value = "*";
|
|
104
|
+
} else if (Array.isArray(headers)) {
|
|
105
|
+
value = headers;
|
|
106
|
+
}
|
|
107
|
+
if (value) res.setHeader("Access-Control-Expose-Headers", value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {import('http').ServerResponse} res
|
|
112
|
+
*/
|
|
113
|
+
function setMaxAge(res) {
|
|
114
|
+
if (maxAge > 0) {
|
|
115
|
+
res.setHeader("Access-Control-Max-Age", maxAge);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {import('http').IncomingMessage} req
|
|
121
|
+
* @param {import('http').ServerResponse} res
|
|
122
|
+
*/
|
|
123
|
+
return (req, res) => {
|
|
124
|
+
setAllowCredentials(res);
|
|
125
|
+
setAllowOrigin(req, res);
|
|
126
|
+
if (req.method === "OPTIONS") {
|
|
127
|
+
setAllowHeaders(req, res);
|
|
128
|
+
setAllowMethods(req, res);
|
|
129
|
+
setMaxAge(res);
|
|
130
|
+
|
|
131
|
+
res.statusCode = 204;
|
|
132
|
+
res.setHeader("Content-Length", "0");
|
|
133
|
+
res.end();
|
|
134
|
+
} else {
|
|
135
|
+
setExposeHeaders(res);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
exports.cors = cors;
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
|
|
3
|
-
// middleware
|
|
4
|
-
function json({ limit = 100 * 1024 } = {}) {
|
|
5
|
-
const reviver = (key, value) => {
|
|
6
|
-
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
return value;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
return async (req, res) => {
|
|
13
|
-
if (!["POST", "PUT", "PATCH", "QUERY"].includes(req.method)) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const contentType = req.headers["content-type"];
|
|
18
|
-
if (!contentType || !contentType.includes("application/json")) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const contentLength = parseInt(req.headers["content-length"], 10);
|
|
23
|
-
if (contentLength && contentLength > limit) {
|
|
24
|
-
res.statusCode = 413;
|
|
25
|
-
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let chunkLength = 0;
|
|
29
|
-
|
|
30
|
-
const chunks = [];
|
|
31
|
-
|
|
32
|
-
for await (const chunk of req) {
|
|
33
|
-
chunkLength += chunk.length;
|
|
34
|
-
if (chunkLength > limit) {
|
|
35
|
-
res.statusCode = 413;
|
|
36
|
-
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
37
|
-
}
|
|
38
|
-
chunks.push(chunk);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (chunks.length === 0) {
|
|
42
|
-
req.body = null;
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
req.body = JSON.parse(Buffer.concat(chunks), reviver);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
res.statusCode = 400;
|
|
50
|
-
throw new Error("Invalid JSON body");
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
exports.json = json;
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
|
|
3
|
+
// middleware
|
|
4
|
+
function json({ limit = 100 * 1024 } = {}) {
|
|
5
|
+
const reviver = (key, value) => {
|
|
6
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return async (req, res) => {
|
|
13
|
+
if (!["POST", "PUT", "PATCH", "QUERY"].includes(req.method)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const contentType = req.headers["content-type"];
|
|
18
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const contentLength = parseInt(req.headers["content-length"], 10);
|
|
23
|
+
if (contentLength && contentLength > limit) {
|
|
24
|
+
res.statusCode = 413;
|
|
25
|
+
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let chunkLength = 0;
|
|
29
|
+
|
|
30
|
+
const chunks = [];
|
|
31
|
+
|
|
32
|
+
for await (const chunk of req) {
|
|
33
|
+
chunkLength += chunk.length;
|
|
34
|
+
if (chunkLength > limit) {
|
|
35
|
+
res.statusCode = 413;
|
|
36
|
+
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
37
|
+
}
|
|
38
|
+
chunks.push(chunk);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (chunks.length === 0) {
|
|
42
|
+
req.body = null;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
req.body = JSON.parse(Buffer.concat(chunks), reviver);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
res.statusCode = 400;
|
|
50
|
+
throw new Error("Invalid JSON body");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.json = json;
|
package/src/node.js
CHANGED
|
@@ -1,81 +1,88 @@
|
|
|
1
|
-
const { Pattern } = require("./pattern.js");
|
|
2
|
-
|
|
3
|
-
class Node {
|
|
4
|
-
constructor(segment = "") {
|
|
5
|
-
this.segment = segment;
|
|
6
|
-
this.
|
|
7
|
-
|
|
8
|
-
this.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
["
|
|
13
|
-
["
|
|
14
|
-
["
|
|
15
|
-
["
|
|
16
|
-
["
|
|
17
|
-
["
|
|
18
|
-
["
|
|
19
|
-
["
|
|
20
|
-
["
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
const { Pattern } = require("./pattern.js");
|
|
2
|
+
|
|
3
|
+
class Node {
|
|
4
|
+
constructor(segment = "") {
|
|
5
|
+
this.segment = segment;
|
|
6
|
+
this.hasRegExp = segment.startsWith(":") || segment.indexOf("*") !== -1;
|
|
7
|
+
this.hasRegExpChildren = false;
|
|
8
|
+
this.pattern = this.hasRegExp && new Pattern(segment);
|
|
9
|
+
/**@type {Map<string,Node>}*/
|
|
10
|
+
this.children = new Map();
|
|
11
|
+
this.handlers = new Map([
|
|
12
|
+
["ALL", []],
|
|
13
|
+
["GET", []],
|
|
14
|
+
["QUERY", []],
|
|
15
|
+
["HEAD", []],
|
|
16
|
+
["OPTIONS", []],
|
|
17
|
+
["TRACE", []],
|
|
18
|
+
["PUT", []],
|
|
19
|
+
["DELETE", []],
|
|
20
|
+
["POST", []],
|
|
21
|
+
["PATCH", []],
|
|
22
|
+
["CONNECT", []],
|
|
23
|
+
]);
|
|
24
|
+
// debug
|
|
25
|
+
Object.defineProperty(this, "handlers", { configurable: true, enumerable: false, writable: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
add(segment) {
|
|
29
|
+
if (!this.children.has(segment)) {
|
|
30
|
+
const node = new Node(segment);
|
|
31
|
+
if (node.hasRegExp) this.hasRegExpChildren = true; // node.hasRegExp;
|
|
32
|
+
this.children.set(segment, node);
|
|
33
|
+
}
|
|
34
|
+
return this.children.get(segment);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Node} node
|
|
39
|
+
*/
|
|
40
|
+
merge(node, offset) {
|
|
41
|
+
for (const [method, handlers] of node.handlers) {
|
|
42
|
+
for (const handler of handlers) {
|
|
43
|
+
handler.seq += offset;
|
|
44
|
+
this.handlers.get(method).push(handler);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [segment, child] of node.children) {
|
|
49
|
+
if (child.hasRegExp) this.hasRegExpChildren = true; // child.hasRegExp;
|
|
50
|
+
if (!this.children.has(segment)) {
|
|
51
|
+
child.setSeqOffset(offset);
|
|
52
|
+
this.children.set(segment, child);
|
|
53
|
+
} else {
|
|
54
|
+
this.children.get(segment).merge(child, offset);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setSeqOffset(offset) {
|
|
60
|
+
for (const [, handlers] of this.handlers) {
|
|
61
|
+
for (const handler of handlers) {
|
|
62
|
+
handler.seq += offset;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const [, child] of this.children) {
|
|
67
|
+
child.setSeqOffset(offset);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getMaxSeq() {
|
|
72
|
+
let max = -1;
|
|
73
|
+
|
|
74
|
+
for (const [, handlers] of this.handlers) {
|
|
75
|
+
for (const handler of handlers) {
|
|
76
|
+
max = Math.max(max, handler.seq);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const [, child] of this.children) {
|
|
81
|
+
max = Math.max(max, child.getMaxSeq());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return max;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.Node = Node;
|
package/src/pattern.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef PatternExecResult
|
|
3
|
-
* @property {number} index
|
|
4
|
-
* @property {string} input
|
|
5
|
-
* @property {Record<string, string>} [groups]
|
|
6
|
-
* @property {number} length
|
|
7
|
-
*/
|
|
8
|
-
class Pattern {
|
|
9
|
-
constructor(input) {
|
|
10
|
-
const pattern = `^${input
|
|
11
|
-
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
12
|
-
.replace(/:(\w+)/g, "(?<$1>[^/]+)")
|
|
13
|
-
.replace(
|
|
14
|
-
Object.defineProperty(this, "regex", {
|
|
15
|
-
value: new RegExp(pattern),
|
|
16
|
-
configurable: true,
|
|
17
|
-
enumerable: false,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {string} input
|
|
23
|
-
* @returns {PatternExecResult | null}
|
|
24
|
-
*/
|
|
25
|
-
exec(input) {
|
|
26
|
-
return this.regex.exec(input);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test(input) {
|
|
30
|
-
return this.regex.test(input);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
exports.Pattern = Pattern;
|
|
1
|
+
/**
|
|
2
|
+
* @typedef PatternExecResult
|
|
3
|
+
* @property {number} index
|
|
4
|
+
* @property {string} input
|
|
5
|
+
* @property {Record<string, string>} [groups]
|
|
6
|
+
* @property {number} length
|
|
7
|
+
*/
|
|
8
|
+
class Pattern {
|
|
9
|
+
constructor(input) {
|
|
10
|
+
const pattern = `^${input
|
|
11
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
12
|
+
.replace(/:(\w+)/g, "(?<$1>[^/]+)")
|
|
13
|
+
.replace(/\\\*/g, ".*")}$`;
|
|
14
|
+
Object.defineProperty(this, "regex", {
|
|
15
|
+
value: new RegExp(pattern),
|
|
16
|
+
configurable: true,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} input
|
|
23
|
+
* @returns {PatternExecResult | null}
|
|
24
|
+
*/
|
|
25
|
+
exec(input) {
|
|
26
|
+
return this.regex.exec(input);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test(input) {
|
|
30
|
+
return this.regex.test(input);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exports.Pattern = Pattern;
|
package/src/request.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
const { parseUrl, parseQueryString } = require("./util.js");
|
|
3
|
-
|
|
4
|
-
class Request extends http.IncomingMessage {
|
|
5
|
-
_path;
|
|
6
|
-
_parsed;
|
|
7
|
-
_query;
|
|
8
|
-
params={};
|
|
9
|
-
timeout=0;
|
|
10
|
-
|
|
11
|
-
get path() {
|
|
12
|
-
if (this._path) return this._path;
|
|
13
|
-
|
|
14
|
-
if (this.url.indexOf("?") === -1) {
|
|
15
|
-
this._path = this.url;
|
|
16
|
-
return this._path;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
this._parsed = parseUrl(this.url)
|
|
20
|
-
this._path = this._parsed.path;
|
|
21
|
-
return this._path;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get query() {
|
|
25
|
-
if (this._query) return this._query;
|
|
26
|
-
|
|
27
|
-
if (!this._parsed) {
|
|
28
|
-
this._query = {};
|
|
29
|
-
return this._query;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
this._query = parseQueryString(this._parsed.queryString);
|
|
33
|
-
return this._query;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
exports.Request = Request;
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
const { parseUrl, parseQueryString } = require("./util.js");
|
|
3
|
+
|
|
4
|
+
class Request extends http.IncomingMessage {
|
|
5
|
+
_path;
|
|
6
|
+
_parsed;
|
|
7
|
+
_query;
|
|
8
|
+
params = {};
|
|
9
|
+
timeout = 0;
|
|
10
|
+
|
|
11
|
+
get path() {
|
|
12
|
+
if (this._path) return this._path;
|
|
13
|
+
|
|
14
|
+
if (this.url.indexOf("?") === -1) {
|
|
15
|
+
this._path = this.url;
|
|
16
|
+
return this._path;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this._parsed = parseUrl(this.url);
|
|
20
|
+
this._path = this._parsed.path;
|
|
21
|
+
return this._path;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get query() {
|
|
25
|
+
if (this._query) return this._query;
|
|
26
|
+
|
|
27
|
+
if (!this._parsed) {
|
|
28
|
+
this._query = {};
|
|
29
|
+
return this._query;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this._query = parseQueryString(this._parsed.queryString);
|
|
33
|
+
return this._query;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.Request = Request;
|
package/src/response.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
|
|
3
|
-
class Response extends http.ServerResponse {
|
|
4
|
-
send(chunk) {
|
|
5
|
-
this.end(chunk);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
json(value) {
|
|
9
|
-
this.setHeader("Content-Type","application/json")
|
|
10
|
-
this.end(JSON.stringify(value));
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
exports.Response = Response;
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
|
|
3
|
+
class Response extends http.ServerResponse {
|
|
4
|
+
send(chunk) {
|
|
5
|
+
this.end(chunk);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
json(value) {
|
|
9
|
+
this.setHeader("Content-Type", "application/json");
|
|
10
|
+
this.end(JSON.stringify(value));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.Response = Response;
|
package/src/router.js
CHANGED
|
@@ -1,164 +1,165 @@
|
|
|
1
|
-
const { Node } = require("./node.js");
|
|
2
|
-
const { Cache } = require("./cache.js");
|
|
3
|
-
|
|
4
|
-
class Router {
|
|
5
|
-
constructor(options = {}) {
|
|
6
|
-
this.cacheSize = options.size ?? 1000;
|
|
7
|
-
|
|
8
|
-
this.root = new Node();
|
|
9
|
-
this.seq = 0;
|
|
10
|
-
this.cache = new Cache(this.cacheSize);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
split(path) {
|
|
14
|
-
return path.split("/").filter(Boolean);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
add(method, path, ...handlers) {
|
|
18
|
-
if (typeof path === "function") {
|
|
19
|
-
handlers = [path, ...handlers];
|
|
20
|
-
path = null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let node = this.root;
|
|
24
|
-
|
|
25
|
-
if (path !== null) {
|
|
26
|
-
for (const segment of this.split(path)) {
|
|
27
|
-
node = node.add(segment);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
for (const handler of handlers) {
|
|
32
|
-
if (handler.root) {
|
|
33
|
-
node.merge(handler.root, this.seq);
|
|
34
|
-
this.seq = node.getMaxSeq() + 1;
|
|
35
|
-
} else {
|
|
36
|
-
handler.seq = this.seq++;
|
|
37
|
-
node.handlers.get(method).push(handler);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
use(path, ...handlers) {
|
|
43
|
-
this.add("ALL", path, ...handlers);
|
|
44
|
-
return this;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
get(path, ...handlers) {
|
|
48
|
-
this.add("GET", path, ...handlers);
|
|
49
|
-
return this;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
query(path, ...handlers) {
|
|
53
|
-
this.add("QUERY", path, ...handlers);
|
|
54
|
-
return this;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
head(path, ...handlers) {
|
|
58
|
-
this.add("HEAD", path, ...handlers);
|
|
59
|
-
return this;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
options(path, ...handlers) {
|
|
63
|
-
this.add("OPTIONS", path, ...handlers);
|
|
64
|
-
return this;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
trace(path, ...handlers) {
|
|
68
|
-
this.add("TRACE", path, ...handlers);
|
|
69
|
-
return this;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
put(path, ...handlers) {
|
|
73
|
-
this.add("PUT", path, ...handlers);
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
delete(path, ...handlers) {
|
|
78
|
-
this.add("DELETE", path, ...handlers);
|
|
79
|
-
return this;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
post(path, ...handlers) {
|
|
83
|
-
this.add("POST", path, ...handlers);
|
|
84
|
-
return this;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
patch(path, ...handlers) {
|
|
88
|
-
this.add("PATCH", path, ...handlers);
|
|
89
|
-
return this;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
connect(path, ...handlers) {
|
|
93
|
-
this.add("CONNECT", path, ...handlers);
|
|
94
|
-
return this;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @param {'GET'|'QUERY'|'HEAD'|'OPTIONS'|'TRACE'|'PUT'|'DELETE'|'POST'|'PATCH'|'CONNECT'} method
|
|
99
|
-
* @param {String} path
|
|
100
|
-
* @returns {{params: {};handlers: any[];errorHandlers: any[];}}
|
|
101
|
-
*/
|
|
102
|
-
find(method, path) {
|
|
103
|
-
const key = `${method} ${path}`;
|
|
104
|
-
|
|
105
|
-
const cached = this.cache.get(key);
|
|
106
|
-
if (cached) return cached;
|
|
107
|
-
|
|
108
|
-
let node = this.root;
|
|
109
|
-
|
|
110
|
-
let params = {};
|
|
111
|
-
const handlers = {};
|
|
112
|
-
const errorHandlers = {};
|
|
113
|
-
|
|
114
|
-
for (const input of this.split(path)) {
|
|
115
|
-
let matched = node.children.get(input);
|
|
116
|
-
|
|
117
|
-
if (!matched) {
|
|
118
|
-
for (const [, child] of node.children) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
1
|
+
const { Node } = require("./node.js");
|
|
2
|
+
const { Cache } = require("./cache.js");
|
|
3
|
+
|
|
4
|
+
class Router {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.cacheSize = options.size ?? 1000;
|
|
7
|
+
|
|
8
|
+
this.root = new Node();
|
|
9
|
+
this.seq = 0;
|
|
10
|
+
this.cache = new Cache(this.cacheSize);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
split(path) {
|
|
14
|
+
return path.split("/").filter(Boolean);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
add(method, path, ...handlers) {
|
|
18
|
+
if (typeof path === "function") {
|
|
19
|
+
handlers = [path, ...handlers];
|
|
20
|
+
path = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let node = this.root;
|
|
24
|
+
|
|
25
|
+
if (path !== null) {
|
|
26
|
+
for (const segment of this.split(path)) {
|
|
27
|
+
node = node.add(segment);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const handler of handlers) {
|
|
32
|
+
if (handler.root) {
|
|
33
|
+
node.merge(handler.root, this.seq);
|
|
34
|
+
this.seq = node.getMaxSeq() + 1;
|
|
35
|
+
} else {
|
|
36
|
+
handler.seq = this.seq++;
|
|
37
|
+
node.handlers.get(method).push(handler);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
use(path, ...handlers) {
|
|
43
|
+
this.add("ALL", path, ...handlers);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get(path, ...handlers) {
|
|
48
|
+
this.add("GET", path, ...handlers);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
query(path, ...handlers) {
|
|
53
|
+
this.add("QUERY", path, ...handlers);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
head(path, ...handlers) {
|
|
58
|
+
this.add("HEAD", path, ...handlers);
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
options(path, ...handlers) {
|
|
63
|
+
this.add("OPTIONS", path, ...handlers);
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
trace(path, ...handlers) {
|
|
68
|
+
this.add("TRACE", path, ...handlers);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
put(path, ...handlers) {
|
|
73
|
+
this.add("PUT", path, ...handlers);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
delete(path, ...handlers) {
|
|
78
|
+
this.add("DELETE", path, ...handlers);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
post(path, ...handlers) {
|
|
83
|
+
this.add("POST", path, ...handlers);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
patch(path, ...handlers) {
|
|
88
|
+
this.add("PATCH", path, ...handlers);
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
connect(path, ...handlers) {
|
|
93
|
+
this.add("CONNECT", path, ...handlers);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {'GET'|'QUERY'|'HEAD'|'OPTIONS'|'TRACE'|'PUT'|'DELETE'|'POST'|'PATCH'|'CONNECT'} method
|
|
99
|
+
* @param {String} path
|
|
100
|
+
* @returns {{params: {};handlers: any[];errorHandlers: any[];}}
|
|
101
|
+
*/
|
|
102
|
+
find(method, path) {
|
|
103
|
+
const key = `${method} ${path}`;
|
|
104
|
+
|
|
105
|
+
const cached = this.cache.get(key);
|
|
106
|
+
if (cached) return cached;
|
|
107
|
+
|
|
108
|
+
let node = this.root;
|
|
109
|
+
|
|
110
|
+
let params = {};
|
|
111
|
+
const handlers = {};
|
|
112
|
+
const errorHandlers = {};
|
|
113
|
+
|
|
114
|
+
for (const input of this.split(path)) {
|
|
115
|
+
let matched = node.children.get(input);
|
|
116
|
+
|
|
117
|
+
if (!matched && node.hasRegExpChildren) {
|
|
118
|
+
for (const [, child] of node.children) {
|
|
119
|
+
if (!child.hasRegExp) continue;
|
|
120
|
+
const match = child.pattern.exec(input);
|
|
121
|
+
if (match) {
|
|
122
|
+
matched = child;
|
|
123
|
+
Object.assign(params, match.groups);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!matched) {
|
|
130
|
+
node = null;
|
|
131
|
+
params = {};
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
node = matched;
|
|
136
|
+
|
|
137
|
+
for (const handler of node.handlers.get("ALL")) {
|
|
138
|
+
if (handler.length === 3) errorHandlers[handler.seq] = handler;
|
|
139
|
+
else handlers[handler.seq] = handler;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const handler of this.root.handlers.get("ALL")) {
|
|
144
|
+
if (handler.length === 3) errorHandlers[handler.seq] = handler;
|
|
145
|
+
else handlers[handler.seq] = handler;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (node) {
|
|
149
|
+
for (const handler of node.handlers.get(method)) {
|
|
150
|
+
handlers[handler.seq] = handler;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const result = {
|
|
155
|
+
params,
|
|
156
|
+
handlers: Object.values(handlers),
|
|
157
|
+
errorHandlers: Object.values(errorHandlers),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
this.cache.set(key, result);
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.Router = Router;
|
package/src/util.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
const URL_REGEX = /^([^?#]*)(\?([^#]*))?(#.*)?$/;
|
|
2
|
-
const QUERY_STRING_REGEX = /([^=&]*)?=([^&]*)?/g;
|
|
3
|
-
|
|
4
|
-
function parseQueryString(queryString) {
|
|
5
|
-
const result = {};
|
|
6
|
-
|
|
7
|
-
let match;
|
|
8
|
-
while ((match = QUERY_STRING_REGEX.exec(queryString)) !== null) {
|
|
9
|
-
const [, name = "", raw = ""] = match;
|
|
10
|
-
|
|
11
|
-
const value = decodeURIComponent(raw);
|
|
12
|
-
|
|
13
|
-
if (result[name]) {
|
|
14
|
-
if (Array.isArray(result[name])) result[name].push(value);
|
|
15
|
-
else result[name] = [result[name], value];
|
|
16
|
-
} else result[name] = value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return result;
|
|
20
|
-
}
|
|
21
|
-
exports.parseQueryString = parseQueryString;
|
|
22
|
-
|
|
23
|
-
function parseUrl(url) {
|
|
24
|
-
const [, path = "/", , queryString = ""] = URL_REGEX.exec(url);
|
|
25
|
-
return { path, queryString };
|
|
26
|
-
}
|
|
27
|
-
exports.parseUrl = parseUrl;
|
|
1
|
+
const URL_REGEX = /^([^?#]*)(\?([^#]*))?(#.*)?$/;
|
|
2
|
+
const QUERY_STRING_REGEX = /([^=&]*)?=([^&]*)?/g;
|
|
3
|
+
|
|
4
|
+
function parseQueryString(queryString) {
|
|
5
|
+
const result = {};
|
|
6
|
+
|
|
7
|
+
let match;
|
|
8
|
+
while ((match = QUERY_STRING_REGEX.exec(queryString)) !== null) {
|
|
9
|
+
const [, name = "", raw = ""] = match;
|
|
10
|
+
|
|
11
|
+
const value = decodeURIComponent(raw);
|
|
12
|
+
|
|
13
|
+
if (result[name]) {
|
|
14
|
+
if (Array.isArray(result[name])) result[name].push(value);
|
|
15
|
+
else result[name] = [result[name], value];
|
|
16
|
+
} else result[name] = value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
exports.parseQueryString = parseQueryString;
|
|
22
|
+
|
|
23
|
+
function parseUrl(url) {
|
|
24
|
+
const [, path = "/", , queryString = ""] = URL_REGEX.exec(url);
|
|
25
|
+
return { path, queryString };
|
|
26
|
+
}
|
|
27
|
+
exports.parseUrl = parseUrl;
|