@jnode/server 1.0.6 → 2.0.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 +394 -34
- package/package.json +2 -2
- package/src/handlers.js +246 -0
- package/src/index.js +28 -8
- package/src/routers.js +210 -0
- package/src/server.js +203 -42
- package/src/error.js +0 -32
- package/src/final.js +0 -129
- package/src/handle.js +0 -88
- package/src/map.js +0 -50
package/src/handlers.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@jnode/server/handlers.js
|
|
3
|
+
v2
|
|
4
|
+
|
|
5
|
+
Simple web server package for Node.js.
|
|
6
|
+
|
|
7
|
+
by JustApple
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// dependencies
|
|
11
|
+
const stream = require('stream');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const mime = require('./mime.json');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// data handler: string, buffer, or stream
|
|
17
|
+
class DataHandler {
|
|
18
|
+
constructor(data, options = {}) {
|
|
19
|
+
this.data = data;
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async handle(ctx, env) {
|
|
24
|
+
if (typeof this.data === 'string') { // string
|
|
25
|
+
ctx.res.writeHead(this.options.statusCode ?? 200, {
|
|
26
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
27
|
+
'Content-Length': Buffer.byteLength(this.data, 'utf8'),
|
|
28
|
+
...this.options.headers
|
|
29
|
+
});
|
|
30
|
+
ctx.res.end(this.data, 'utf8');
|
|
31
|
+
} else if (this.data instanceof Uint8Array) { // buffer
|
|
32
|
+
ctx.res.writeHead(this.options.statusCode ?? 200, {
|
|
33
|
+
'Content-Type': 'application/octet-stream',
|
|
34
|
+
'Content-Length': this.data.length,
|
|
35
|
+
...this.options.headers
|
|
36
|
+
});
|
|
37
|
+
ctx.res.end(this.data);
|
|
38
|
+
} else if (stream.isReadable(this.data)) { // stream
|
|
39
|
+
ctx.res.writeHead(this.options.statusCode ?? 200, {
|
|
40
|
+
'Content-Type': 'application/octet-stream',
|
|
41
|
+
...this.options.headers
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await stream.promises.pipeline(this.data, ctx.res);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
if (e.code === 'ERR_STREAM_PREMATURE_CLOSE') return;
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// file handler: local file
|
|
55
|
+
class FileHandler {
|
|
56
|
+
constructor(file, options = {}) {
|
|
57
|
+
this.file = path.resolve(this.file);
|
|
58
|
+
this.options = options;
|
|
59
|
+
|
|
60
|
+
// range may be disabled by `options.disableRange` or when `statusCode` is set to non-200 value
|
|
61
|
+
this.options.disableRange = this.options.disableRange || (this.options.statusCode && this.options.statusCode !== 200);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async handle(ctx, env) {
|
|
65
|
+
let stats;
|
|
66
|
+
try {
|
|
67
|
+
stats = await fs.promises.stat(this.file);
|
|
68
|
+
} catch { throw 404; }
|
|
69
|
+
|
|
70
|
+
// folders are not allowed (if you need, try to make your own handler :D)
|
|
71
|
+
if (!stats.isFile()) throw 404;
|
|
72
|
+
|
|
73
|
+
// handle caching
|
|
74
|
+
if (this.options.cache) {
|
|
75
|
+
// set etag
|
|
76
|
+
if (!this._etag) this._etag = this.options.headers?.['ETag'] ?? `"${stats.size}-${stats.mtime.getTime()}"`;
|
|
77
|
+
|
|
78
|
+
// by etag
|
|
79
|
+
if (ctx.req.headers['if-none-match'] === this._etag) {
|
|
80
|
+
ctx.res.writeHead(304, {
|
|
81
|
+
'Last-Modified': stats.mtime.toUTCString(),
|
|
82
|
+
'ETag': this._etag,
|
|
83
|
+
...this.options.headers
|
|
84
|
+
});
|
|
85
|
+
ctx.res.end();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// by mtime
|
|
90
|
+
const since = new Date(ctx.req.headers['if-modified-since'] ?? 0).getTime();
|
|
91
|
+
if (!isNaN(since) && stats.mtime.getTime() <= since) {
|
|
92
|
+
ctx.res.writeHead(304, {
|
|
93
|
+
'Last-Modified': stats.mtime.toUTCString(),
|
|
94
|
+
'ETag': this._etag,
|
|
95
|
+
...this.options.headers
|
|
96
|
+
});
|
|
97
|
+
ctx.res.end();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// range
|
|
103
|
+
let start = 0;
|
|
104
|
+
let end = stats.size - 1;
|
|
105
|
+
|
|
106
|
+
// parse range header
|
|
107
|
+
let range = /^bytes=(\d*)-(\d*)$/.exec(ctx.req.headers.range || '');
|
|
108
|
+
if (this.options.disableRange || !(ctx.method === 'GET' || ctx.method === 'HEAD')) range = null;
|
|
109
|
+
if (range) {
|
|
110
|
+
// cache check
|
|
111
|
+
if (this.options.cache && ctx.req.headers['if-range']) {
|
|
112
|
+
if (ctx.req.headers['if-range'].startsWith('"') && ctx.req.headers['if-range'].endsWith('"')) { // etag
|
|
113
|
+
if (ctx.req.headers['if-range'] !== this._etag) range = null;
|
|
114
|
+
} else { // mtime
|
|
115
|
+
const since = new Date(ctx.req.headers['if-range']).getTime();
|
|
116
|
+
if (isNaN(since) || stats.mtime.getTime() > since) range = null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (range[1] === '' && range[2] === '') throw 416; // invalid range
|
|
121
|
+
|
|
122
|
+
if (range[1] === '' && range[2] !== '') {
|
|
123
|
+
start = Math.max(stats.size - parseInt(range[2], 10), 0); // last n bytes
|
|
124
|
+
} else {
|
|
125
|
+
if (range[1] !== '') start = parseInt(range[1], 10);
|
|
126
|
+
if (range[2] !== '') end = Math.min(parseInt(range[2], 10), stats.size - 1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (start > end) throw 416;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// headers
|
|
133
|
+
ctx.res.writeHead(this.options.statusCode ?? (range ? 206 : 200), {
|
|
134
|
+
'Content-Type': mime[path.extname(this.file)] || 'application/octet-stream',
|
|
135
|
+
'Content-Length': range ? (end - start) + 1 : stats.size,
|
|
136
|
+
'Last-Modified': stats.mtime.toUTCString(),
|
|
137
|
+
...(this._etag && { 'ETag': this._etag }),
|
|
138
|
+
...(this.options.cache && { 'Cache-Control': 'max-age=' + (this.options.cache === true ? 'no-cache' : this.options.cache) }), // cache
|
|
139
|
+
...(!this.options.disableRange && { 'Accept-Ranges': 'bytes' }),
|
|
140
|
+
...(range && { 'Content-Range': `bytes ${start}-${end}/${stats.size}` }),
|
|
141
|
+
...this.options.headers
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// pipe stream or send with only headers
|
|
145
|
+
if (ctx.method === 'HEAD' && !this.options.disableHead) {
|
|
146
|
+
ctx.res.end();
|
|
147
|
+
} else {
|
|
148
|
+
// send with stream
|
|
149
|
+
try {
|
|
150
|
+
await stream.promises.pipeline(
|
|
151
|
+
fs.createReadStream(this.file, {
|
|
152
|
+
start, end,
|
|
153
|
+
highWaterMark: this.options.highWaterMark || 64 * 1024
|
|
154
|
+
}),
|
|
155
|
+
ctx.res
|
|
156
|
+
);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
if (e.code === 'ERR_STREAM_PREMATURE_CLOSE') return;
|
|
159
|
+
throw e;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// folder handler: local folder, continues from env.path
|
|
166
|
+
class FolderHandler {
|
|
167
|
+
constructor(folder, options = {}) {
|
|
168
|
+
this.folder = path.resolve(folder);
|
|
169
|
+
this.options = options;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
handle(ctx, env) {
|
|
173
|
+
const file = path.resolve(this.folder, ...env.path.slice(env.pathPointer));
|
|
174
|
+
|
|
175
|
+
// safety check
|
|
176
|
+
const rel = path.relative(this.folder, file);
|
|
177
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) throw 404;
|
|
178
|
+
|
|
179
|
+
// use a FileHandler
|
|
180
|
+
return (new FileHandler(file, this.options)).handle(ctx, env);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// JSON handler: JSON object
|
|
185
|
+
class JSONHandler {
|
|
186
|
+
constructor(obj, options = {}) {
|
|
187
|
+
this.obj = obj;
|
|
188
|
+
this.options = options;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
handle(ctx, env) {
|
|
192
|
+
const data = JSON.stringify(this.obj);
|
|
193
|
+
|
|
194
|
+
ctx.res.writeHead(this.options.statusCode ?? 200, {
|
|
195
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
196
|
+
'Content-Length': Buffer.byteLength(data, 'utf8'),
|
|
197
|
+
...this.options.headers
|
|
198
|
+
});
|
|
199
|
+
ctx.res.end(data, 'utf8');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// redirect handler: 307 redirect
|
|
204
|
+
class RedirectHandler {
|
|
205
|
+
constructor(location, options = {}) {
|
|
206
|
+
this.location = location;
|
|
207
|
+
this.options = options;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
handle(ctx, env) {
|
|
211
|
+
ctx.res.writeHead(this.options.statusCode ?? 307, {
|
|
212
|
+
'Location': this.options.base ?
|
|
213
|
+
this.options.base +
|
|
214
|
+
(this.options.base.endsWith('/') ? '' : '/') +
|
|
215
|
+
env.path.slice(env.pathPointer).map(encodeURIComponent).join('/') :
|
|
216
|
+
this.location,
|
|
217
|
+
...this.options.headers
|
|
218
|
+
});
|
|
219
|
+
ctx.res.end();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// function handler: custom function
|
|
224
|
+
class FunctionHandler {
|
|
225
|
+
constructor(func) {
|
|
226
|
+
this.func = func;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
handle(ctx, env) {
|
|
230
|
+
return this.func(ctx, env);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// export
|
|
235
|
+
module.exports = {
|
|
236
|
+
DataHandler, TextHandler: DataHandler, FileHandler, FolderHandler, JSONHandler, RedirectHandler, FunctionHandler,
|
|
237
|
+
handlerConstructors: {
|
|
238
|
+
Data: (...args) => new DataHandler(...args),
|
|
239
|
+
Text: (...args) => new DataHandler(...args),
|
|
240
|
+
File: (...args) => new FileHandler(...args),
|
|
241
|
+
Folder: (...args) => new FolderHandler(...args),
|
|
242
|
+
JSON: (...args) => new JSONHandler(...args),
|
|
243
|
+
Redirect: (...args) => new RedirectHandler(...args),
|
|
244
|
+
Function: (...args) => new FunctionHandler(...args)
|
|
245
|
+
}
|
|
246
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
2
|
+
@jnode/server
|
|
3
|
+
v2
|
|
3
4
|
|
|
4
5
|
Simple web server package for Node.js.
|
|
5
6
|
|
|
6
7
|
by JustNode Dev Team / JustApple
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
+
// router
|
|
11
|
+
class Router {
|
|
12
|
+
constructor() {
|
|
13
|
+
console.warn('Hey! There is no need to use class `Router` directly, just use any object with a `.route(env, ctx)` method as a router.');
|
|
14
|
+
console.warn('Learn more at the documentation (README.md).');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
route(env, ctx) { }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// handler
|
|
21
|
+
class Handler {
|
|
22
|
+
constructor() {
|
|
23
|
+
console.warn('Hey! There is no need to use class `Handler` directly, just use any object with a `.handle(ctx, env)` method as a handler.');
|
|
24
|
+
console.warn('Learn more at the documentation (README.md).');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handle(ctx, env) { }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// export
|
|
10
31
|
module.exports = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
mimeType: require('./mime.json')
|
|
32
|
+
mimeTypes: require('./mime.json'),
|
|
33
|
+
Router, Handler,
|
|
34
|
+
...require('./routers.js'),
|
|
35
|
+
...require('./handlers.js'),
|
|
36
|
+
...require('./server.js')
|
|
17
37
|
};
|
package/src/routers.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@jnode/server/routers.js
|
|
3
|
+
v2
|
|
4
|
+
|
|
5
|
+
Simple web server package for Node.js.
|
|
6
|
+
|
|
7
|
+
by JustApple
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// path router: the most important router, routes the request by path
|
|
11
|
+
class PathRouter {
|
|
12
|
+
constructor(end = 404, map = {}) {
|
|
13
|
+
this.end = end;
|
|
14
|
+
|
|
15
|
+
// parse map
|
|
16
|
+
this.map = {};
|
|
17
|
+
for (let [key, value] of Object.entries(map)) {
|
|
18
|
+
// any path segment
|
|
19
|
+
if (key === '*') {
|
|
20
|
+
this.map['*'] = value;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
key = key.trimStart();
|
|
25
|
+
|
|
26
|
+
const firstSlashIndex = key.indexOf('/');
|
|
27
|
+
if (firstSlashIndex === -1) continue;
|
|
28
|
+
|
|
29
|
+
// key format: '[@ (path end check)][METHOD (method check)]/path/segments'
|
|
30
|
+
// example: '@ GET /cat/names', 'POST /cats', '/api'
|
|
31
|
+
const routeEnd = key.startsWith('@');
|
|
32
|
+
const routeMethod = key.substring(routeEnd ? 1 : 0, firstSlashIndex).trim().toUpperCase();
|
|
33
|
+
const routePath = key.substring(firstSlashIndex).split('/').slice(1).map(decodeURIComponent);
|
|
34
|
+
|
|
35
|
+
// expand map
|
|
36
|
+
let current = this.map;
|
|
37
|
+
for (const segment of routePath) {
|
|
38
|
+
if (!current['/' + segment]) current['/' + segment] = {};
|
|
39
|
+
current = current['/' + segment];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// '*' for non-end check, '@' for end check
|
|
43
|
+
// '*' for any method, 'METHOD' for specific method
|
|
44
|
+
if (!current[routeEnd ? '@' : '*']) current[routeEnd ? '@' : '*'] = {};
|
|
45
|
+
current[routeEnd ? '@' : '*'][routeMethod || '*'] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
route(env, ctx) {
|
|
50
|
+
if (env.pathPointer >= env.path.length) return this.end;
|
|
51
|
+
|
|
52
|
+
let result = this.map['*'];
|
|
53
|
+
let resultPointer = env.pathPointer;
|
|
54
|
+
let current = this.map;
|
|
55
|
+
while (env.pathPointer < env.path.length) {
|
|
56
|
+
const segment = '/' + env.path[env.pathPointer];
|
|
57
|
+
if (!current[segment]) break;
|
|
58
|
+
|
|
59
|
+
// prepare fallback
|
|
60
|
+
if (current[segment]['*']?.['*'] || current[segment]['*']?.[ctx.method]) {
|
|
61
|
+
result = current[segment]['*'][ctx.method] ?? current[segment]['*']['*'];
|
|
62
|
+
resultPointer = env.pathPointer + 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
current = current[segment];
|
|
66
|
+
env.pathPointer++;
|
|
67
|
+
|
|
68
|
+
// ends
|
|
69
|
+
if (env.pathPointer >= env.path.length && (current['@']?.['*'] || current['@']?.[ctx.method])) {
|
|
70
|
+
result = current['@'][ctx.method] ?? current['@']['*'];
|
|
71
|
+
resultPointer = env.pathPointer;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
env.pathPointer = resultPointer;
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// host router: routes the request by host
|
|
81
|
+
class HostRouter {
|
|
82
|
+
constructor(end = 404, map = {}) {
|
|
83
|
+
this.end = end;
|
|
84
|
+
|
|
85
|
+
// parse map
|
|
86
|
+
this.map = {};
|
|
87
|
+
for (let [key, value] of Object.entries(map)) {
|
|
88
|
+
// any path segment
|
|
89
|
+
if (key === '*') {
|
|
90
|
+
this.map['*'] = value;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
key = key.trimStart();
|
|
95
|
+
|
|
96
|
+
const firstDotIndex = key.indexOf('.');
|
|
97
|
+
if (firstDotIndex === -1) continue;
|
|
98
|
+
|
|
99
|
+
// key format: '[@ (domain end check)].domain.segments'
|
|
100
|
+
// example: '@ .com.example', '.com.example', '.localhost', '.1.0.0.127' (yes, that's how it works)
|
|
101
|
+
const routeEnd = key.startsWith('@');
|
|
102
|
+
const routeDomain = key.substring(firstDotIndex).split('.').slice(1);
|
|
103
|
+
|
|
104
|
+
// expand map
|
|
105
|
+
let current = this.map;
|
|
106
|
+
for (const segment of routeDomain) {
|
|
107
|
+
if (!current['.' + segment]) current['.' + segment] = {};
|
|
108
|
+
current = current['.' + segment];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// '*' for non-end check, '@' for end check
|
|
112
|
+
current[routeEnd ? '@' : '*'] = value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
route(env, ctx) {
|
|
117
|
+
if (env.hostPointer >= env.host.length) return this.end;
|
|
118
|
+
|
|
119
|
+
let result = this.map['*'];
|
|
120
|
+
let resultPointer = env.hostPointer;
|
|
121
|
+
let current = this.map;
|
|
122
|
+
while (env.hostPointer < env.host.length) {
|
|
123
|
+
const segment = '.' + env.host[env.hostPointer];
|
|
124
|
+
if (!current[segment]) break;
|
|
125
|
+
|
|
126
|
+
// prepare fallback
|
|
127
|
+
if (current[segment]['*']) {
|
|
128
|
+
result = current[segment]['*'];
|
|
129
|
+
resultPointer = env.hostPointer + 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
current = current[segment];
|
|
133
|
+
env.hostPointer++;
|
|
134
|
+
|
|
135
|
+
// ends
|
|
136
|
+
if (env.hostPointer >= env.host.length && current['@']) {
|
|
137
|
+
result = current['@'];
|
|
138
|
+
resultPointer = env.hostPointer;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
env.hostPointer = resultPointer;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// method router: routes the request by method
|
|
148
|
+
class MethodRouter {
|
|
149
|
+
constructor(methodMap = {}) {
|
|
150
|
+
this.methodMap = methodMap;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
route(env, ctx) {
|
|
154
|
+
return this.methodMap[ctx.method] || this.methodMap['*'] || 405;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// function router: a simple router that allows you to make custom routing logic
|
|
159
|
+
class FunctionRouter {
|
|
160
|
+
constructor(fn) {
|
|
161
|
+
this.fn = fn;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
route(env, ctx) {
|
|
165
|
+
return this.fn(env, ctx);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// path argument router: collects a path segment and save to `ctx.params`
|
|
170
|
+
class PathArgRouter {
|
|
171
|
+
constructor(paramName, next) {
|
|
172
|
+
this.paramName = paramName;
|
|
173
|
+
this.next = next;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
route(env, ctx) {
|
|
177
|
+
ctx.params[this.paramName] = env.path[env.pathPointer];
|
|
178
|
+
env.pathPointer++;
|
|
179
|
+
|
|
180
|
+
return this.next;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// host argument router: collects a host segment and save to `ctx.params`
|
|
185
|
+
class HostArgRouter {
|
|
186
|
+
constructor(paramName, next) {
|
|
187
|
+
this.paramName = paramName;
|
|
188
|
+
this.next = next;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
route(env, ctx) {
|
|
192
|
+
ctx.params[this.paramName] = env.host[env.hostPointer];
|
|
193
|
+
env.hostPointer++;
|
|
194
|
+
|
|
195
|
+
return this.next;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// export
|
|
200
|
+
module.exports = {
|
|
201
|
+
PathRouter, HostRouter, MethodRouter, FunctionRouter, PathArgRouter, HostArgRouter,
|
|
202
|
+
routerConstructors: {
|
|
203
|
+
Path: (...args) => new PathRouter(...args),
|
|
204
|
+
Host: (...args) => new HostRouter(...args),
|
|
205
|
+
Method: (...args) => new MethodRouter(...args),
|
|
206
|
+
Function: (...args) => new FunctionRouter(...args),
|
|
207
|
+
PathArg: (...args) => new PathArgRouter(...args),
|
|
208
|
+
HostArg: (...args) => new HostArgRouter(...args)
|
|
209
|
+
}
|
|
210
|
+
};
|