@jnode/server 2.1.0 → 2.2.1
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 +22 -7
- package/package.json +1 -1
- package/src/handlers.js +46 -30
- package/src/routers.js +40 -18
- package/src/server.js +10 -6
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ Also, we provide some powerful built-in routers and handlers so you can start bu
|
|
|
83
83
|
- `options` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
|
|
84
84
|
- `maxRoutingSteps` [\<number\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) The max steps for routing; when exceeded but still getting another router, it'll throw the client a **508** error. **Default:** `50`.
|
|
85
85
|
- `enableHTTP2` [\<boolean\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type) Enable HTTP/2 support (with `node:http2` [Compatibility API](https://nodejs.org/docs/latest/api/http2.html#compatibility-api)). **Default:** `false`.
|
|
86
|
+
- `codeHandlers` [\<Object\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) Global status code handlers. Keys are HTTP status codes, values are [handlers-extended](#handler-extended).
|
|
86
87
|
- Options in [`http.createServer()`](https://nodejs.org/docs/latest/api/http.html#httpcreateserveroptions-requestlistener), [`https.createServer()`](https://nodejs.org/docs/latest/api/https.html#httpscreateserveroptions-requestlistener), [`http2.createServer()`](https://nodejs.org/docs/latest/api/http2.html#http2createserveroptions-onrequesthandler), or [`http2.createSecureServer()`](https://nodejs.org/docs/latest/api/http2.html#http2createsecureserveroptions-onrequesthandler).
|
|
87
88
|
- Returns: [\<server.Server\>](#class-serverserver)
|
|
88
89
|
|
|
@@ -268,6 +269,8 @@ We provide two methods to use them:
|
|
|
268
269
|
|
|
269
270
|
`PathRouter` is probably the most important router; almost every server needs it!
|
|
270
271
|
|
|
272
|
+
Please note that when defining only `/%:arg/b` and `/a/c`, requesting `/a/b` **WILL NOT** return the value of `/%:arg/b`. Instead, it will return `404`. This limitation is in place to improve performance, and we believe in designing a great API. If you still need this functionality, you can build your own router.
|
|
273
|
+
|
|
271
274
|
By the way, if you’re looking for a universal matching character, use `'/%:'` instead of `'/*'` (this will match to `'*'` in a literal sense).
|
|
272
275
|
|
|
273
276
|
#### How it works?
|
|
@@ -284,7 +287,8 @@ map = {
|
|
|
284
287
|
'@/a/b': 'C!',
|
|
285
288
|
'GET/a': 'D!',
|
|
286
289
|
'@GET/a/b': 'E!',
|
|
287
|
-
'
|
|
290
|
+
'@GET/%:arg/c': 'F!',
|
|
291
|
+
'*': 'G!'
|
|
288
292
|
};
|
|
289
293
|
|
|
290
294
|
// we will parse into
|
|
@@ -306,11 +310,19 @@ parsed = {
|
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
},
|
|
309
|
-
'
|
|
313
|
+
':': {
|
|
314
|
+
'/c': {
|
|
315
|
+
'@': {
|
|
316
|
+
'GET': 'F!',
|
|
317
|
+
'::GET': [ 'arg' ]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
'*': 'G!'
|
|
310
322
|
};
|
|
311
323
|
```
|
|
312
324
|
|
|
313
|
-
This format
|
|
325
|
+
This format allows for fast and flexible path routing while maintaining simplicity for developers. However, as mentioned earlier, some specialized path matching will not be supported.
|
|
314
326
|
|
|
315
327
|
### Router: `HostRouter(end, map)`
|
|
316
328
|
|
|
@@ -319,6 +331,7 @@ This format enables fast and flexible path routing while keeping it simple for d
|
|
|
319
331
|
- `.<host_segment>[.<host_segment>...]` [router](#class-serverrouter) | [handler-extended](#handler-extended) A simple host segment routing (reversed). E.G., `.example.com` will match `example.com`, `.localhost` will match `localhost`.
|
|
320
332
|
- `@.<host_segment>[.<host_segment>...]` [router](#class-serverrouter) | [handler-extended](#handler-extended) Used when the host resolver ends here. E.G., `@.example.com` (only works for exactly `example.com` but not `sub.example.com`).
|
|
321
333
|
- `*` [router](#class-serverrouter) | [handler-extended](#handler-extended) Any host segment.
|
|
334
|
+
- `.%:<host_parameter_name>` [router](#class-serverrouter) | [handler-extended](#handler-extended) Match any segment (if exists) and save the segment to `ctx.params` by `<host_parameter_name>`.
|
|
322
335
|
|
|
323
336
|
### Router: `MethodRouter(methodMap)`
|
|
324
337
|
|
|
@@ -327,9 +340,10 @@ This format enables fast and flexible path routing while keeping it simple for d
|
|
|
327
340
|
- `*` [router](#class-serverrouter) | [handler-extended](#handler-extended) Any method, used as fallback.
|
|
328
341
|
- Returns: [\<MethodRouter\>](#router-methodroutermethodmap) Routes based on HTTP method, returns 405 if no method matches.
|
|
329
342
|
|
|
330
|
-
### Router: `FunctionRouter(fn)`
|
|
343
|
+
### Router: `FunctionRouter(fn, ext)`
|
|
331
344
|
|
|
332
|
-
- `fn` [\<Function\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) A function with signature `(env, ctx) => router | handler-extended`.
|
|
345
|
+
- `fn` [\<Function\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) A function with signature `(env, ctx, ext) => router | handler-extended`.
|
|
346
|
+
- `ext` [\<any\>] Passed to `func`.
|
|
333
347
|
|
|
334
348
|
A simple router that allows you to implement custom routing logic.
|
|
335
349
|
|
|
@@ -410,8 +424,9 @@ Sends a JavaScript object serialized as JSON with `Content-Type: application/jso
|
|
|
410
424
|
|
|
411
425
|
Redirects the request to a specified location. Supports both absolute URLs and dynamic redirects based on remaining path segments.
|
|
412
426
|
|
|
413
|
-
### Handler: `FunctionHandler(func)`
|
|
427
|
+
### Handler: `FunctionHandler(func, ext)`
|
|
414
428
|
|
|
415
|
-
- `func` [\<Function\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) A function with signature `(ctx, env) => void | Promise<void>`.
|
|
429
|
+
- `func` [\<Function\>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) A function with signature `(ctx, env, ext) => void | Promise<void>`.
|
|
430
|
+
- `ext` [\<any\>] Passed to `func`.
|
|
416
431
|
|
|
417
432
|
Allows you to implement custom request handling logic directly within a function.
|
package/package.json
CHANGED
package/src/handlers.js
CHANGED
|
@@ -18,28 +18,38 @@ class DataHandler {
|
|
|
18
18
|
constructor(data, options = {}) {
|
|
19
19
|
this.data = data;
|
|
20
20
|
this.options = options;
|
|
21
|
-
}
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
// prebuild headers
|
|
23
|
+
this._statusCode = this.options.statusCode ?? 200;
|
|
24
24
|
if (typeof this.data === 'string') { // string
|
|
25
|
-
|
|
25
|
+
this.data = Buffer.from(this.data, 'utf8');
|
|
26
|
+
this._headers = {
|
|
26
27
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
27
|
-
'Content-Length':
|
|
28
|
+
'Content-Length': this.data.length,
|
|
28
29
|
...this.options.headers
|
|
29
|
-
}
|
|
30
|
-
ctx.res.end(this.data, 'utf8');
|
|
30
|
+
};
|
|
31
31
|
} else if (this.data instanceof Uint8Array) { // buffer
|
|
32
|
-
|
|
32
|
+
this._headers = {
|
|
33
33
|
'Content-Type': 'application/octet-stream',
|
|
34
34
|
'Content-Length': this.data.length,
|
|
35
35
|
...this.options.headers
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ctx.res.writeHead(this.options.statusCode ?? 200, {
|
|
36
|
+
};
|
|
37
|
+
} else if (stream.isReadable(this.data)) {
|
|
38
|
+
this._headers = {
|
|
40
39
|
'Content-Type': 'application/octet-stream',
|
|
41
40
|
...this.options.headers
|
|
42
|
-
}
|
|
41
|
+
};
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error('Unsupported data type.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async handle(ctx, env) {
|
|
48
|
+
if (this.data instanceof Uint8Array) { // buffer
|
|
49
|
+
ctx.res.writeHead(this._statusCode, this._headers);
|
|
50
|
+
ctx.res.end(this.data);
|
|
51
|
+
} else if (stream.isReadable(this.data)) { // stream
|
|
52
|
+
ctx.res.writeHead(this._statusCode, this._headers);
|
|
43
53
|
|
|
44
54
|
try {
|
|
45
55
|
await stream.promises.pipeline(this.data, ctx.res);
|
|
@@ -184,19 +194,21 @@ class FolderHandler {
|
|
|
184
194
|
// JSON handler: JSON object
|
|
185
195
|
class JSONHandler {
|
|
186
196
|
constructor(obj, options = {}) {
|
|
187
|
-
this.
|
|
197
|
+
this.data = Buffer.from(JSON.stringify(obj), 'utf8');
|
|
188
198
|
this.options = options;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
handle(ctx, env) {
|
|
192
|
-
const data = JSON.stringify(this.obj);
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
// prebuild headers
|
|
201
|
+
this._statusCode = this.options.statusCode ?? 200;
|
|
202
|
+
this._headers = {
|
|
195
203
|
'Content-Type': 'application/json; charset=utf-8',
|
|
196
204
|
'Content-Length': Buffer.byteLength(data, 'utf8'),
|
|
197
205
|
...this.options.headers
|
|
198
|
-
}
|
|
199
|
-
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
handle(ctx, env) {
|
|
210
|
+
ctx.res.writeHead(this.statusCode, this._headers);
|
|
211
|
+
ctx.res.end(this.data);
|
|
200
212
|
}
|
|
201
213
|
}
|
|
202
214
|
|
|
@@ -205,10 +217,13 @@ class RedirectHandler {
|
|
|
205
217
|
constructor(location, options = {}) {
|
|
206
218
|
this.location = location;
|
|
207
219
|
this.options = options;
|
|
220
|
+
|
|
221
|
+
// prebuild headers
|
|
222
|
+
this._statusCode = this.options.statusCode ?? 307;
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
handle(ctx, env) {
|
|
211
|
-
ctx.res.writeHead(this.
|
|
226
|
+
ctx.res.writeHead(this._statusCode, {
|
|
212
227
|
'Location': this.options.base ?
|
|
213
228
|
this.options.base +
|
|
214
229
|
(this.options.base.endsWith('/') ? '' : '/') +
|
|
@@ -222,12 +237,13 @@ class RedirectHandler {
|
|
|
222
237
|
|
|
223
238
|
// function handler: custom function
|
|
224
239
|
class FunctionHandler {
|
|
225
|
-
constructor(func) {
|
|
240
|
+
constructor(func, ext) {
|
|
226
241
|
this.func = func;
|
|
242
|
+
this.ext = ext;
|
|
227
243
|
}
|
|
228
244
|
|
|
229
245
|
handle(ctx, env) {
|
|
230
|
-
return this.func(ctx, env);
|
|
246
|
+
return this.func(ctx, env, this.ext);
|
|
231
247
|
}
|
|
232
248
|
}
|
|
233
249
|
|
|
@@ -235,12 +251,12 @@ class FunctionHandler {
|
|
|
235
251
|
module.exports = {
|
|
236
252
|
DataHandler, TextHandler: DataHandler, FileHandler, FolderHandler, JSONHandler, RedirectHandler, FunctionHandler,
|
|
237
253
|
handlerConstructors: {
|
|
238
|
-
Data: (
|
|
239
|
-
Text: (
|
|
240
|
-
File: (
|
|
241
|
-
Folder: (
|
|
242
|
-
JSON: (
|
|
243
|
-
Redirect: (
|
|
244
|
-
Function: (
|
|
254
|
+
Data: (data, options) => new DataHandler(data, options),
|
|
255
|
+
Text: (data, options) => new DataHandler(data, options),
|
|
256
|
+
File: (file, options) => new FileHandler(file, options),
|
|
257
|
+
Folder: (folder, options) => new FolderHandler(folder, options),
|
|
258
|
+
JSON: (obj, options) => new JSONHandler(obj, options),
|
|
259
|
+
Redirect: (location, options) => new RedirectHandler(location, options),
|
|
260
|
+
Function: (func, ext) => new FunctionHandler(func, ext)
|
|
245
261
|
}
|
|
246
262
|
};
|
package/src/routers.js
CHANGED
|
@@ -62,9 +62,8 @@ class PathRouter {
|
|
|
62
62
|
let result = this.map['*'];
|
|
63
63
|
let resultPointer = env.pathPointer;
|
|
64
64
|
let current = this.map;
|
|
65
|
-
let resultArgs = [];
|
|
66
65
|
let currentArgs = [];
|
|
67
|
-
let resultArgNames
|
|
66
|
+
let resultArgNames;
|
|
68
67
|
while (env.pathPointer < env.path.length) {
|
|
69
68
|
let segment = '/' + env.path[env.pathPointer];
|
|
70
69
|
if (!current[segment] && !current[':']) break;
|
|
@@ -77,7 +76,6 @@ class PathRouter {
|
|
|
77
76
|
if (current[segment]['*']?.['*'] || current[segment]['*']?.[ctx.method]) {
|
|
78
77
|
result = current[segment]['*'][ctx.method] ?? current[segment]['*']['*'];
|
|
79
78
|
resultPointer = env.pathPointer + 1;
|
|
80
|
-
resultArgs = [...currentArgs];
|
|
81
79
|
resultArgNames = current[segment]['*']['::' + ctx.method] ?? current[segment]['*']['::*'];
|
|
82
80
|
}
|
|
83
81
|
|
|
@@ -88,15 +86,17 @@ class PathRouter {
|
|
|
88
86
|
if (env.pathPointer >= env.path.length && (current['@']?.['*'] || current['@']?.[ctx.method])) {
|
|
89
87
|
result = current['@'][ctx.method] ?? current['@']['*'];
|
|
90
88
|
resultPointer = env.pathPointer;
|
|
91
|
-
resultArgs = currentArgs;
|
|
92
89
|
resultArgNames = current['@']['::' + ctx.method] ?? current['@']['::*'];
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
env.pathPointer = resultPointer;
|
|
97
|
-
if (resultArgNames)
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
if (resultArgNames) {
|
|
95
|
+
const len = resultArgNames.length;
|
|
96
|
+
for (let i = 0; i < len; i++) {
|
|
97
|
+
ctx.params[resultArgNames[i]] = currentArgs[i];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
100
|
return result;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -127,13 +127,22 @@ class HostRouter {
|
|
|
127
127
|
|
|
128
128
|
// expand map
|
|
129
129
|
let current = this.map;
|
|
130
|
+
let args = [];
|
|
130
131
|
for (const segment of routeDomain) {
|
|
132
|
+
if (segment.startsWith('%:')) {
|
|
133
|
+
args.push(segment.substring(2));
|
|
134
|
+
if (!current[':']) current[':'] = {};
|
|
135
|
+
current = current[':'];
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
if (!current['.' + segment]) current['.' + segment] = {};
|
|
132
140
|
current = current['.' + segment];
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
// '*' for non-end check, '@' for end check
|
|
136
144
|
current[routeEnd ? '@' : '*'] = value;
|
|
145
|
+
if (args.length > 0) current['::'] = args;
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
148
|
|
|
@@ -143,14 +152,21 @@ class HostRouter {
|
|
|
143
152
|
let result = this.map['*'];
|
|
144
153
|
let resultPointer = env.hostPointer;
|
|
145
154
|
let current = this.map;
|
|
155
|
+
let currentArgs = [];
|
|
156
|
+
let resultArgNames;
|
|
146
157
|
while (env.hostPointer < env.host.length) {
|
|
147
|
-
|
|
148
|
-
if (!current[segment]) break;
|
|
158
|
+
let segment = '.' + env.host[env.hostPointer];
|
|
159
|
+
if (!current[segment] && !current[':']) break;
|
|
160
|
+
if (!current[segment]) {
|
|
161
|
+
segment = ':';
|
|
162
|
+
currentArgs.push(env.host[env.hostPointer]);
|
|
163
|
+
}
|
|
149
164
|
|
|
150
165
|
// prepare fallback
|
|
151
166
|
if (current[segment]['*']) {
|
|
152
167
|
result = current[segment]['*'];
|
|
153
168
|
resultPointer = env.hostPointer + 1;
|
|
169
|
+
resultArgNames = current[segment]['::'];
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
current = current[segment];
|
|
@@ -164,6 +180,12 @@ class HostRouter {
|
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
env.hostPointer = resultPointer;
|
|
183
|
+
if (resultArgNames) {
|
|
184
|
+
const len = resultArgNames.length;
|
|
185
|
+
for (let i = 0; i < len; i++) {
|
|
186
|
+
ctx.params[resultArgNames[i]] = currentArgs[i];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
167
189
|
return result;
|
|
168
190
|
}
|
|
169
191
|
}
|
|
@@ -181,12 +203,13 @@ class MethodRouter {
|
|
|
181
203
|
|
|
182
204
|
// function router: a simple router that allows you to make custom routing logic
|
|
183
205
|
class FunctionRouter {
|
|
184
|
-
constructor(fn) {
|
|
206
|
+
constructor(fn, ext) {
|
|
185
207
|
this.fn = fn;
|
|
208
|
+
this.ext = ext;
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
route(env, ctx) {
|
|
189
|
-
return this.fn(env, ctx);
|
|
212
|
+
return this.fn(env, ctx, this.ext);
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
215
|
|
|
@@ -237,12 +260,11 @@ class SetCodeRouter {
|
|
|
237
260
|
module.exports = {
|
|
238
261
|
PathRouter, HostRouter, MethodRouter, FunctionRouter, PathArgRouter, HostArgRouter, SetCodeRouter,
|
|
239
262
|
routerConstructors: {
|
|
240
|
-
Path: (
|
|
241
|
-
Host: (
|
|
242
|
-
Method: (
|
|
243
|
-
Function: (
|
|
244
|
-
PathArg: (
|
|
245
|
-
|
|
246
|
-
SetCode: (...args) => new SetCodeRouter(...args)
|
|
263
|
+
Path: (end, map) => new PathRouter(end, map),
|
|
264
|
+
Host: (end, map) => new HostRouter(end, map),
|
|
265
|
+
Method: (methodMap) => new MethodRouter(methodMap),
|
|
266
|
+
Function: (fn, ext) => new FunctionRouter(fn, ext),
|
|
267
|
+
PathArg: (name, next) => new PathArgRouter(name, next),
|
|
268
|
+
SetCode: (handlers, next) => new SetCodeRouter(handlers, next)
|
|
247
269
|
}
|
|
248
270
|
};
|
package/src/server.js
CHANGED
|
@@ -14,6 +14,7 @@ const http2 = require('http2');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const stream = require('stream');
|
|
16
16
|
const EventEmitter = require('events');
|
|
17
|
+
const constants = require('constants');
|
|
17
18
|
|
|
18
19
|
// server class
|
|
19
20
|
class Server extends EventEmitter {
|
|
@@ -106,11 +107,12 @@ class Server extends EventEmitter {
|
|
|
106
107
|
} else if (typeof handler === 'function') { // function
|
|
107
108
|
await handler(ctx, env);
|
|
108
109
|
} else if (typeof handler === 'string') { // string
|
|
110
|
+
const data = Buffer.from(handler, 'utf8');
|
|
109
111
|
ctx.res.writeHead(200, {
|
|
110
112
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
111
|
-
'Content-Length':
|
|
113
|
+
'Content-Length': data.length
|
|
112
114
|
});
|
|
113
|
-
ctx.res.end(
|
|
115
|
+
ctx.res.end(data);
|
|
114
116
|
} else if (handler instanceof Uint8Array) { // buffer
|
|
115
117
|
ctx.res.writeHead(200, {
|
|
116
118
|
'Content-Type': 'application/octet-stream',
|
|
@@ -130,11 +132,12 @@ class Server extends EventEmitter {
|
|
|
130
132
|
} else if (typeof handler === 'function') { // function
|
|
131
133
|
await handler(ctx, env);
|
|
132
134
|
} else if (typeof handler === 'string') { // string
|
|
135
|
+
const data = Buffer.from(handler, 'utf8');
|
|
133
136
|
ctx.res.writeHead(code, {
|
|
134
137
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
135
|
-
'Content-Length':
|
|
138
|
+
'Content-Length': data.length
|
|
136
139
|
});
|
|
137
|
-
ctx.res.end(
|
|
140
|
+
ctx.res.end(data);
|
|
138
141
|
} else if (handler instanceof Uint8Array) { // buffer
|
|
139
142
|
ctx.res.writeHead(code, {
|
|
140
143
|
'Content-Type': 'application/octet-stream',
|
|
@@ -164,11 +167,12 @@ class Server extends EventEmitter {
|
|
|
164
167
|
} else if (typeof handler === 'function') { // function
|
|
165
168
|
await handler(ctx, env);
|
|
166
169
|
} else if (typeof handler === 'string') { // string
|
|
170
|
+
const data = Buffer.from(handler, 'utf8');
|
|
167
171
|
ctx.res.writeHead(code, {
|
|
168
172
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
169
|
-
'Content-Length':
|
|
173
|
+
'Content-Length': data.length
|
|
170
174
|
});
|
|
171
|
-
ctx.res.end(
|
|
175
|
+
ctx.res.end(data);
|
|
172
176
|
} else if (handler instanceof Uint8Array) { // buffer
|
|
173
177
|
ctx.res.writeHead(code, {
|
|
174
178
|
'Content-Type': 'application/octet-stream',
|