@ndiinginc/router 4.1.3 → 4.2.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 +3 -3
- package/src/app.js +76 -67
- package/src/cache.js +24 -28
- package/src/error.js +26 -0
- package/src/index.js +9 -0
- package/src/{middleware.js → json.js} +55 -52
- package/src/node.js +81 -86
- package/src/pattern.js +34 -34
- package/src/request.js +37 -43
- package/src/response.js +13 -14
- package/src/router.js +164 -179
- package/src/util.js +27 -0
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndiinginc/router",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "./src/
|
|
5
|
+
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "nodemon --exec \"node -e \\\"require('@ndiinginc/test')(require('path').resolve('tests'),{verbose:true})\\\"\"",
|
|
8
|
-
"dev": "nodemon src/
|
|
8
|
+
"dev": "nodemon src/index.js",
|
|
9
9
|
"prettier": "npx prettier . --write",
|
|
10
10
|
"commit": "node -e \"require('@ndiinginc/commit')()\"",
|
|
11
11
|
"npm:dry-run": "npm publish --dry-run",
|
package/src/app.js
CHANGED
|
@@ -1,67 +1,76 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
const {
|
|
3
|
-
const { Request } = require("./request.js");
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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,28 +1,24 @@
|
|
|
1
|
-
class Cache {
|
|
2
|
-
constructor(size) {
|
|
3
|
-
this.size = size;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.map.set(key, value);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
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
ADDED
|
@@ -0,0 +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;
|
package/src/index.js
ADDED
|
@@ -1,52 +1,55 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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,86 +1,81 @@
|
|
|
1
|
-
const { Pattern } = require("./pattern.js");
|
|
2
|
-
|
|
3
|
-
class Node {
|
|
4
|
-
constructor(segment) {
|
|
5
|
-
this.segment = segment;
|
|
6
|
-
this.pattern = new Pattern(segment);
|
|
7
|
-
/**@type {Map<string,
|
|
8
|
-
this.children = new Map();
|
|
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
|
-
for (const [,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for (const [,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return max;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
exports.Node = Node;
|
|
1
|
+
const { Pattern } = require("./pattern.js");
|
|
2
|
+
|
|
3
|
+
class Node {
|
|
4
|
+
constructor(segment = "") {
|
|
5
|
+
this.segment = segment;
|
|
6
|
+
this.pattern = new Pattern(segment);
|
|
7
|
+
/**@type {Map<string,Node>}*/
|
|
8
|
+
this.children = new Map();
|
|
9
|
+
this.handlers = new Map([
|
|
10
|
+
["ALL", []],
|
|
11
|
+
["GET", []],
|
|
12
|
+
["QUERY", []],
|
|
13
|
+
["HEAD", []],
|
|
14
|
+
["OPTIONS", []],
|
|
15
|
+
["TRACE", []],
|
|
16
|
+
["PUT", []],
|
|
17
|
+
["DELETE", []],
|
|
18
|
+
["POST", []],
|
|
19
|
+
["PATCH", []],
|
|
20
|
+
["CONNECT", []],
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
add(segment) {
|
|
25
|
+
if (!this.children.has(segment)) {
|
|
26
|
+
this.children.set(segment, new Node(segment));
|
|
27
|
+
}
|
|
28
|
+
return this.children.get(segment);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {Node} node
|
|
33
|
+
*/
|
|
34
|
+
merge(node, offset) {
|
|
35
|
+
for (const [method, handlers] of node.handlers) {
|
|
36
|
+
for (const handler of handlers) {
|
|
37
|
+
handler.seq += offset;
|
|
38
|
+
this.handlers.get(method).push(handler);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [segment, child] of node.children) {
|
|
43
|
+
if (!this.children.has(segment)) {
|
|
44
|
+
child.setSeqOffset(offset);
|
|
45
|
+
this.children.set(segment, child);
|
|
46
|
+
} else {
|
|
47
|
+
this.children.get(segment).merge(child, offset);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setSeqOffset(offset) {
|
|
53
|
+
for (const [, handlers] of this.handlers) {
|
|
54
|
+
for (const handler of handlers) {
|
|
55
|
+
handler.seq += offset;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [, child] of this.children) {
|
|
60
|
+
child.setSeqOffset(offset);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getMaxSeq() {
|
|
65
|
+
let max = -1;
|
|
66
|
+
|
|
67
|
+
for (const [, handlers] of this.handlers) {
|
|
68
|
+
for (const handler of handlers) {
|
|
69
|
+
max = Math.max(max, handler.seq);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const [, child] of this.children) {
|
|
74
|
+
max = Math.max(max, child.getMaxSeq());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return max;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
exports.Node = Node;
|
package/src/pattern.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef
|
|
3
|
-
* @property {number} index
|
|
4
|
-
* @property {string} input
|
|
5
|
-
* @property {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.replace(
|
|
12
|
-
.replace(
|
|
13
|
-
|
|
14
|
-
Object.defineProperty(this, "
|
|
15
|
-
value: new RegExp(
|
|
16
|
-
|
|
17
|
-
enumerable: false,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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(/\*/, ".*")}$`;
|
|
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,43 +1,37 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
this._query = query;
|
|
40
|
-
return this._query;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
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,14 +1,13 @@
|
|
|
1
|
-
const http = require("node:http");
|
|
2
|
-
|
|
3
|
-
class Response extends http.ServerResponse {
|
|
4
|
-
send(
|
|
5
|
-
this.end(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
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,179 +1,164 @@
|
|
|
1
|
-
const { Node } = require("./node");
|
|
2
|
-
const { Cache } = require("./cache");
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
this
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
165
|
-
else handlers[handler.seq] = handler;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (node) {
|
|
169
|
-
for (const handler of node.handlers.get(method)) {
|
|
170
|
-
handlers[handler.seq] = handler;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const value = { params, handlers: Object.values(handlers), errorHandlers: Object.values(errorHandlers) };
|
|
175
|
-
this.cache.set(key, value);
|
|
176
|
-
return value;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
module.exports.Router = Router;
|
|
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
|
+
const match = child.pattern.exec(input);
|
|
120
|
+
if (match) {
|
|
121
|
+
matched = child;
|
|
122
|
+
Object.assign(params, match.groups);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!matched) {
|
|
129
|
+
node = null;
|
|
130
|
+
params = {};
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
node = matched;
|
|
135
|
+
|
|
136
|
+
for (const handler of node.handlers.get("ALL")) {
|
|
137
|
+
if (handler.length === 3) errorHandlers[handler.seq] = handler;
|
|
138
|
+
else handlers[handler.seq] = handler;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const handler of this.root.handlers.get("ALL")) {
|
|
143
|
+
if (handler.length === 3) errorHandlers[handler.seq] = handler;
|
|
144
|
+
else handlers[handler.seq] = handler;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (node) {
|
|
148
|
+
for (const handler of node.handlers.get(method)) {
|
|
149
|
+
handlers[handler.seq] = handler
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = {
|
|
154
|
+
params,
|
|
155
|
+
handlers: Object.values(handlers),
|
|
156
|
+
errorHandlers: Object.values(errorHandlers),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
this.cache.set(key, result);
|
|
160
|
+
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.Router = Router;
|
package/src/util.js
ADDED
|
@@ -0,0 +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;
|