@ndiinginc/router 4.0.0 → 4.1.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/README.md +10 -14
- package/package.json +1 -1
- package/rest/router.http +3 -3
- package/src/app.js +67 -0
- package/src/cache.js +28 -0
- package/src/middleware.js +5 -5
- package/src/node.js +19 -3
- package/src/pattern.js +22 -12
- package/src/request.js +22 -8
- package/src/response.js +3 -3
- package/src/router.js +108 -141
- package/src/polyfill.js +0 -9
package/README.md
CHANGED
|
@@ -46,11 +46,7 @@ app.listen(3000);
|
|
|
46
46
|
Semua method mengembalikan `this`, jadi bisa di-chain.
|
|
47
47
|
|
|
48
48
|
```js
|
|
49
|
-
app.get("/users", handler)
|
|
50
|
-
.post("/users", handler)
|
|
51
|
-
.put("/users/:id", handler)
|
|
52
|
-
.patch("/users/:id", handler)
|
|
53
|
-
.delete("/users/:id", handler);
|
|
49
|
+
app.get("/users", handler).post("/users", handler).put("/users/:id", handler).patch("/users/:id", handler).delete("/users/:id", handler);
|
|
54
50
|
```
|
|
55
51
|
|
|
56
52
|
Method yang tersedia: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, `trace`, `connect`, `query`, dan `use` (semua method).
|
|
@@ -149,12 +145,12 @@ Jika tidak ada handler yang mengirim response, router mengirim JSON otomatis:
|
|
|
149
145
|
|
|
150
146
|
## Request
|
|
151
147
|
|
|
152
|
-
| Properti
|
|
153
|
-
|
|
|
154
|
-
| `req.path`
|
|
155
|
-
| `req.query`
|
|
156
|
-
| `req.params` | Parameter dari route dinamis
|
|
157
|
-
| `req.body`
|
|
148
|
+
| Properti | Keterangan |
|
|
149
|
+
| ------------ | ---------------------------------------------------- |
|
|
150
|
+
| `req.path` | Pathname tanpa query string |
|
|
151
|
+
| `req.query` | Object query string. Key yang berulang menjadi array |
|
|
152
|
+
| `req.params` | Parameter dari route dinamis |
|
|
153
|
+
| `req.body` | Hasil parse body (butuh middleware `json()`) |
|
|
158
154
|
|
|
159
155
|
```js
|
|
160
156
|
// GET /search?tag=a&tag=b&page=2
|
|
@@ -165,9 +161,9 @@ app.get("/search", (req, res) => {
|
|
|
165
161
|
|
|
166
162
|
## Response
|
|
167
163
|
|
|
168
|
-
| Method
|
|
169
|
-
|
|
|
170
|
-
| `res.send(string)` | Kirim response dan akhiri chain
|
|
164
|
+
| Method | Keterangan |
|
|
165
|
+
| ------------------ | ----------------------------------------------------------- |
|
|
166
|
+
| `res.send(string)` | Kirim response dan akhiri chain |
|
|
171
167
|
| `res.json(object)` | Set `content-type: application/json`, serialize, lalu kirim |
|
|
172
168
|
|
|
173
169
|
Keduanya juga turunan dari `http.ServerResponse`, jadi `res.statusCode`, `res.setHeader()`, dan lainnya tetap tersedia.
|
package/package.json
CHANGED
package/rest/router.http
CHANGED
|
@@ -13,6 +13,6 @@ GET http://localhost:4000
|
|
|
13
13
|
###
|
|
14
14
|
GET http://localhost:5000
|
|
15
15
|
|
|
16
|
-
autocannon -c 100 -d
|
|
17
|
-
autocannon -c 100 -d
|
|
18
|
-
autocannon -c 100 -d
|
|
16
|
+
autocannon -c 100 -d 40 -p 10 localhost:3000
|
|
17
|
+
autocannon -c 100 -d 40 -p 10 localhost:4000
|
|
18
|
+
autocannon -c 100 -d 40 -p 10 localhost:5000
|
package/src/app.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
const { Response } = require("./response.js");
|
|
3
|
+
const { Request } = require("./request.js");
|
|
4
|
+
const { Router } = require("./Router.js");
|
|
5
|
+
|
|
6
|
+
class App extends Router {
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('http').IncomingMessage} req
|
|
9
|
+
* @param {import('http').ServerResponse} res
|
|
10
|
+
*/
|
|
11
|
+
async handleRequest(req, res) {
|
|
12
|
+
let err;
|
|
13
|
+
|
|
14
|
+
const { params, handlers, errorHandlers } = this.find(req.method, req.path);
|
|
15
|
+
req.params = params;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
for (const handler of handlers) {
|
|
19
|
+
if (res.done) break;
|
|
20
|
+
await handler(req, res);
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
err = error;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
for (const handler of errorHandlers) {
|
|
27
|
+
if (res.done) break;
|
|
28
|
+
await handler(err, req, res);
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
err = error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!res.done) {
|
|
36
|
+
if (!err) {
|
|
37
|
+
res.statusCode = 404;
|
|
38
|
+
err = new Error(http.STATUS_CODES[res.statusCode]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
res.statusCode = res.statusCode >= 200 && res.statusCode < 300 ? 500 : res.statusCode;
|
|
42
|
+
const error = { message: err.message };
|
|
43
|
+
if (process.env.NODE_ENV === "development") error.stack = err.stack;
|
|
44
|
+
res.json({ error });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {import('net').ListenOptions} options
|
|
50
|
+
* @returns {import('http').Server}
|
|
51
|
+
*/
|
|
52
|
+
listen(options = {}, callback) {
|
|
53
|
+
const serverOptions = {
|
|
54
|
+
IncomingMessage: Request,
|
|
55
|
+
ServerResponse: Response,
|
|
56
|
+
};
|
|
57
|
+
const requestListener = (req, res) => this.handleRequest(req, res);
|
|
58
|
+
|
|
59
|
+
const server = http.createServer(serverOptions, requestListener);
|
|
60
|
+
|
|
61
|
+
server.listen(options, callback);
|
|
62
|
+
|
|
63
|
+
return server;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = App;
|
package/src/cache.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Cache {
|
|
2
|
+
constructor(size) {
|
|
3
|
+
this.size = size;
|
|
4
|
+
|
|
5
|
+
this.map = new Map();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get(key) {
|
|
9
|
+
if (!this.map.has(key)) return undefined;
|
|
10
|
+
|
|
11
|
+
const value = this.map.get(key);
|
|
12
|
+
|
|
13
|
+
this.map.delete(key);
|
|
14
|
+
this.map.set(key, value);
|
|
15
|
+
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set(key, value) {
|
|
20
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
21
|
+
else if (this.map.size >= this.size) {
|
|
22
|
+
this.map.delete(this.map.keys().next().value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.map.set(key, value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.Cache = Cache;
|
package/src/middleware.js
CHANGED
|
@@ -24,14 +24,15 @@ function json({ limit = 100 * 1024 } = {}) {
|
|
|
24
24
|
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
let
|
|
27
|
+
let chunkLength = 0;
|
|
28
28
|
const chunks = [];
|
|
29
29
|
for await (const chunk of req) {
|
|
30
|
-
|
|
31
|
-
if (
|
|
30
|
+
chunkLength += chunk.length;
|
|
31
|
+
if (chunkLength > limit) {
|
|
32
32
|
res.statusCode = 413;
|
|
33
33
|
throw new Error(http.STATUS_CODES[res.statusCode]);
|
|
34
34
|
}
|
|
35
|
+
|
|
35
36
|
chunks.push(chunk);
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -40,9 +41,8 @@ function json({ limit = 100 * 1024 } = {}) {
|
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const rawBody = Buffer.concat(chunks).toString("utf8");
|
|
44
44
|
try {
|
|
45
|
-
req.body = JSON.parse(
|
|
45
|
+
req.body = JSON.parse(Buffer.concat(chunks), reviver);
|
|
46
46
|
} catch (error) {
|
|
47
47
|
res.statusCode = 400;
|
|
48
48
|
throw new Error("Invalid JSON body");
|
package/src/node.js
CHANGED
|
@@ -6,14 +6,27 @@ class Node {
|
|
|
6
6
|
this.pattern = new Pattern(segment);
|
|
7
7
|
/**@type {Map<string, Node>}*/
|
|
8
8
|
this.children = new Map();
|
|
9
|
-
/**@type {Map<string,
|
|
10
|
-
this.handlers = new Map(
|
|
9
|
+
/**@type {Map<string, FunctionHandler[]>}*/
|
|
10
|
+
this.handlers = new Map([
|
|
11
|
+
["ALL", []],
|
|
12
|
+
["GET", []],
|
|
13
|
+
["QUERY", []],
|
|
14
|
+
["HEAD", []],
|
|
15
|
+
["OPTIONS", []],
|
|
16
|
+
["TRACE", []],
|
|
17
|
+
["PUT", []],
|
|
18
|
+
["DELETE", []],
|
|
19
|
+
["POST", []],
|
|
20
|
+
["PATCH", []],
|
|
21
|
+
["CONNECT", []],
|
|
22
|
+
]);
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
add(segment) {
|
|
14
26
|
if (!this.children.has(segment)) {
|
|
15
27
|
this.children.set(segment, new Node(segment));
|
|
16
28
|
}
|
|
29
|
+
|
|
17
30
|
return this.children.get(segment);
|
|
18
31
|
}
|
|
19
32
|
|
|
@@ -25,7 +38,7 @@ class Node {
|
|
|
25
38
|
for (const [method, handlers] of node.handlers) {
|
|
26
39
|
for (const handler of handlers) {
|
|
27
40
|
handler.seq += offset;
|
|
28
|
-
this.handlers.
|
|
41
|
+
this.handlers.get(method).push(handler);
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
44
|
|
|
@@ -39,6 +52,9 @@ class Node {
|
|
|
39
52
|
}
|
|
40
53
|
}
|
|
41
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @param {Number} offset
|
|
57
|
+
*/
|
|
42
58
|
setSeqOffset(offset) {
|
|
43
59
|
for (const [, handlers] of this.handlers) {
|
|
44
60
|
for (const handler of handlers) {
|
package/src/pattern.js
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} PatternExec
|
|
3
|
+
* @property {number} index - The zero-based index of the match in the input string.
|
|
4
|
+
* @property {string} input - The original input string.
|
|
5
|
+
* @property {Object.<string, string>} [groups] - Named capture groups, or undefined.
|
|
6
|
+
*/
|
|
1
7
|
class Pattern {
|
|
2
|
-
static baseURL = "http://localhost";
|
|
3
|
-
|
|
4
8
|
constructor(input) {
|
|
5
|
-
const
|
|
9
|
+
const pattern = input
|
|
6
10
|
.replace(/[.+*?^${}()|[\]\\]/g, "\\$&")
|
|
7
11
|
.replace(/:(\w+)/g, "(?<$1>[^/]+)")
|
|
8
12
|
.replace(/\\\*/g, ".*");
|
|
9
|
-
this.regex = new RegExp(`^${regexStr}$`);
|
|
10
|
-
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
14
|
+
Object.defineProperty(this, "re", {
|
|
15
|
+
value: new RegExp(`^${pattern}$`),
|
|
16
|
+
writable: true,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
configurable: true,
|
|
19
|
+
});
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
test(input) {
|
|
21
|
-
return this.
|
|
23
|
+
return this.re.test(input);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {String} input
|
|
28
|
+
* @returns {PatternExec | null}
|
|
29
|
+
*/
|
|
30
|
+
exec(input) {
|
|
31
|
+
return this.re.exec(input);
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
exports.Pattern = Pattern;
|
package/src/request.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
const
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
|
|
3
|
+
class Request extends http.IncomingMessage {
|
|
4
|
+
static base = "http://127.0.0";
|
|
2
5
|
|
|
3
|
-
class Request extends IncomingMessage {
|
|
4
6
|
get path() {
|
|
5
|
-
if (this.
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
if (this._path) return this._path;
|
|
8
|
+
|
|
9
|
+
if (this.url === "/") {
|
|
10
|
+
this._path = this.url;
|
|
11
|
+
return this._path;
|
|
8
12
|
}
|
|
9
|
-
|
|
13
|
+
|
|
14
|
+
this.parsed = URL.parse(this.url, Request.base);
|
|
15
|
+
this._path = this.parsed.pathname;
|
|
16
|
+
return this._path;
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
get query() {
|
|
20
|
+
if (this._query) return this._query;
|
|
21
|
+
|
|
22
|
+
if (!this.parsed) {
|
|
23
|
+
this._query = {};
|
|
24
|
+
return this._query;
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
const query = {};
|
|
14
|
-
if (!this.parsed) return query;
|
|
15
28
|
for (const [name, value] of this.parsed.searchParams) {
|
|
16
29
|
if (query[name]) {
|
|
17
30
|
if (Array.isArray(query[name])) {
|
|
@@ -23,7 +36,8 @@ class Request extends IncomingMessage {
|
|
|
23
36
|
query[name] = value;
|
|
24
37
|
}
|
|
25
38
|
}
|
|
26
|
-
|
|
39
|
+
this._query = query;
|
|
40
|
+
return this._query;
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
43
|
exports.Request = Request;
|
package/src/response.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const http = require("node:http");
|
|
2
2
|
|
|
3
|
-
class Response extends ServerResponse {
|
|
3
|
+
class Response extends http.ServerResponse {
|
|
4
4
|
send(string) {
|
|
5
|
-
this.done = true;
|
|
6
5
|
this.end(string);
|
|
6
|
+
this.done = true;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
json(object) {
|
package/src/router.js
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
|
-
require("./
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const { Node } = require("./node");
|
|
2
|
+
const { Cache } = require("./cache");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @callback Handler
|
|
6
|
+
* @param {*} req
|
|
7
|
+
* @param {*} res
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @callback Middleware
|
|
11
|
+
* @param {*} req
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @callback ErrorHandler
|
|
15
|
+
* @param {*} err
|
|
16
|
+
* @param {*} req
|
|
17
|
+
* @param {*} res
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Handler|Middleware|ErrorHandler} FunctionHandler
|
|
21
|
+
*/
|
|
6
22
|
|
|
7
23
|
class Router {
|
|
8
|
-
|
|
9
|
-
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.cacheSize = options.cacheSize ?? 30_000;
|
|
10
26
|
|
|
11
|
-
|
|
12
|
-
this.root = new Node("");
|
|
27
|
+
this.root = new Node("/");
|
|
13
28
|
this.seq = 0;
|
|
29
|
+
this.cache = new Cache(this.cacheSize);
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
split(path) {
|
|
33
|
+
if (path === "/") return [];
|
|
34
|
+
|
|
17
35
|
return path.split("/").filter(Boolean);
|
|
18
36
|
}
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
/**
|
|
39
|
+
* @param {*} method
|
|
40
|
+
* @param {*} path
|
|
41
|
+
* @param {FunctionHandler[]} handler
|
|
42
|
+
*/
|
|
43
|
+
add(method, path, ...handlers) {
|
|
21
44
|
if (typeof path === "function") {
|
|
22
|
-
|
|
45
|
+
handlers = [path, ...handlers];
|
|
23
46
|
path = null;
|
|
24
47
|
}
|
|
25
48
|
|
|
@@ -31,182 +54,126 @@ class Router {
|
|
|
31
54
|
}
|
|
32
55
|
}
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
for (const handler of handlers) {
|
|
58
|
+
if (handler.root) {
|
|
59
|
+
node.merge(handler.root, this.seq);
|
|
60
|
+
this.seq = node.getMaxSeq() + 1;
|
|
61
|
+
} else {
|
|
62
|
+
handler.seq = this.seq++;
|
|
63
|
+
node.handlers.get(method).push(handler);
|
|
64
|
+
}
|
|
40
65
|
}
|
|
41
66
|
}
|
|
42
67
|
|
|
43
|
-
use(path,
|
|
44
|
-
this.add("ALL", path,
|
|
68
|
+
use(path, ...handlers) {
|
|
69
|
+
this.add("ALL", path, ...handlers);
|
|
45
70
|
return this;
|
|
46
71
|
}
|
|
47
72
|
|
|
48
|
-
get(path,
|
|
49
|
-
this.add("GET", path,
|
|
73
|
+
get(path, ...handlers) {
|
|
74
|
+
this.add("GET", path, ...handlers);
|
|
50
75
|
return this;
|
|
51
76
|
}
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
|
|
78
|
+
query(path, ...handlers) {
|
|
79
|
+
this.add("QUERY", path, ...handlers);
|
|
54
80
|
return this;
|
|
55
81
|
}
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
|
|
83
|
+
head(path, ...handlers) {
|
|
84
|
+
this.add("HEAD", path, ...handlers);
|
|
58
85
|
return this;
|
|
59
86
|
}
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
|
|
88
|
+
options(path, ...handlers) {
|
|
89
|
+
this.add("OPTIONS", path, ...handlers);
|
|
62
90
|
return this;
|
|
63
91
|
}
|
|
64
|
-
|
|
65
|
-
|
|
92
|
+
|
|
93
|
+
trace(path, ...handlers) {
|
|
94
|
+
this.add("TRACE", path, ...handlers);
|
|
66
95
|
return this;
|
|
67
96
|
}
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
|
|
98
|
+
put(path, ...handlers) {
|
|
99
|
+
this.add("PUT", path, ...handlers);
|
|
70
100
|
return this;
|
|
71
101
|
}
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
|
|
103
|
+
delete(path, ...handlers) {
|
|
104
|
+
this.add("DELETE", path, ...handlers);
|
|
74
105
|
return this;
|
|
75
106
|
}
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
|
|
108
|
+
post(path, ...handlers) {
|
|
109
|
+
this.add("POST", path, ...handlers);
|
|
78
110
|
return this;
|
|
79
111
|
}
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
|
|
113
|
+
patch(path, ...handlers) {
|
|
114
|
+
this.add("PATCH", path, ...handlers);
|
|
82
115
|
return this;
|
|
83
116
|
}
|
|
84
|
-
|
|
85
|
-
|
|
117
|
+
|
|
118
|
+
connect(path, ...handlers) {
|
|
119
|
+
this.add("CONNECT", path, ...handlers);
|
|
86
120
|
return this;
|
|
87
121
|
}
|
|
88
122
|
|
|
89
123
|
find(method, path) {
|
|
90
|
-
|
|
91
|
-
let handlers = {};
|
|
92
|
-
let errorHandlers = {};
|
|
124
|
+
const key = `${method} ${path}`;
|
|
93
125
|
|
|
94
|
-
|
|
126
|
+
const cached = this.cache.get(key);
|
|
127
|
+
if (cached) return cached;
|
|
95
128
|
|
|
96
|
-
|
|
97
|
-
if (inputs.length) {
|
|
98
|
-
for (const input of inputs) {
|
|
99
|
-
let matched = node.children.get(input);
|
|
100
|
-
|
|
101
|
-
if (!matched) {
|
|
102
|
-
for (const [, child] of node.children) {
|
|
103
|
-
const match = child.pattern.exec(input);
|
|
104
|
-
if (match) {
|
|
105
|
-
matched = child;
|
|
106
|
-
Object.assign(params, match.pathname.groups);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!matched) {
|
|
113
|
-
node = null;
|
|
114
|
-
params = {};
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
node = matched;
|
|
129
|
+
let node = this.root;
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
let params = {};
|
|
132
|
+
const handlers = {};
|
|
133
|
+
const errorHandlers = {};
|
|
134
|
+
|
|
135
|
+
for (const input of this.split(path)) {
|
|
136
|
+
let matched = node.children.get(input);
|
|
137
|
+
|
|
138
|
+
if (!matched) {
|
|
139
|
+
for (const [, child] of node.children) {
|
|
140
|
+
const match = child.pattern.exec(input);
|
|
141
|
+
if (match) {
|
|
142
|
+
matched = child;
|
|
143
|
+
Object.assign(params, match.groups);
|
|
144
|
+
break;
|
|
124
145
|
}
|
|
125
146
|
}
|
|
126
147
|
}
|
|
127
|
-
}
|
|
128
148
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
if (!matched) {
|
|
150
|
+
node = null;
|
|
151
|
+
params = {};
|
|
152
|
+
break;
|
|
133
153
|
}
|
|
134
|
-
}
|
|
135
154
|
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
node = matched;
|
|
156
|
+
|
|
157
|
+
for (const handler of node.handlers.get("ALL")) {
|
|
138
158
|
if (handler.length === 3) errorHandlers[handler.seq] = handler;
|
|
139
159
|
else handlers[handler.seq] = handler;
|
|
140
160
|
}
|
|
141
161
|
}
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
handlers
|
|
146
|
-
errorHandlers: Object.values(errorHandlers),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @param {import('http').RequestOptions} req
|
|
152
|
-
* @param {import('http').ServerResponse} res
|
|
153
|
-
*/
|
|
154
|
-
async handleRequest(req, res) {
|
|
155
|
-
const result = this.find(req.method, req.path);
|
|
156
|
-
req.params = result.params;
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
for (const handler of result.handlers) {
|
|
160
|
-
if (res.done) break;
|
|
161
|
-
await handler(req, res);
|
|
162
|
-
}
|
|
163
|
-
} catch (error) {
|
|
164
|
-
res.error = error;
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
for (const handler of result.errorHandlers) {
|
|
168
|
-
if (res.done) break;
|
|
169
|
-
await handler(res.error, req, res);
|
|
170
|
-
}
|
|
171
|
-
} catch (error) {
|
|
172
|
-
res.error = error;
|
|
173
|
-
}
|
|
163
|
+
for (const handler of this.root.handlers.get("ALL")) {
|
|
164
|
+
if (handler.length === 3) errorHandlers[handler.seq] = handler;
|
|
165
|
+
else handlers[handler.seq] = handler;
|
|
174
166
|
}
|
|
175
167
|
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
res.error = new Error(http.STATUS_CODES[res.statusCode]);
|
|
168
|
+
if (node) {
|
|
169
|
+
for (const handler of node.handlers.get(method)) {
|
|
170
|
+
handlers[handler.seq] = handler;
|
|
180
171
|
}
|
|
181
|
-
|
|
182
|
-
res.statusCode = res.statusCode >= 200 && res.statusCode < 300 ? 500 : res.statusCode;
|
|
183
|
-
|
|
184
|
-
const error = {
|
|
185
|
-
code: res.error.code,
|
|
186
|
-
message: res.error.message,
|
|
187
|
-
};
|
|
188
|
-
if (process.env.NODE_ENV === "development") error.stack = res.error.stack;
|
|
189
|
-
|
|
190
|
-
res.json({ error });
|
|
191
172
|
}
|
|
192
|
-
}
|
|
193
173
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
listen(...params) {
|
|
198
|
-
const server = http.createServer(
|
|
199
|
-
{
|
|
200
|
-
IncomingMessage: Request,
|
|
201
|
-
ServerResponse: Response,
|
|
202
|
-
},
|
|
203
|
-
(req, res) => this.handleRequest(req, res),
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
server.listen(...params);
|
|
207
|
-
|
|
208
|
-
return server;
|
|
174
|
+
const value = { params, handlers: Object.values(handlers), errorHandlers: Object.values(errorHandlers) };
|
|
175
|
+
this.cache.set(key, value);
|
|
176
|
+
return value;
|
|
209
177
|
}
|
|
210
178
|
}
|
|
211
|
-
|
|
212
|
-
module.exports = Router;
|
|
179
|
+
module.exports.Router = Router;
|