topbit 1.0.0 → 3.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.
Files changed (89) hide show
  1. package/LICENSE +128 -0
  2. package/README.cn.md +1519 -0
  3. package/README.md +1483 -0
  4. package/bin/app.js +17 -0
  5. package/bin/loadinfo.sh +18 -0
  6. package/bin/new-ctl.js +234 -0
  7. package/bin/newapp.js +22 -0
  8. package/demo/allow.js +98 -0
  9. package/demo/cert/localhost-cert.pem +19 -0
  10. package/demo/cert/localhost-privkey.pem +28 -0
  11. package/demo/controller/api.js +15 -0
  12. package/demo/extends.js +5 -0
  13. package/demo/group-api.js +161 -0
  14. package/demo/group-api2.js +109 -0
  15. package/demo/http2.js +34 -0
  16. package/demo/http2_proxy_backend.js +45 -0
  17. package/demo/http2proxy.js +48 -0
  18. package/demo/http_proxy_backend.js +44 -0
  19. package/demo/httpproxy.js +47 -0
  20. package/demo/loader.js +27 -0
  21. package/demo/log.js +118 -0
  22. package/demo/memlimit.js +31 -0
  23. package/demo/min.js +7 -0
  24. package/demo/serv.js +15 -0
  25. package/images/middleware.jpg +0 -0
  26. package/images/topbit-middleware.png +0 -0
  27. package/images/topbit.png +0 -0
  28. package/package.json +42 -11
  29. package/src/_loadExtends.js +21 -0
  30. package/src/bodyparser.js +420 -0
  31. package/src/connfilter.js +125 -0
  32. package/src/context1.js +166 -0
  33. package/src/context2.js +182 -0
  34. package/src/ctxpool.js +39 -0
  35. package/src/ext.js +318 -0
  36. package/src/extends/Http2Pool.js +365 -0
  37. package/src/extends/__randstring.js +24 -0
  38. package/src/extends/cookie.js +44 -0
  39. package/src/extends/cors.js +334 -0
  40. package/src/extends/errorlog.js +252 -0
  41. package/src/extends/http2limit.js +126 -0
  42. package/src/extends/http2proxy.js +691 -0
  43. package/src/extends/jwt.js +217 -0
  44. package/src/extends/mixlogger.js +63 -0
  45. package/src/extends/paramcheck.js +266 -0
  46. package/src/extends/proxy.js +662 -0
  47. package/src/extends/realip.js +34 -0
  48. package/src/extends/referer.js +68 -0
  49. package/src/extends/resource.js +398 -0
  50. package/src/extends/session.js +174 -0
  51. package/src/extends/setfinal.js +50 -0
  52. package/src/extends/sni.js +48 -0
  53. package/src/extends/sse.js +293 -0
  54. package/src/extends/timing.js +111 -0
  55. package/src/extends/tofile.js +123 -0
  56. package/src/fastParseUrl.js +426 -0
  57. package/src/headerLimit.js +18 -0
  58. package/src/http1.js +336 -0
  59. package/src/http2.js +337 -0
  60. package/src/httpc.js +251 -0
  61. package/src/lib/npargv.js +354 -0
  62. package/src/lib/zipdata.js +45 -0
  63. package/src/loader/loader.js +999 -0
  64. package/src/logger.js +32 -0
  65. package/src/loggermsg.js +349 -0
  66. package/src/makeId.js +200 -0
  67. package/src/midcore.js +213 -0
  68. package/src/middleware1.js +103 -0
  69. package/src/middleware2.js +116 -0
  70. package/src/monitor.js +380 -0
  71. package/src/movefile.js +30 -0
  72. package/src/optionsCheck.js +54 -0
  73. package/src/randstring.js +23 -0
  74. package/src/router.js +682 -0
  75. package/src/sendmsg.js +27 -0
  76. package/src/strong.js +72 -0
  77. package/src/token/token.js +461 -0
  78. package/src/topbit.js +1293 -0
  79. package/src/versionCheck.js +31 -0
  80. package/test/test-bigctx.js +29 -0
  81. package/test/test-daemon-args.js +7 -0
  82. package/test/test-ext.js +81 -0
  83. package/test/test-find.js +69 -0
  84. package/test/test-route-sort.js +71 -0
  85. package/test/test-route.js +49 -0
  86. package/test/test-route2.js +51 -0
  87. package/test/test-run-args.js +7 -0
  88. package/test/test-url.js +52 -0
  89. package/main.js +0 -0
package/README.md ADDED
@@ -0,0 +1,1483 @@
1
+ ![](images/topbit.png)
2
+
3
+ # Topbit
4
+
5
+ [中文文档](README.cn.md)
6
+
7
+ Topbit is a server-side Web framework based on Node.js. It has no third-party dependencies and is optimized for extreme performance with a unique routing and middleware grouping execution mechanism.
8
+
9
+ **Core Features:**
10
+
11
+ * **Request Context Design:** Shields interface differences.
12
+ * **Global Middleware Pattern.**
13
+ * **Route Grouping and Naming.**
14
+ * **Group-Based Middleware Execution:** Middleware is matched and executed based on request methods and route groups.
15
+ * **Daemon Mode:** Supports multi-process clustering and automated worker process load adjustment.
16
+ * **Load Monitoring:** Displays child process load status.
17
+ * **Body Parsing:** Parses body data by default.
18
+ * **HTTP/1.1 & HTTP/2:** Supports enabling HTTP/1.1 or HTTP/2 via configuration. Allows simultaneous support for HTTP/2 and HTTP/1.1.
19
+ * **HTTPS Support:** Supports HTTPS configuration (HTTP/2 service requires HTTPS).
20
+ * **Request Limiting:** Limits the maximum number of visits per single IP within a specific time period.
21
+ * **IP Blacklist and Whitelist.**
22
+ * **Memory Management:** In cluster mode, monitors child processes and restarts them if they exceed the maximum memory limit.
23
+ * **Auto-Scaling:** Optional automatic load mode: creates new child processes to handle requests based on load and restores the initial state when idle.
24
+ * **Security:** Default settings related to network security to avoid DDoS attacks and other network security issues at the software service level.
25
+
26
+ ## Installation
27
+
28
+ ```javascript
29
+ npm i topbit
30
+ ```
31
+
32
+ You can also install via yarn:
33
+
34
+ ```javascript
35
+ yarn add topbit
36
+ ```
37
+
38
+ ## Minimal Example
39
+
40
+ ```javascript
41
+ 'use strict'
42
+
43
+ const Topbit = require('topbit')
44
+
45
+ const app = new Topbit({
46
+ debug: true
47
+ })
48
+
49
+ app.run(1234)
50
+ ```
51
+
52
+ When no routes are added, Topbit adds a default route:
53
+
54
+ `/*`
55
+
56
+ Visiting via a browser will show a very simple page. This is merely for initial understanding and documentation access; it will not affect actual development.
57
+
58
+ ## Add a Route
59
+
60
+ ``` JavaScript
61
+ 'use strict'
62
+
63
+ const Topbit = require('topbit')
64
+
65
+ const app = new Topbit({
66
+ debug: true
67
+ })
68
+
69
+
70
+ app.get('/', async ctx => {
71
+ ctx.to('success')
72
+ })
73
+
74
+ // Defaults to listening on 0.0.0.0, parameters are consistent with the native listen interface.
75
+ app.run(1234)
76
+
77
+ ```
78
+
79
+ `ctx.data` is the response data to be returned. You can also use `ctx.to(data)`.
80
+ > Actually, `ctx.to()` internally sets the value of `ctx.data`. **Using `ctx.to()` to set return data is recommended.**
81
+
82
+ ## Using Import
83
+
84
+ In `.mjs` files, you can use ES6 import:
85
+
86
+ ```javascript
87
+ import Topbit from 'topbit'
88
+
89
+ const app = new Topbit({
90
+ debug: true
91
+ })
92
+
93
+ app.get('/', async ctx => {
94
+ ctx.to('success')
95
+ })
96
+
97
+ app.run(1234)
98
+
99
+ ```
100
+
101
+ ## Routes and Request Types
102
+
103
+ The HTTP start line defines the request type, also known as the Request Method. Current request methods:
104
+ ```
105
+ GET POST PUT PATCH DELETE OPTIONS TRACE HEAD
106
+ ```
107
+
108
+ The first 6 are the most commonly used. For each request type, the router has a corresponding lowercase function for mounting routes. For convenience, after initializing the app, you can use the shortcut calls with the same names on the `app` instance. (The framework supports these types.)
109
+
110
+ **Example:**
111
+
112
+ ``` JavaScript
113
+
114
+ 'use strict'
115
+
116
+ const Topbit = require('titibit')
117
+
118
+ const app = new Topbit({
119
+ debug: true
120
+ })
121
+
122
+ app.get('/', async c => {
123
+ c.to('success')
124
+ })
125
+
126
+ app.get('/p', async c => {
127
+ c.to(`${c.method} ${c.routepath}`)
128
+ })
129
+
130
+ app.post('/', async c => {
131
+ // Return uploaded data
132
+ c.to(c.body)
133
+ })
134
+
135
+ app.put('/p', async c => {
136
+ c.to({
137
+ method : c.method,
138
+ body : c.body,
139
+ query : c.query
140
+ })
141
+ })
142
+
143
+ // Defaults to listening on 0.0.0.0
144
+ app.run(8080)
145
+
146
+ ```
147
+
148
+ ## Get URL Parameters
149
+
150
+ - Query strings in the URL (parameters like `?a=1&b=2`) are parsed into `c.query`.
151
+ - Form submitted data is parsed into `c.body`.
152
+
153
+ > The content-type for forms is `application/x-www-form-urlencoded`.
154
+
155
+ ``` JavaScript
156
+ 'use strict';
157
+
158
+ const Topbit = require('topbit');
159
+
160
+ let app = new Topbit({
161
+ debug: true
162
+ })
163
+
164
+ app.get('/q', async c => {
165
+ // Query strings after ? in URL are parsed into query.
166
+ // Returns JSON text, main difference is content-type in header is text/json
167
+ c.to(c.query)
168
+ })
169
+
170
+ app.post('/p', async c => {
171
+ // Data submitted via POST/PUT is saved to body.
172
+ // If it's a form, it is automatically parsed; otherwise, raw text value is saved.
173
+ // Middleware can be used to handle various data types.
174
+ c.to(c.body)
175
+ })
176
+
177
+ app.run(2019)
178
+
179
+ ```
180
+
181
+ ## Get POST Data
182
+
183
+ Requests that submit body data are POST and PUT. In front-end pages, this is usually form submission or asynchronous requests.
184
+
185
+ - Form submitted data is parsed into `c.body`.
186
+
187
+ > Form content-type is `application/x-www-form-urlencoded`.
188
+
189
+ > Asynchronous request data often has a content-type of `application/json`.
190
+
191
+ For both types above, `c.body` will be an object.
192
+
193
+ ``` JavaScript
194
+ 'use strict'
195
+
196
+ const Topbit = require('topbit')
197
+
198
+ let app = new Topbit({debug: true})
199
+
200
+ app.post('/p', async c => {
201
+ // POST/PUT data saved to body. Forms are auto-parsed to object.
202
+ // Middleware can be used to process various data.
203
+ c.to(c.body)
204
+ });
205
+
206
+ app.run(2019)
207
+
208
+ ```
209
+
210
+ ## About content-type
211
+
212
+ **application/x-www-form-urlencoded**
213
+
214
+ Basic form types are parsed into `c.body` as a JS object.
215
+
216
+ **text/\***
217
+
218
+ If content-type is `text/*` (starts with text/), such as `text/json`, the framework does not parse it. It simply converts the uploaded data to a string in utf8 format and assigns it to `c.body`. Subsequent processing is decided by the developer.
219
+
220
+ **multipart/form-data;boundary=xxx**
221
+
222
+ If content-type is a file upload type, it is parsed by default. The parsed file objects are placed in `c.files`, accessible via `c.getFile`.
223
+
224
+ **application/json**
225
+
226
+ This type will be parsed using `JSON.parse`.
227
+
228
+ **Other types**
229
+
230
+ If content-type is any other type, `c.body` defaults to point to `c.rawBody`, which is the rawest Buffer data.
231
+
232
+ The framework provides basic core support. Other types need to be handled by the developer or via extensions.
233
+
234
+ To be easy to use while leaving enough space for developers, you can completely discard the default body parsing by setting the initialization option `parseBody` to `false`. You can also extend upon this.
235
+
236
+ The body parsing module is essentially a middleware designed to facilitate extension and replacement.
237
+
238
+ ## to Function Returning Data
239
+
240
+ The `to` function is a wrapper for `c.data`; it sets the value of `c.data`. There are two aliases: `ok` and `oo`. You can choose freely based on the scenario.
241
+
242
+ ``` JavaScript
243
+
244
+ app.get('/', async c => {
245
+ c.to('success')
246
+ })
247
+
248
+ app.get('/randerr', async c => {
249
+ let n = parseInt(Math.random() * 10)
250
+ if (n >= 5) {
251
+ c.ok('success')
252
+ } else {
253
+ // Return 404 status code
254
+ /*
255
+ Equivalent to:
256
+ c.status(404).data = 'not found'
257
+ */
258
+ // You can use chain calls in versions above v22.4.6.
259
+ c.status(404).oo('not found')
260
+ }
261
+ })
262
+
263
+ app.run(1234)
264
+
265
+ ```
266
+
267
+ ## Chain Calls
268
+
269
+ You can use chain calls for `setHeader`, `status`, and `sendHeader`.
270
+
271
+ ```javascript
272
+
273
+ app.get('/', async c => {
274
+
275
+ c.setHeader('content-type', 'text/plain; charset=utf-8')
276
+ .setHeader('x-server', 'nodejs server')
277
+ .status(200)
278
+ .to(`${Date.now()} Math.random()}`)
279
+
280
+ })
281
+
282
+ ```
283
+
284
+ ## Route Parameters
285
+
286
+ ``` JavaScript
287
+ app.get('/:name/:id', async c => {
288
+ // Use : to indicate route parameters, request params are parsed into c.param
289
+ let username = c.param.name;
290
+ let uid = c.param.id;
291
+ c.to(`${username} ${id}`)
292
+ })
293
+
294
+ app.run(8000)
295
+ ```
296
+
297
+ ## Wildcard Path Parameters
298
+
299
+ `*` represents any path, but it must appear at the end of the route.
300
+
301
+ ``` JavaScript
302
+
303
+ app.get('/static/*', async c => {
304
+ // Any path represented by * is parsed into c.param.starPath
305
+ let spath = c.param.starPath
306
+
307
+ c.to(spath)
308
+ })
309
+
310
+ ```
311
+
312
+ ----
313
+
314
+ ## Route Lookup Rules
315
+
316
+ ----
317
+
318
+ The route lookup process strictly controls the order of parameterized routes and routes with `*`, rather than matching based on addition order.
319
+
320
+ Applications developed with previous versions are unaffected; there are no compatibility issues. Stricter ordering reduces the likelihood of conflicts.
321
+
322
+ Route Lookup Strategy:
323
+
324
+ 1. Ordinary string paths.
325
+ 2. Parameterized routes (routes with fewer parameters match first).
326
+ 3. Routes with `*` (matched in longest-to-shortest pattern).
327
+
328
+ ```
329
+ Example:
330
+ Existing routes: /x/y/:id /x/y/* /x/* /x/:key/:id
331
+
332
+ /x/y/123 matches /x/y/:id first, stops matching.
333
+
334
+ /x/y/123/345 matches /x/y/* first, stops matching.
335
+
336
+ /x/q/123 will match /x/:key/:id.
337
+
338
+ /x/a.jpg will match /x/*, other routes cannot match.
339
+
340
+ /x/static/images/a.jpg will match /x/*, other routes cannot match.
341
+ ```
342
+
343
+ ----
344
+
345
+ ## Grouping Routes
346
+
347
+ You can use `app.middleware` to specify middleware and use the returned `group` method to add grouped routes, or use `app.group` directly.
348
+
349
+ **topbit.prototype.middleware(mids, options=null)**
350
+
351
+ - `mids` is an array. Each element is a middleware function or an array where the first element is the middleware and the second is the options for adding that middleware.
352
+ - `options` defaults to `null`. Pass an object for options applying to all `mids`, e.g., `{pre: true}`.
353
+
354
+ **topbit.prototype.group(group_name, callback, prefix=true)**
355
+
356
+ - `group_name` is a string representing the route group name. If it is a valid path, it also serves as the route prefix.
357
+ - `callback` is a callback function. The parameters received by the callback can still call `middleware` and `group`, as well as `get`, `post`, etc., to add routes.
358
+ - `prefix` is a boolean, defaults to `true`. It controls whether `group_name` is added as a route prefix. However, it only acts as a prefix if `group_name` is a valid route string.
359
+
360
+ ```javascript
361
+ 'use strict'
362
+
363
+ const Topbit = require('topbit')
364
+
365
+ const app = new Topbit({
366
+ debug: true
367
+ })
368
+
369
+ // Middleware function
370
+ let mid_timing = async (c, next) => {
371
+ console.time('request')
372
+ await next(c)
373
+ console.timeEnd('request')
374
+ }
375
+
376
+ // The return value of group can use 'use' and 'pre' to add middleware.
377
+ // /api is also added to the route prefix.
378
+ app.group('/api', route => {
379
+ route.get('/test', async c => {
380
+ c.to('api test')
381
+ })
382
+
383
+ route.get('/:name', async c => {
384
+ c.to(c.param)
385
+ })
386
+ })
387
+
388
+ // Add middleware to the corresponding group
389
+ app.use(
390
+ async (c, next) => {
391
+ console.log(c.method, c.headers)
392
+ await next(c)
393
+ }, {group: '/sub'}
394
+ ).group('/sub', route => {
395
+ route.get('/:id', async c => {
396
+ c.to(c.param.id)
397
+ })
398
+ })
399
+
400
+ // Test: Does not conform to route rules, so it won't be a path prefix.
401
+ app.group('Test', route => {
402
+ route.get('/test', async c => {
403
+ console.log(c.group, c.name)
404
+ c.to('test ok')
405
+ }, 'test')
406
+ })
407
+
408
+ app.run(1234)
409
+
410
+ ```
411
+
412
+ Specifying multiple middlewares in this way can be somewhat complex; you can use the `middleware` method. See the example below.
413
+
414
+ ### Assigning Middleware to Groups and Subgroups
415
+
416
+ ```javascript
417
+ 'use strict'
418
+
419
+ const Topbit = require('topbit')
420
+ // Import ToFile extension
421
+ const {ToFile} = require('topbit-toolkit')
422
+
423
+ const app = new Topbit({
424
+ debug: true
425
+ })
426
+
427
+ // Middleware function
428
+ let mid_timing = async (c, next) => {
429
+ console.time('request')
430
+ await next(c)
431
+ console.timeEnd('request')
432
+ }
433
+
434
+ let sub_mid_test = async (c, next) => {
435
+ console.log('mid test start')
436
+ await next(c)
437
+ console.log('mid test end')
438
+ }
439
+
440
+ // group return value can use use, pre, middleware to add middleware.
441
+ // /api is also added to the route prefix.
442
+
443
+ app.middleware([
444
+ // Time recording middleware, runs before receiving body data, so set pre: true
445
+ [ mid_timing, {pre: true} ],
446
+
447
+ // ToFile extension runs after receiving body data, only for POST and PUT requests
448
+ [ new ToFile(), {method: ['POST', 'PUT']} ]
449
+ ])
450
+ .group('/api', route => {
451
+ route.get('/test', async c => {
452
+ c.to('api test')
453
+ })
454
+
455
+ route.get('/:name', async c => {
456
+ c.to(c.param)
457
+ })
458
+
459
+ // Subgroup /sub enables middleware sub_mid_test.
460
+ // Simultaneously, subgroups inherit all middleware from the upper layer.
461
+ route.middleware([sub_mid_test])
462
+ .group('/sub', sub => {
463
+ sub.get('/:key', async c => {
464
+ c.to(c.param)
465
+ })
466
+ })
467
+ })
468
+
469
+ app.run(1234)
470
+
471
+ ```
472
+
473
+ Groups support nested calls, but the hierarchy cannot exceed 9 levels. Usually, nesting more than 3 levels indicates a design issue and should be reconsidered.
474
+
475
+ **This feature is not as convenient and easy to use as the automatic loading mechanism of the `TopbitLoader` extension. However, in practice, requirements vary. Sometimes you have to use a single file for the service while still taking advantage of the framework's routing and middleware grouping, and also conveniently writing logic-clear, structured code. Therefore, `middleware` and `group` interfaces are convenient for handling this. Also, if you are not used to TopbitLoader's MCM pattern (Middleware - Controller - Model, similar to MVC), this method combines well with other module code.**
476
+
477
+ The function of assigning routing groups above is non-intrusive; it will not affect existing code, nor will it conflict with TopbitLoader.
478
+
479
+ **!! Complex route handling functions should be placed in separate modules and completed using a unified automated loading function.**
480
+
481
+ It supports adding via return values, so passing a callback function is not mandatory:
482
+
483
+ ```javascript
484
+ 'use strict'
485
+
486
+ const Topbit = require('topbit')
487
+ // Import ToFile extension
488
+ const {ToFile} = require('topbit-toolkit')
489
+
490
+ const app = new Topbit({
491
+ debug: true
492
+ })
493
+
494
+ // Middleware function
495
+ let mid_timing = async (c, next) => {
496
+ console.time('request')
497
+ await next(c)
498
+ console.timeEnd('request')
499
+ }
500
+
501
+ let sub_mid_test = async (c, next) => {
502
+ console.log('mid test start')
503
+ await next(c)
504
+ console.log('mid test end')
505
+ }
506
+
507
+ let route = app.middleware([
508
+ // Time recording middleware, pre set to true
509
+ [ mid_timing, {pre: true} ],
510
+
511
+ // ToFile extension runs after receiving body data, only for POST and PUT
512
+ [ new ToFile(), {method: ['POST', 'PUT']} ]
513
+ ])
514
+ .group('/api')
515
+
516
+ route.get('/test', async c => {
517
+ c.to('api test')
518
+ })
519
+
520
+ route.get('/:name', async c => {
521
+ c.to(c.param)
522
+ })
523
+
524
+ // Subgroup /sub enables middleware sub_mid_test.
525
+ // Subgroup inherits upper layer middleware.
526
+ route.middleware([sub_mid_test])
527
+ .group('/sub', sub => {
528
+ sub.get('/:key', async c => {
529
+ c.to(c.param)
530
+ })
531
+ })
532
+
533
+ app.run(1234)
534
+
535
+ ```
536
+
537
+ ----
538
+
539
+ ## Uploading Files
540
+
541
+ Uploaded files are parsed by default. You can turn this off by passing the `parseBody` option when initializing the service (detailed in options below).
542
+ Parsed file data is stored in `c.files`. The specific structure is shown later.
543
+
544
+ ``` JavaScript
545
+ 'use strict'
546
+
547
+ const Topbit = require('topbit')
548
+
549
+ const app = new Topbit()
550
+
551
+ app.post('/upload', async c => {
552
+
553
+ let f = c.getFile('image')
554
+
555
+ // Helper functions: makeName generates a name based on timestamp by default,
556
+ // extName parses the file extension.
557
+ // let fname = `${c.ext.makeName()}${c.ext.extName(f.filename)}`
558
+
559
+ // Generate a unique filename based on original extension + timestamp + random number.
560
+ let fname = c.ext.makeName(f.filename)
561
+
562
+ try {
563
+ c.to(await c.moveFile(f, fname))
564
+ } catch (err) {
565
+ c.status(500).to(err.message)
566
+ }
567
+
568
+ }, 'upload-image'); // Name the route 'upload-image', accessible in c.name.
569
+
570
+ app.run(1234)
571
+
572
+ ```
573
+
574
+ ## c.files Data Structure
575
+
576
+ This structure is designed based on the data construction of HTTP protocol file uploads. HTTP protocol allows multiple files for the same upload name, so it parses into an array. Using `getFile` returns the first file by default, as usually, one upload name corresponds to one file.
577
+
578
+ > For the front-end, the upload name is the name attribute in the HTML form: `<input type="file" name="image">`.
579
+ > `image` is the upload name; do not confuse upload name with filename.
580
+
581
+ ```javascript
582
+ {
583
+ image : [
584
+ {
585
+ 'content-type': CONTENT_TYPE,
586
+ // Available since 23.2.6, alias for content-type for easier access
587
+ type: CONTENT_TYPE,
588
+ filename: ORIGIN_FILENAME,
589
+ start : START,
590
+ end : END,
591
+ length: LENGTH,
592
+ rawHeader: HEADER_DATA,
593
+ headers: {...}
594
+ },
595
+ ...
596
+ ],
597
+
598
+ video : [
599
+ {
600
+ 'content-type': CONTENT_TYPE,
601
+ // Available since 23.2.6, alias for content-type
602
+ type: CONTENT_TYPE,
603
+ filename: ORIGIN_FILENAME,
604
+ start : START,
605
+ end : END,
606
+ length: LENGTH,
607
+ rawHeader: HEADER_DATA,
608
+ headers: {...}
609
+ },
610
+ ...
611
+ ]
612
+ }
613
+ ```
614
+
615
+ `c.getFile` indexes by name. The default index value is 0. If a number less than 0 is passed, it gets the entire file array; returns null if not found.
616
+
617
+ ## Max Body Data Limit
618
+
619
+ ```javascript
620
+ 'use strict'
621
+
622
+ const Topbit = require('topbit')
623
+
624
+ const app = new Topbit({
625
+ // Allows POST or PUT request data max value to be approx 20MB.
626
+ // Unit is bytes.
627
+ maxBody: 20000000
628
+ })
629
+
630
+ //...
631
+
632
+ app.run(1234)
633
+
634
+ ```
635
+
636
+ ## Middleware
637
+
638
+ Middleware is a very useful pattern. Implementation varies slightly between languages, but the essence is the same. The middleware mechanism allows developers to organize code better and implement complex logic easily. In fact, the entire framework runs on a middleware pattern.
639
+
640
+ Middleware Diagram:
641
+
642
+ ![](images/middleware.jpg)
643
+
644
+ This framework's middleware is designed to distinguish by route groups and recognize different request types to determine whether to execute or skip to the next layer. This makes it extremely fast. Multiple routes and groups have their own middleware, do not conflict, and avoid meaningless calls. Reference format:
645
+
646
+ ``` JavaScript
647
+
648
+ /*
649
+ The second parameter is optional; omitting it enables the middleware globally.
650
+ Here, the second parameter indicates: execute only for POST requests AND the route group must be /api.
651
+ This design ensures execution on demand, avoiding unnecessary operations.
652
+ */
653
+ app.add(async (c, next) => {
654
+ console.log('before');
655
+ await next(c);
656
+ console.log('after');
657
+ }, {method: 'POST', group: '/api'});
658
+
659
+ ```
660
+
661
+ Middleware added with `add` executes in reverse order of addition (LIFO), which is the standard onion model. To provide logic that is easier to understand, the `use` interface is provided. Middleware added with `use` executes in the order of addition (FIFO). Different frameworks have different logic for order implementation, but sequential execution suits developer habits better.
662
+
663
+ **It is recommended to use `use` to add middleware:**
664
+
665
+ ``` JavaScript
666
+ // Executes first
667
+ app.use(async (c, next) => {
668
+ let start_time = Date.now()
669
+ await next(c)
670
+ let end_time = Date.now()
671
+ console.log(end_time - start_time)
672
+ })
673
+
674
+ // Executes later
675
+ app.use(async (c, next) => {
676
+ console.log(c.method, c.path)
677
+ await next(c)
678
+ })
679
+
680
+ // use can be cascaded: app.use(m1).use(m2)
681
+ // Available after v21.5.4, but this feature is not critical
682
+ // because the topbit-loader extension offers much more powerful functionality.
683
+ ```
684
+
685
+ ## Topbit Complete Flow Chart
686
+
687
+ ![](images/topbit-middleware.png)
688
+
689
+ > **It is important to know that internally, body data reception and parsing are also middleware. The order is deliberately arranged, separating `pre` and `use` interfaces.**
690
+
691
+ ## Middleware Parameters
692
+
693
+ Using `use` or `pre` interfaces to add middleware supports a second parameter for precise control via option properties:
694
+
695
+ * `group`: Route group. Indicates which group to execute for.
696
+ * `method`: Request method. String or Array, must be uppercase.
697
+ * `name`: Request name. Indicates execution only for this request name.
698
+
699
+ Example:
700
+
701
+ ```javascript
702
+
703
+ app.get('/xyz', async c => {
704
+ //...
705
+ // Route group named proxy
706
+ }, {group: 'proxy'})
707
+
708
+ app.use(proxy, {
709
+ method : ['PUT', 'POST', 'GET', 'DELETE', 'OPTIONS'],
710
+ // Execute for requests in route group 'proxy'.
711
+ group : 'proxy'
712
+ })
713
+ ```
714
+
715
+ ## pre (Before Receiving Body Data)
716
+
717
+ The main difference between middleware added via the `pre` interface and `use` is that `pre` executes before receiving body data. It can be used for permission filtering operations before receiving data. Its parameters are consistent with `use`.
718
+
719
+ For a consistent development experience, you can use the `use` interface and simply specify `pre` in the options:
720
+
721
+ ```javascript
722
+ let setbodysize = async (c, next) => {
723
+ // Set max body receive data to ~10k.
724
+ c.maxBody = 10000;
725
+ await next(c);
726
+ };
727
+
728
+ // Equivalent to app.pre(setbodysize);
729
+ app.use(setbodysize, {pre: true});
730
+
731
+ ```
732
+
733
+ Using `pre` allows for more complex processing, including intercepting and not executing the next layer. For example, the `proxy` module in the `topbit-toolkit` extension uses this feature to implement a high-performance proxy service directly as a framework middleware. Its main operation is to set the request's `data` event to receive data at this layer, handle other logic, and return directly.
734
+
735
+ **Dynamically limiting request body size based on different request types**
736
+
737
+ This requirement can be solved by adding middleware via `pre`:
738
+
739
+ ```javascript
740
+
741
+ const app = new Topbit({
742
+ // Default max body limit ~10M.
743
+ maxBody: 10000000
744
+ })
745
+
746
+ app.pre(async (c, next) => {
747
+
748
+ let ctype = c.headers['content-type'] || ''
749
+
750
+ if (ctype.indexOf('text/') === 0) {
751
+ // 50K
752
+ c.maxBody = 50000
753
+ } else if (ctype.indexOf('application/') === 0) {
754
+ // 100K
755
+ c.maxBody = 100000
756
+ } else if (ctype.indexOf('multipart/form-data') < 0) {
757
+ // 10K
758
+ c.maxBody = 10000
759
+ }
760
+
761
+ await next(c)
762
+
763
+ }, {method: ['POST', 'PUT']})
764
+
765
+
766
+ ```
767
+
768
+ If these parameters appear in the file simultaneously, it can look complex and be hard to maintain, but the functionality is powerful. Therefore, leaving it to automated program completion can greatly simplify coding.
769
+
770
+ **For complete project structure setup, please use `topbit-loader`. This extension completes automatic loading of routes/models and automatic orchestration of middleware. <a target=_blank href="https://gitee.com/daoio/topbit-loader">topbit-loader</a>**
771
+
772
+ ## HTTPS
773
+
774
+ ```javascript
775
+ 'use strict'
776
+
777
+ const Topbit = require('topbit')
778
+
779
+ // Just pass the path to the certificate and key files
780
+ const app = new Topbit({
781
+ // './xxx.pem' file also works
782
+ cert: './xxx.cert',
783
+ key: './xxx.key'
784
+ })
785
+
786
+ app.run(1234)
787
+
788
+ ```
789
+
790
+ ## Simultaneous Support for HTTP/2 and HTTP/1.1 (Compatibility Mode)
791
+
792
+ Compatibility mode utilizes the ALPN protocol and requires HTTPS, so certificates and keys must be configured.
793
+
794
+ ```javascript
795
+ 'use strict'
796
+
797
+ const Topbit = require('topbit')
798
+
799
+ // Just pass the path to the certificate and key files
800
+ const app = new Topbit({
801
+ cert: './xxx.cert',
802
+ key: './xxx.key',
803
+ // Enable http2 and allow http1; compatibility mode is enabled automatically
804
+ http2: true,
805
+ allowHTTP1: true
806
+ })
807
+
808
+ app.run(1234)
809
+
810
+ ```
811
+
812
+ ## Configuration Options
813
+
814
+ Full configuration options for app initialization are as follows. Please read the comments carefully.
815
+
816
+ ``` JavaScript
817
+ {
818
+ // This config represents max bytes for POST/PUT form submission and file uploads.
819
+ maxBody : 8000000,
820
+
821
+ // Max number of files to parse
822
+ maxFiles : 12,
823
+
824
+ daemon : false, // Enable daemon mode
825
+
826
+ /*
827
+ After enabling daemon mode, if path is not empty string, pid is written to this file. Used for service management.
828
+ */
829
+ pidFile : '',
830
+
831
+ // Whether to enable global logging. true enables it, outputting request info or writing to file.
832
+ globalLog: false,
833
+
834
+ // Log output type: 'stdio' for terminal, 'file' for file output.
835
+ logType : 'stdio',
836
+
837
+ // File path for successful request logs
838
+ logFile : '',
839
+
840
+ // File path for error request logs
841
+ errorLogFile : '',
842
+
843
+ // Max lines per log file
844
+ logMaxLines: 50000,
845
+
846
+ // Max number of historical log files
847
+ logHistory: 50,
848
+
849
+ // Custom log handler function
850
+ logHandle: null,
851
+
852
+ // Enable HTTPS
853
+ https : false,
854
+
855
+ http2 : false,
856
+
857
+ allowHTTP1: false,
858
+
859
+ // HTTPS key and cert file paths. If paths are set, https is automatically set to true.
860
+ key : '',
861
+ cert : '',
862
+
863
+ // Server options written in 'server', passed during http service initialization. Reference http2.createSecureServer, tls.createServer
864
+ server : {
865
+ handshakeTimeout: 8192, // TLS handshake timeout
866
+ //sessionTimeout: 350,
867
+ },
868
+
869
+ // Set server timeout in ms. Specific request timeouts can be set in specific requests.
870
+ timeout : 15000,
871
+
872
+ debug : false,
873
+
874
+ // Ignore trailing / in paths
875
+ ignoreSlash: true,
876
+
877
+ // Enable request limiting
878
+ useLimit: false,
879
+
880
+ // Max connections, 0 means no limit
881
+ maxConn : 1024,
882
+
883
+ // Max connections per single IP within unit time, 0 means no limit
884
+ maxIPRequest: 0,
885
+
886
+ // Rate limit unit time for IP, 1 means 1 second. Default 60 seconds. Range 0.1 ~ 86400.
887
+ unitTime : 60,
888
+
889
+ // Display load info, requires enabling cluster mode via daemon interface
890
+ loadMonitor : true,
891
+
892
+ // Load info type: text, json, --null
893
+ // json type is for program communication, convenient for interface development
894
+ loadInfoType : 'text',
895
+
896
+ // Load info file path. If not set, outputs to terminal; otherwise saves to file.
897
+ loadInfoFile : '',
898
+
899
+ // Data to return for 404
900
+ notFound: 'Not Found',
901
+
902
+ // Data to return for 400
903
+ badRequest : 'Bad Request',
904
+
905
+ // Percentage parameter controlling max memory usage for child processes. Range -0.42 ~ 0.36. Base is 0.52, so default is 80%.
906
+ memFactor: 0.28,
907
+
908
+ // Max URL length
909
+ maxUrlLength: 2048,
910
+
911
+ // Max number of request context cache pool.
912
+ maxpool: 4096,
913
+
914
+ // Timer milliseconds for child process resource reporting.
915
+ monitorTimeSlice: 640,
916
+
917
+ // When globalLog is true, record real IP address? Mainly used in reverse proxy mode.
918
+ realIP: false,
919
+
920
+ // Max allowed querystring parameters.
921
+ maxQuery: 25,
922
+
923
+ // Enable strong mode? If enabled, processes rejectionHandled and uncaughtException events,
924
+ // and captures errors: TypeError,ReferenceError,RangeError,AssertionError,URIError,Error.
925
+ strong: false,
926
+
927
+ // Fast querystring parsing. Multiple values with same name only set the first one, not parsed as array.
928
+ fastParseQuery: false,
929
+
930
+ // Whether to auto decode Query parameters using decodeURIComponent.
931
+ autoDecodeQuery: true,
932
+
933
+ // In multipart format, limit max length of a single form item.
934
+ maxFormLength: 1000000,
935
+
936
+ /* Error handler function. Unifies collection of runtime errors:
937
+ tlsClientError, server error, secureConnection error, clientError, thrown runtime errors.
938
+ errname is a string marking error info and location, format --ERR-CONNECTION--, --ERR-CLIENT--, etc.
939
+
940
+ Usually Node.js thrown errors have code and message.
941
+ errname is optional but passed.
942
+ Pass custom function via config option to implement custom error collection/handling.
943
+ */
944
+ errorHandle: (err, errname) => {
945
+ this.config.debug && console.error(errname, err)
946
+ },
947
+
948
+ // Max load rate percentage. Default 75 means if CPU usage > 75%, automatically create child process.
949
+ // Must enable auto load mode via autoWorker.
950
+ maxLoadRate: 75,
951
+
952
+ // http2 protocol http2Stream timeout. If not set, -1 means consistent with timeout.
953
+ streamTimeout: -1,
954
+
955
+ // Request timeout. This is total request time, mainly to counter malicious requests.
956
+ // e.g. Slowloris attacks where 1 byte is sent per second.
957
+ // Idle timeout won't work, allowing long-term resource occupation.
958
+ // This is a DDoS attack.
959
+ requestTimeout: 100000,
960
+
961
+ };
962
+ // For HTTP status codes, only these two are needed here. Others don't strictly need full support,
963
+ // and you can handle them in your application implementation.
964
+ // Because once execution starts, corresponding status codes can be returned via runtime state.
965
+ // Before that, the framework prepares for the onion model execution, which is very fast.
966
+
967
+ ```
968
+
969
+ ## Request Context
970
+
971
+ Request context is an object encapsulating various request data. This design handles differences between HTTP/1.1 and HTTP/2 protocols and incompatibilities from Node.js evolution. For design and performance, the HTTP2 module encapsulates the request object as a stream, not the `http` module's `IncomingMessage` and `ServerResponse`.
972
+
973
+ **Request Context Properties and Description**
974
+
975
+ | Property | Description |
976
+ | ---- | ---- |
977
+ | version | Protocol version, string type, '1.1' or '2'. |
978
+ | major | Major protocol version number, 1, 2, 3 (HTTP/1.1, HTTP/2, HTTP/3). |
979
+ | maxBody | Max supported request body bytes. Number. Defaults to `maxBody` init option. Can be set automatically in middleware based on request. |
980
+ | method | Request type (GET, POST, etc.). Uppercase string. |
981
+ | host | Service hostname, value of `request.headers.host`. |
982
+ | protocol | Protocol string without colon, 'https', 'http'. |
983
+ | path | Specific request path. |
984
+ | routepath | Actual executed route string. |
985
+ | query | URL passed parameters. |
986
+ | param | Route parameters. |
987
+ | files | Uploaded file information. |
988
+ | body | Body request data. Format depends on content-type (string, object, buffer). |
989
+ | port | Client request port number. |
990
+ | ip | Client request IP address (socket address). Check x-real-ip or x-forwarded-for if using proxy. |
991
+ | headers | Points to `request.headers`. |
992
+ | isUpload() | Is it a file upload request? Checks if content-type header is multipart/form-data. |
993
+ | name | Route name, defaults to empty string. |
994
+ | group | Route group, defaults to empty string. |
995
+ | res | HTTP/1.1 points to response, HTTP/2 points to stream. |
996
+ | req | HTTP/1.1 is `IncomingMessage` object, HTTP/2 points to stream object. |
997
+ | box | Defaults to empty object. Can add any property value to dynamically pass info to next layer components. |
998
+ | service | Object for dependency injection, points to `app.service`. |
999
+ | data | Saves final data to return to client. Assign to data or use `ctx.to`. Pre-v24.x was `ctx.res.body`. |
1000
+ | ext | Provides helper functions. See wiki. |
1001
+ | to(data) | Function to set `ctx.data`. |
1002
+ | write(data) | Directly write data to client. |
1003
+ | moveFile(file:object, target_filepath:string) | Function to move uploaded file to specified path. |
1004
+ | status() | Function to set status code. |
1005
+ | setHeader(k, v) | Function to set header. |
1006
+ | removeHeader(k) | Function to remove header waiting to be sent. |
1007
+ | getFile(name) | Function to get uploaded file info (reads files property). |
1008
+ | sendHeader() | Function to send headers for http2. `setHeader` only caches headers. For http/1.1, this is an empty function for code consistency. |
1009
+ | user | Standard property for user login, defaults to null. |
1010
+ | json(data) | Function, sets return data and marks type as json. |
1011
+ | text(data) | Function, sets return data and marks type as text. |
1012
+ | html(data) | Function, sets return data and marks type as html. |
1013
+ | pipe(filepath) | Function, stream response data. Example: `await ctx.setHeader('content-type', 'text/html').pipe('./index.html')` |
1014
+ | pipeJson(filepath) | Stream file data as json type. |
1015
+ | pipeText(filepath) | Stream file data as text type. |
1016
+ | pipeHtml(filepath) | Stream file data as html type. |
1017
+
1018
+ Note: `to` function only sets `ctx.data` value; data is returned at the end. It's same as direct assignment, but function calls reveal errors faster (wrong assignment adds a property without error but returns incorrect data).
1019
+
1020
+ ## Dependency Injection
1021
+
1022
+ The request context has a `service` item pointing to `app.service`. After initializing the app, all data/instances needed can be mounted to `app.service`.
1023
+
1024
+ ``` JavaScript
1025
+
1026
+ 'use strict';
1027
+
1028
+ const Topbit = require('topbit');
1029
+
1030
+ let app = new Topbit({
1031
+ debug: true
1032
+ });
1033
+
1034
+ // Overwrites if exists, adds if not.
1035
+ app.addService('name', 'first');
1036
+ app.addService('data', {
1037
+ id : 123,
1038
+ ip : '127.0.0.1'
1039
+ });
1040
+
1041
+ /*
1042
+ This might seem useless in a single file where variables are accessible.
1043
+ However, it becomes very important for module separation.
1044
+ */
1045
+ app.get('/info', async c => {
1046
+ c.to({
1047
+ name : c.service.name,
1048
+ data : c.service.data
1049
+ })
1050
+ })
1051
+
1052
+ app.run(1234)
1053
+
1054
+ ```
1055
+
1056
+ ## Extending Request Context
1057
+
1058
+ To add extension support to the request context object, use `app.httpServ.context`. This property is the constructor for the request context.
1059
+
1060
+ **Example:**
1061
+
1062
+ ```javascript
1063
+ 'use strict'
1064
+ const Topbit = require('topbit')
1065
+ const app = new Topbit({
1066
+ debug: true
1067
+ })
1068
+
1069
+ // 'this' represents the request context
1070
+ app.httpServ.context.prototype.testCtx = function () {
1071
+ console.log(this.method, this.path)
1072
+ }
1073
+
1074
+ app.get('/test', async ctx => {
1075
+ ctx.testCtx()
1076
+ })
1077
+
1078
+ app.run(1234)
1079
+
1080
+ ```
1081
+
1082
+ ## app.isMaster and app.isWorker
1083
+
1084
+ Node.js v16.x recommends `isPrimary` over `isMaster`. However, `isMaster` is still available. After Topbit initialization, `app` has two getters: `isMaster` and `isWorker`. They function identically to `cluster` properties. Purposes:
1085
+
1086
+ - Avoid writing `const cluster = require('cluster')` again.
1087
+ - Shield future incompatible changes in `cluster`, enhancing code compatibility.
1088
+
1089
+ ## daemon and run
1090
+
1091
+ `run` parameters: `port`, `host`. `host` defaults to `0.0.0.0`. Can also be `sockPath` (path to .sock file), supported by http listen interface. Using .sock ignores host.
1092
+
1093
+ `daemon` first two parameters match `run`. Third parameter is a number indicating how many child processes to use. Defaults to 0, which automatically creates child processes based on CPU core count. It maintains stable child process count, creating new ones if any terminate unexpectedly.
1094
+
1095
+ **In cluster mode, max child processes will not exceed 2x CPU cores.**
1096
+
1097
+ Example:
1098
+
1099
+ ```javascript
1100
+
1101
+ // Host defaults to 0.0.0.0, port 1234
1102
+ app.run(1234)
1103
+
1104
+ // Listen on localhost, only accessible locally
1105
+ app.run(1234, 'localhost')
1106
+
1107
+ // Use 2 child processes, host defaults to 0.0.0.0
1108
+ app.daemon(1234, 2)
1109
+
1110
+ // Use 3 child processes
1111
+ app.daemon(1234, 'localhost', 3)
1112
+
1113
+ ```
1114
+
1115
+ ## Logging
1116
+
1117
+ The framework provides global logging. When using cluster mode (`daemon` interface), use initialization option `globalLog` to enable global logs and specify a log file. In single process mode, logs output to terminal (can use output/error redirection to save to file).
1118
+
1119
+ **Note: Only daemon execution (cluster mode) allows saving logs to file natively. `run` (single process) outputs to screen; use IO redirection to save.**
1120
+
1121
+ Besides file saving and terminal output, `logHandle` option allows custom log processing functions.
1122
+
1123
+ **Setting `logHandle` invalidates `logFile` and `errorLogFile`. See code for details.**
1124
+
1125
+ Example:
1126
+
1127
+ ``` JavaScript
1128
+
1129
+ const Topbit = require('topbit')
1130
+
1131
+ const app = new Topbit({
1132
+ debug: true,
1133
+ // Enable global log
1134
+ globalLog: true,
1135
+
1136
+ // Output to file. Default is 'stdio' (terminal).
1137
+ logType: 'file'
1138
+
1139
+ // Log file for status codes 2xx or 3xx
1140
+ logFile : '/tmp/topbit.log',
1141
+
1142
+ // Log file for errors (4xx or 5xx)
1143
+ errorLogFile: '/tmp/topbit-error.log',
1144
+
1145
+ // Custom handler function. logFile/errorLogFile become invalid.
1146
+ // Parameters: (worker, message)
1147
+ // worker: see cluster worker docs
1148
+ /*
1149
+ msg is log object, properties:
1150
+ {
1151
+ type : '_log',
1152
+ success : true,
1153
+ log : '@ GET | https://localhost:2021/randst | 200 | 2020-10-31 20:27:7 | 127.0.0.1 | User-Agent'
1154
+ }
1155
+ */
1156
+ logHandle : (w, msg) => {
1157
+ console.log(w.id, msg)
1158
+ }
1159
+
1160
+ })
1161
+
1162
+ app.daemon(1234, 3)
1163
+
1164
+ ```
1165
+
1166
+ Using middleware for logging does not conflict with global logging. However, middleware logging cannot capture 404s returned by the framework when no route matches (as context is not created to avoid overhead).
1167
+
1168
+ Furthermore, this method integrates better with cluster mode as it uses master-worker communication internally.
1169
+
1170
+ ## Message Event Handling
1171
+
1172
+ Based on the `message` event, in daemon mode (based on cluster module), a `setMsgEvent` function is provided to handle events sent by child processes.
1173
+
1174
+ This requires the message sent by worker processes to be an object, with a mandatory `type` property indicating the event name. Other fields can be custom.
1175
+
1176
+ Usage:
1177
+
1178
+ ``` JavaScript
1179
+
1180
+ const Topbit = require('topbit')
1181
+ const cluster = require('cluster')
1182
+
1183
+ const app = new Topbit({
1184
+ debug: true,
1185
+ loadInfoFile: '/tmp/loadinfo.log'
1186
+ })
1187
+
1188
+ if (cluster.isMaster) {
1189
+ app.setMsgEvent('test-msg', (worker, msg, handle) => {
1190
+ // Child process receives message via message event
1191
+ worker.to({
1192
+ id : worker.id,
1193
+ data : 'ok'
1194
+ })
1195
+
1196
+ console.log(msg)
1197
+ })
1198
+ } else {
1199
+ // Receive message sent by worker.to
1200
+ process.on('message', msg => {
1201
+ console.log(msg)
1202
+ })
1203
+
1204
+ setIneterval(() => {
1205
+ process.to({
1206
+ type : 'test-msg',
1207
+ pid : process.pid,
1208
+ time : (new Date()).toLocaleString()
1209
+ })
1210
+ }, 1000)
1211
+
1212
+ }
1213
+
1214
+ ```
1215
+
1216
+ Sending messages from worker processes is complex. Since version 22.4.0, a `send` method is provided for quick message sending. It only sends to master from a worker process, so no extra worker check is needed.
1217
+
1218
+ ## app.to and app.workerMsg
1219
+
1220
+ Let's rewrite the worker message sending part of the code above:
1221
+
1222
+ ```javascript
1223
+
1224
+ const Topbit = require('topbit')
1225
+
1226
+ const app = new Topbit({
1227
+ debug: true,
1228
+ loadInfoFile: '/tmp/loadinfo.log'
1229
+ })
1230
+
1231
+ // Master process registers message event type. Worker process ignores this.
1232
+ app.setMsgEvent('test-msg', (worker, msg, handle) => {
1233
+ // Child process receives message via message event
1234
+ worker.to({
1235
+ id : worker.id,
1236
+ data : 'ok'
1237
+ })
1238
+
1239
+ console.log(msg)
1240
+ })
1241
+
1242
+ // Only worker process listens.
1243
+ app.workerMsg(msg => {
1244
+ console.log(msg)
1245
+ })
1246
+
1247
+ cluster.isWorker
1248
+ &&
1249
+ setInterval(() => {
1250
+ // Only worker executes.
1251
+ app.to('test-msg', {
1252
+ pid: process.pid,
1253
+ time: (new Date).toLocaleString()
1254
+ })
1255
+
1256
+ }, 1000)
1257
+
1258
+ app.daemon(1234, 2)
1259
+
1260
+ ```
1261
+
1262
+ ## Automatically Adjusting Child Process Quantity
1263
+
1264
+ The parameter passed to `daemon` sets the base number of child processes, e.g.:
1265
+
1266
+ ``` JavaScript
1267
+
1268
+ // Use 2 child processes to handle requests.
1269
+ app.daemon(1234, 2)
1270
+
1271
+ ```
1272
+
1273
+ To automatically create child processes based on load and terminate them when idle (maintaining base quantity), use `autoWorker` to set a maximum value. This value must be larger than the base quantity to take effect.
1274
+
1275
+ ```javascript
1276
+
1277
+ // Max 9 child processes.
1278
+ app.autoWorker(9)
1279
+
1280
+ //...
1281
+
1282
+ app.daemon(1234, 2)
1283
+
1284
+ ```
1285
+
1286
+ When load is high, child processes are created. After a period of idleness, processes with 0 connections are terminated, restoring the base number.
1287
+
1288
+ **Available in v21.9.6+. Please use the latest version as this feature has been improved for stability and performance in subsequent versions.**
1289
+
1290
+ ----
1291
+
1292
+ ## Strong Mode
1293
+
1294
+ Enable strong mode via the `strong` option. This monitors `uncaughtException` and `unhandledRejection` events to ensure stable operation. Simplest usage: set `strong` to `true`.
1295
+
1296
+ **All strong mode features can be implemented via the `process` module; this just simplifies it.**
1297
+
1298
+ ```javascript
1299
+ 'use strict';
1300
+
1301
+ const Topbit = require('topbit');
1302
+
1303
+ setTimeout(() => {
1304
+ // Throw exception inside timer loop
1305
+ throw new Error(`test error`)
1306
+ }, 2000);
1307
+
1308
+ const app = new Topbit({
1309
+ // Debug mode, output errors.
1310
+ debug: true,
1311
+ // Enable strong mode
1312
+ strong: true
1313
+ });
1314
+
1315
+ app.run(1234);
1316
+
1317
+ ```
1318
+
1319
+ By default, strong mode captures:
1320
+
1321
+ ```
1322
+ 'TypeError', 'ReferenceError', 'RangeError', 'AssertionError', 'URIError', 'Error'
1323
+ ```
1324
+
1325
+ You can customize handling by passing an object to `strong`.
1326
+
1327
+ ```javascript
1328
+
1329
+ // Core code example
1330
+ const app = new Topbit({
1331
+ // Debug mode, output errors.
1332
+ debug: true,
1333
+ // Enable strong mode
1334
+ strong: {
1335
+ // Silent behavior, no error output.
1336
+ quiet: true,
1337
+ // Custom error handler
1338
+ errorHandle: (err, errname) => {
1339
+ //....
1340
+ },
1341
+
1342
+ // Which errors to catch
1343
+ catchErrors: [
1344
+ 'TypeError', 'URIError', 'Error', 'RangeError'
1345
+ ]
1346
+
1347
+ }
1348
+ });
1349
+
1350
+ ```
1351
+
1352
+ ## Simultaneous HTTP and HTTPS?
1353
+
1354
+ Note the question mark. You shouldn't do this in production. If HTTPS is enabled, HTTP isn't needed, and some frontend features won't work without HTTPS.
1355
+
1356
+ If needed (perhaps for testing), do this:
1357
+
1358
+ ```javascript
1359
+ 'use strict'
1360
+
1361
+ const Topbit = require('topbit')
1362
+ const http = require('node:http')
1363
+ const https = require('https')
1364
+
1365
+ const app = new Topbit({
1366
+ // Enable debug
1367
+ debug: true,
1368
+ })
1369
+
1370
+ // Below are http/1.1 services. For http2, enable http2 and compatibility mode.
1371
+ // Or use topbit-httpc extension.
1372
+
1373
+ // In this case, you must set event listeners manually.
1374
+
1375
+ let http_server = http.createServer(app.httpServ.onRequest())
1376
+ let https_server = https.createServer(app.httpServ.onRequest())
1377
+
1378
+ http_server.listen(2025)
1379
+ https_server.listen(2026)
1380
+
1381
+ ```
1382
+
1383
+ **Note: You cannot support http2 in this scenario, but you can use http2 to be compatible with http1.**
1384
+
1385
+ ## Request Limiting
1386
+
1387
+ The framework provides IP-based rate limiting to prevent dense requests from the same IP. For HTTP/2, use `http2limit` module from `topbit-toolkit`.
1388
+
1389
+ ```javascript
1390
+ 'use strict';
1391
+
1392
+ const Topbit = require('topbit')
1393
+
1394
+ const app = new Topbit({
1395
+ debug : true,
1396
+ // Enable request limiting
1397
+ useLimit: true,
1398
+
1399
+ // IPs allowed without frequency limit
1400
+ allow: new Set(['127.0.0.1']),
1401
+
1402
+ // IPs to deny
1403
+ deny: (ip) => {
1404
+ // Example only, supports function and Set
1405
+ if (ip.indexOf('1.') === 0) return false
1406
+ return true
1407
+ },
1408
+
1409
+ // Max requests per IP within unit time
1410
+ maxIPRequest: 6,
1411
+
1412
+ // Unit time, 15 seconds
1413
+ unitTime: 15,
1414
+
1415
+ // Max concurrent connections per worker process
1416
+ maxConn: 2000,
1417
+
1418
+ loadMonitor: true,
1419
+ loadInfoType : 'text',
1420
+ globalLog : true,
1421
+ logType: 'stdio',
1422
+ // Load info in memory
1423
+ loadInfoFile : '--mem'
1424
+ })
1425
+
1426
+
1427
+ app.get('/', async ctx => {
1428
+ ctx.to('ok')
1429
+ })
1430
+
1431
+ // Use 3 worker processes. Each allows 6 requests/unit time.
1432
+ // Total approx 6 requests? Set maxIPRequest to 2.
1433
+ app.daemon(1234, '0.0.0.0', 3)
1434
+
1435
+ ```
1436
+
1437
+ ## Other
1438
+
1439
+ - After running, Topbit has a final wrapper middleware. Setting `c.data` returns data. It detects simple text types and sets content-type automatically (text/plain, text/html, application/json) if not set.
1440
+
1441
+ - Defaults to limiting max URL length and sets a max memory usage rate based on hardware.
1442
+
1443
+ - All of this can be extended/overridden via config options or middleware.
1444
+
1445
+ - It is fast, and we focus on optimization. If comparing, add multiple middleware and hundreds of routes to test.
1446
+
1447
+ - Provides a `sched` function to quickly set cluster scheduling policy ('rr' or 'none'). Sets `cluster.schedulingPolicy`.
1448
+
1449
+ The framework automatically detects memory size and sets limits on initialization. You can change limits via properties in `secure` after init, but this requires using `daemon` (master managing workers).
1450
+
1451
+ ```javascript
1452
+ 'use strict'
1453
+
1454
+ const Topbit = require('topbit');
1455
+
1456
+ let app = new Topbit();
1457
+
1458
+ /*
1459
+ Operations below can be controlled via memFactor option. See config options above.
1460
+ */
1461
+
1462
+ // Max memory 600M, auto restart only when connection count is 0.
1463
+ app.secure.maxmem = 600_000_000;
1464
+
1465
+ // Mandatory restart max memory limit 900M. Total memory usage including Buffers.
1466
+ // Must be larger than maxmem. If memory > maxmem AND connections != 0,
1467
+ // AND continues to exceed diemem, process restarts immediately.
1468
+ app.secure.diemem = 900_000_000;
1469
+
1470
+ // Max RSS memory 800M. Program memory usage, excluding Buffer allocations.
1471
+ app.secure.maxrss = 800_000_000;
1472
+
1473
+ app.get('/', async c => {
1474
+ c.to('ok');
1475
+ })
1476
+
1477
+ app.daemon(8008, 2);
1478
+
1479
+ ```
1480
+
1481
+ **Note: Requires `loadMonitor` option (enabled by default unless set to false).**
1482
+
1483
+ Service initialization automatically sets configuration based on available system memory. Unless necessary, stick to default configurations.