@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 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
- '*': 'F!'
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
- '*': 'F!'
313
+ ':': {
314
+ '/c': {
315
+ '@': {
316
+ 'GET': 'F!',
317
+ '::GET': [ 'arg' ]
318
+ }
319
+ }
320
+ },
321
+ '*': 'G!'
310
322
  };
311
323
  ```
312
324
 
313
- This format enables fast and flexible path routing while keeping it simple for developers.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jnode/server",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Simple web server package for Node.js.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
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
- async handle(ctx, env) {
22
+ // prebuild headers
23
+ this._statusCode = this.options.statusCode ?? 200;
24
24
  if (typeof this.data === 'string') { // string
25
- ctx.res.writeHead(this.options.statusCode ?? 200, {
25
+ this.data = Buffer.from(this.data, 'utf8');
26
+ this._headers = {
26
27
  'Content-Type': 'text/plain; charset=utf-8',
27
- 'Content-Length': Buffer.byteLength(this.data, 'utf8'),
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
- ctx.res.writeHead(this.options.statusCode ?? 200, {
32
+ this._headers = {
33
33
  'Content-Type': 'application/octet-stream',
34
34
  'Content-Length': this.data.length,
35
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, {
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.obj = obj;
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
- ctx.res.writeHead(this.options.statusCode ?? 200, {
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
- ctx.res.end(data, 'utf8');
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.options.statusCode ?? 307, {
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: (...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)
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) resultArgs.forEach((arg, index) => {
98
- ctx.params[resultArgNames[index]] = arg;
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
- const segment = '.' + env.host[env.hostPointer];
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: (...args) => new PathRouter(...args),
241
- Host: (...args) => new HostRouter(...args),
242
- Method: (...args) => new MethodRouter(...args),
243
- Function: (...args) => new FunctionRouter(...args),
244
- PathArg: (...args) => new PathArgRouter(...args),
245
- HostArg: (...args) => new HostArgRouter(...args),
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': Buffer.byteLength(handler, 'utf8')
113
+ 'Content-Length': data.length
112
114
  });
113
- ctx.res.end(handler, 'utf8');
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': Buffer.byteLength(handler, 'utf8')
138
+ 'Content-Length': data.length
136
139
  });
137
- ctx.res.end(handler, 'utf8');
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': Buffer.byteLength(handler, 'utf8')
173
+ 'Content-Length': data.length
170
174
  });
171
- ctx.res.end(handler, 'utf8');
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',