topbit 1.0.0 → 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.
Files changed (71) hide show
  1. package/LICENSE +128 -0
  2. package/README.cn.md +1562 -0
  3. package/README.md +1272 -0
  4. package/bin/app.js +17 -0
  5. package/bin/loadinfo.sh +18 -0
  6. package/bin/new-ctl.js +230 -0
  7. package/bin/newapp.js +22 -0
  8. package/cache/allow.js +130 -0
  9. package/cache/errserv.js +45 -0
  10. package/cache/minserv.js +167 -0
  11. package/cache/router.js +84 -0
  12. package/cache/rsa/localhost-cert.pem +19 -0
  13. package/cache/rsa/localhost-privkey.pem +28 -0
  14. package/cache/servsock.js +286 -0
  15. package/cache/sni.js +66 -0
  16. package/demo/allow.js +98 -0
  17. package/demo/group-api.js +161 -0
  18. package/demo/group-api2.js +109 -0
  19. package/demo/log.js +118 -0
  20. package/demo/memlimit.js +31 -0
  21. package/demo/min.js +7 -0
  22. package/demo/serv.js +15 -0
  23. package/images/middleware.jpg +0 -0
  24. package/images/titbit-middleware.png +0 -0
  25. package/images/titbit.png +0 -0
  26. package/package.json +38 -8
  27. package/src/bodyparser.js +420 -0
  28. package/src/connfilter.js +125 -0
  29. package/src/context1.js +166 -0
  30. package/src/context2.js +179 -0
  31. package/src/ctxpool.js +38 -0
  32. package/src/ext.js +318 -0
  33. package/src/fastParseUrl.js +426 -0
  34. package/src/headerLimit.js +18 -0
  35. package/src/http1.js +337 -0
  36. package/src/http2.js +337 -0
  37. package/src/httpc.js +251 -0
  38. package/src/loader/loader.js +999 -0
  39. package/src/logger.js +32 -0
  40. package/src/loggermsg.js +349 -0
  41. package/src/makeId.js +200 -0
  42. package/src/midcore.js +213 -0
  43. package/src/middleware1.js +104 -0
  44. package/src/middleware2.js +121 -0
  45. package/src/monitor.js +380 -0
  46. package/src/movefile.js +30 -0
  47. package/src/optionsCheck.js +54 -0
  48. package/src/randstring.js +23 -0
  49. package/src/router.js +682 -0
  50. package/src/sendmsg.js +27 -0
  51. package/src/strong.js +72 -0
  52. package/src/token/token.js +462 -0
  53. package/src/topbit.js +1291 -0
  54. package/src/versionCheck.js +31 -0
  55. package/test/test-bigctx.js +29 -0
  56. package/test/test-daemon-args.js +7 -0
  57. package/test/test-find.js +69 -0
  58. package/test/test-helper.js +81 -0
  59. package/test/test-route-sort.js +71 -0
  60. package/test/test-route.js +49 -0
  61. package/test/test-route2.js +51 -0
  62. package/test/test-run-args.js +7 -0
  63. package/test/test-url.js +52 -0
  64. package/tmp/buff-code +134 -0
  65. package/tmp/devplan +9 -0
  66. package/tmp/evt-test.js +34 -0
  67. package/tmp/fastParseUrl.js +302 -0
  68. package/tmp/router-rule.js +559 -0
  69. package/tmp/test-cdps.js +122 -0
  70. package/tmp/titbit.js +1286 -0
  71. package/main.js +0 -0
package/src/topbit.js ADDED
@@ -0,0 +1,1291 @@
1
+ 'use strict';
2
+
3
+ const process = require('node:process');
4
+
5
+ const versionCheck = require('./versionCheck.js');
6
+ let _vchk = versionCheck();
7
+
8
+ if (_vchk.stat === false) {
9
+ console.error(_vchk.errmsg);
10
+ process.exit(1);
11
+ }
12
+
13
+ const fs = require('node:fs');
14
+ const cluster = require('node:cluster');
15
+ const os = require('node:os');
16
+ const {spawn} = require('node:child_process');
17
+ const Bodyparser = require('./bodyparser.js');
18
+ const Middleware1 = require('./middleware1.js');
19
+ const Middleware2 = require('./middleware2.js');
20
+ const Router = require('./router.js');
21
+ const connfilter = require('./connfilter.js');
22
+ const Http1 = require('./http1.js');
23
+ const Httpt = require('./http2.js');
24
+ const Httpc = require('./httpc.js');
25
+ const loggermsg = require('./loggermsg.js');
26
+ const Monitor = require('./monitor.js');
27
+ const Strong = require('./strong.js');
28
+ const optionsCheck = require('./optionsCheck.js');
29
+ const Context1 = require('./context1.js');
30
+ const Context2 = require('./context2.js');
31
+ const ext = require('./ext.js');
32
+ const TopbitLoader = require('./loader/loader.js')
33
+ const TopbitToken = require('./token/token.js')
34
+
35
+ let __instance__ = 0;
36
+
37
+ let _topbit_server_running = `
38
+ :.:.:.:.:.:.topbit in service.:.:.:.:.:.:
39
+ `;
40
+
41
+ let _topbit_home_page = `<!DOCTYPE html><html>
42
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width"></head>
43
+ <body>
44
+ <div style="width:90%;margin:auto;margin-top:1rem;color:#4a4a4f;">
45
+ <div style="text-align:center;">
46
+ <h1>topbit</h1>
47
+ <br>
48
+ <p>功能强大、简洁高效的Web开发框架。</p>
49
+ <p>文档 & 仓库:
50
+ <a style="text-decoration:none;color:#345689;" href="https://gitee.com/daoio/topbit" target=_blank>
51
+ topbit</a></p>
52
+ </div>
53
+ </div>
54
+ </body>
55
+ </html>`;
56
+
57
+ function sigExit() {
58
+ process.kill(0, 'SIGTERM');
59
+
60
+ //防止有监听SIGTERM不退出的情况。
61
+ setTimeout(() => {
62
+ process.kill(0, 'SIGKILL');
63
+ }, 5);
64
+ }
65
+
66
+ function makeSubAppForGroup(app, grp_name) {
67
+ let makeOptions = (grp, opts) => {
68
+ if (!opts || typeof opts !== 'object') {
69
+ return {group: grp}
70
+ }
71
+
72
+ let options = {...opts}
73
+
74
+ options.group = grp
75
+
76
+ return options
77
+ }
78
+
79
+ let subapp = {
80
+ /* middleware: (mids, options=null) => {
81
+ if (!Array.isArray(mids)) mids = [mids]
82
+
83
+ for (let m of mids) {
84
+ app.use(m, makeOptions(grp_name, options))
85
+ }
86
+
87
+ return subapp
88
+ }, */
89
+
90
+ pre: function (mid, options=null) {
91
+ app.pre(mid, makeOptions(grp_name, options))
92
+ return this
93
+ },
94
+
95
+ use: function (mid, options=null) {
96
+ app.use(mid, makeOptions(grp_name, options))
97
+ return this
98
+ }
99
+ }
100
+
101
+ Object.defineProperties(subapp, {
102
+ __mids__: {
103
+ enumerable: false,
104
+ writable: true,
105
+ value:[]
106
+ },
107
+ __prefix__: {
108
+ enumerable: false,
109
+ writable: true,
110
+ value: ''
111
+ },
112
+ __group__: {
113
+ enumerable: false,
114
+ writable: true,
115
+ value: ''
116
+ },
117
+ })
118
+
119
+ return subapp
120
+ }
121
+
122
+ class Topbit {
123
+ /**
124
+ * @param {object} options 初始化选项,参考值如下:
125
+ * - debug 调试模式,默认为false
126
+ * - maxConn 最大连接数,使用daemon接口,则每个进程都可以最多处理maxConn限制数量,0表示不限制。
127
+ * - deny {Array} IP字符串数组,表示要拒绝访问的IP。
128
+ * - maxIPRequest {number} 单个IP单元时间内最大访问次数。
129
+ * - unitTime {number} 单元时间,配合maxIPRequest,默认为1表示1秒钟清空一次。
130
+ * - maxIPCache {number} 最大IP缓存个数,配合限制IP访问次数使用,默认为50000。
131
+ * - allow {Array} 限制IP请求次数的白名单。
132
+ * - useLimit {bool} 启用连接限制,用于限制请求的选项需要启用此选项才会生效。
133
+ * - timeout {number} 超时。
134
+ * - cert {string} 启用HTTPS要使用的证书文件路径。
135
+ * - key {string} 启用HTTPS的密钥文件路径。
136
+ * - globalLog {bool} 启用全局日志。
137
+ * - maxBody {number} 表示POST/PUT提交表单的最大字节数,包括上传文件。
138
+ * - maxFiles {number} 最大上传文件数量,超过则不处理。
139
+ * - daemon {bool} 启用守护进程模式。
140
+ * - pidFile {string} 保存Master进程PID的文件路径。
141
+ * - logFile {string} 日志文件。
142
+ * - errorLogFile {string} 错误日志文件。
143
+ * - logType {string} 日志类型,支持stdio、file、self
144
+ * - logHandle {function} 自定义日志处理函数,接收参数为worker和msg(json格式的日志)
145
+ * - logHistory {number} 最大日志文件数量,默认为50。
146
+ * - server {object} 服务器选项,参考http2.createSecureServer
147
+ * - notFound {string} 404页面数据。
148
+ * - parseBody {bool} 自动解析上传文件数据,默认为true。
149
+ * - http2 {bool} 默认false。
150
+ * - allowHTTP1 {bool} 默认为false。
151
+ * - loadInfoFile {string} daemon为true,负载信息会输出到设置的文件,默认为空
152
+ * - memFactor {number} 控制内存最大使用量的系数,范围从 -0.45 ~ 0.45,会使用基本系数加上此值并乘以内存总量。默认值0.28。
153
+ * RSS基本系数是0.52。不要设置的太低,提供比较低的值是为了测试使用。
154
+ * - maxUrlLength 最大URL长度,包括path和querystring
155
+ * - maxpool 请求上下文的最大缓存池数量。
156
+ * - loadMonitor true|false,表示是否启用负载监控功能,在daemon模式有效,默认为true。
157
+ * - monitorTimeSlice 子进程获取系统占用资源的定时器时间片,毫秒值,默认为500。
158
+ * - maxQuery 最大允许的querystring的参数,默认为12。
159
+ * - fastParseQuery 快速解析querystring,默认为false,会把多个重名的解析为数组,true表示快速解析,不允许重复的名字,否则仅第一个生效。
160
+ * - maxFormLength 在multipart/form-data类型提交数据时,单个form项的最大值,默认为1000000字节。
161
+ * - errorHandle 收集错误并处理的函数,默认是输出错误信息,接收参数为两个,第一个是错误信息,第二个是错误的名字描述。
162
+ * - ignoreSlash 忽略末尾的/,默认为true。
163
+ * - maxLoadRate 在自动创建子进程平衡负载模式,最大子进程负载率限制:25 ~ 98表示百分比。
164
+ * - streamTimeout http2Stream超时,若不设置,默认采用timeout的设置。
165
+ */
166
+ constructor(options={}) {
167
+ if (__instance__ > 0)
168
+ throw new Error('topbit遵循单例模式,不能构造多次。你可以在多进程或多线程中构造新的实例。');
169
+
170
+ __instance__ += 1;
171
+
172
+ this.config = {
173
+ //此配置表示POST/PUT提交表单的最大字节数,也是上传文件的最大限制,
174
+ maxBody : 50_000_000,
175
+ maxFiles : 12,
176
+ daemon : false, //开启守护进程
177
+
178
+ //开启守护进程模式后,如果设置路径不为空字符串,则会把pid写入到此文件,可用于服务管理。
179
+ pidFile : '',
180
+ logFile : '',
181
+ errorLogFile : '',
182
+
183
+ //最大日志文件数量
184
+ logHistory : 50,
185
+
186
+ logMaxLines : 20000,
187
+
188
+ // stdio or file
189
+ logType : 'stdio',
190
+
191
+ //开启HTTPS
192
+ https : false,
193
+
194
+ http2 : false,
195
+
196
+ allowHTTP1 : false,
197
+
198
+ //HTTPS密钥和证书的路径
199
+ key : '',
200
+ cert : '',
201
+
202
+ //服务器选项,参考http2.createSecureServer、tls.createServer
203
+ server : {
204
+ //TLS握手连接(HANDSHAKE)超时
205
+ handshakeTimeout: 9000
206
+ },
207
+
208
+ //设置服务器超时,毫秒单位。
209
+ timeout : 20000,
210
+
211
+ debug : false,
212
+
213
+ notFound : 'not found',
214
+ badRequest : 'bad request',
215
+ //展示负载信息,必须使用daemon接口
216
+ showLoadInfo : true,
217
+ loadInfoType : 'text', // text | json | orgjson | --null
218
+ loadInfoFile : '',
219
+
220
+ ignoreSlash: true,
221
+ parseBody: true,
222
+
223
+ useLimit: false,
224
+
225
+ //启用全局日志
226
+ globalLog: false,
227
+ logHandle : null,
228
+ realIP: false,
229
+
230
+ //内存使用控制系数,-0.35 ~ 0.35
231
+ memFactor : 0.28,
232
+
233
+ autoDecodeQuery : true,
234
+
235
+ maxUrlLength: 1152,
236
+
237
+ maxpool : 4096,
238
+
239
+ //子进程汇报资源信息的定时器毫秒数。
240
+ monitorTimeSlice: 500,
241
+
242
+ //querystring最大个数
243
+ maxQuery: 25,
244
+
245
+ //快速解析querystring,多个同名的值会仅设置第一个,不会解析成数组。
246
+ fastParseQuery: false,
247
+
248
+ strong : false,
249
+
250
+ //在multipart格式中,限制单个表单项的最大长度。
251
+ maxFormLength: 1000000,
252
+
253
+ //允许的最大表单键值个数。
254
+ maxFormKey: 100,
255
+
256
+ errorHandle: (err, errname = '') => {
257
+ if (err) {
258
+ if (err.code === 'HPE_INVALID_EOF_STATE' || err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
259
+ return;
260
+ }
261
+ }
262
+
263
+ this.config.debug && console.error(errname, err);
264
+ },
265
+
266
+ //-1表示使用timeout的设置。
267
+ streamTimeout: -1,
268
+
269
+ requestTimeout: 100000,
270
+
271
+ maxLoadRate: 75
272
+ };
273
+
274
+ this.whoami = 'topbit';
275
+
276
+ this.limit = {
277
+ maxConn : 2560,
278
+ deny : null,
279
+ deny_type : 's',
280
+ //每秒单个IP可以进行请求次数的上限,0表示不限制。
281
+ maxIPRequest : 0,
282
+ unitTime : 60_000,
283
+ maxIPCache : 10_0000,
284
+ allow : null,
285
+ allow_type : 's',
286
+ };
287
+
288
+ if (typeof options !== 'object') options = {};
289
+
290
+ for(let k in options) {
291
+ switch (k) {
292
+ case 'maxConn':
293
+ optionsCheck(k, options[k], this.limit, {type: 'number', min: 0});
294
+ break;
295
+
296
+ case 'deny':
297
+ optionsCheck(k, options[k], this.limit, {type: ['set', 'function']});
298
+ this.limit.deny_type = (typeof options[k] === 'function') ? 'f' : 's';
299
+ break;
300
+
301
+ case 'maxIPRequest':
302
+ optionsCheck(k, options[k], this.limit, {type: 'number', min: 0});
303
+ break;
304
+
305
+ case 'unitTime':
306
+ if (typeof options[k] === 'number' && options[k] >= 0.1 && options[k] <= 86400) {
307
+ this.limit.unitTime = parseInt(options[k] * 1000);
308
+ }
309
+ break;
310
+
311
+ case 'maxIPCache':
312
+ optionsCheck(k, options[k], this.limit, {type: 'number', min: 1024});
313
+ break;
314
+
315
+ case 'allow':
316
+ optionsCheck(k, options[k], this.limit, {type: ['set', 'function']});
317
+ this.limit.allow_type = (typeof options[k] === 'function') ? 'f' : 's';
318
+ break;
319
+
320
+ case 'logHandle':
321
+ case 'errorHandle':
322
+ optionsCheck(k, options[k], this.config, {type: 'function'});
323
+ break;
324
+
325
+ case 'logMaxLines':
326
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 1, max: 5000000});
327
+ break;
328
+
329
+ case 'memFactor':
330
+ optionsCheck(k, options[k], this.config, {type: 'number', min: -0.45, max: 0.45});
331
+ break;
332
+
333
+ case 'maxUrlLength':
334
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 1, max: 4096});
335
+ break;
336
+
337
+ case 'maxpool':
338
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 2, max: 50000});
339
+ break;
340
+
341
+ case 'monitorTimeSlice':
342
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 5, max: 5000});
343
+ break;
344
+
345
+ case 'maxLoadRate':
346
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 25, max: 98});
347
+ break;
348
+
349
+ case 'maxFiles':
350
+ case 'maxBody':
351
+ case 'maxQuery':
352
+ case 'requestTimeout':
353
+ case 'timeout':
354
+ case 'streamTimeout':
355
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 0});
356
+ break;
357
+
358
+ case 'maxFormLength':
359
+ case 'maxFormKey':
360
+ case 'logHistory':
361
+ optionsCheck(k, options[k], this.config, {type: 'number', min: 1});
362
+ break;
363
+
364
+ case 'logType':
365
+ optionsCheck(k, options[k], this.config, {list: ['stdio','file', '']});
366
+ break;
367
+
368
+ case 'loadInfoType':
369
+ optionsCheck(k, options[k], this.config, {list: ['--null', 'text', 'json', 'orgjson']});
370
+ break;
371
+
372
+ case 'loadMonitor':
373
+ this.config.showLoadInfo = !!options[k];
374
+ break;
375
+
376
+ case 'showLoadInfo':
377
+ case 'daemon':
378
+ case 'debug':
379
+ case 'globalLog':
380
+ case 'ignoreSlash':
381
+ case 'parseBody':
382
+ case 'useLimit':
383
+ case 'http2':
384
+ case 'https':
385
+ case 'autoDecodeQuery':
386
+ case 'realIP':
387
+ case 'fastParseQuery':
388
+ case 'allowHTTP1':
389
+ this.config[k] = !!options[k]; break;
390
+
391
+ case 'notFound':
392
+ case 'badRequest':
393
+ case 'logFile':
394
+ case 'errorLogFile':
395
+ case 'loadInfoFile':
396
+ case 'pidFile':
397
+ optionsCheck(k, options[k], this.config, {type: 'string'});
398
+ break;
399
+
400
+ case 'strong':
401
+ this.config[k] = options[k]; break;
402
+
403
+ default:
404
+ if (this.config[k] === undefined) {
405
+ setTimeout(() => {
406
+ console.error(`\x1b[7m!!未知选项: ${k}\n!!请查看文档使用正确的选项。\n\x1b[0m`);
407
+ }, 500);
408
+ }
409
+ }
410
+ }
411
+
412
+ if (options.server && typeof options.server === 'object') {
413
+ for (let x in options.server) {
414
+ this.config.server[x] = options.server[x];
415
+ }
416
+ }
417
+
418
+ if (options.key && options.cert) {
419
+ this.config.cert = options.cert;
420
+ this.config.key = options.key;
421
+ this.config.https = true;
422
+ } else if (this.config.server.SNICallback && typeof this.config.server.SNICallback === 'function') {
423
+ this.config.https = true;
424
+ }
425
+
426
+ if (this.config.streamTimeout < 0) {
427
+ this.config.streamTimeout = this.config.timeout;
428
+ }
429
+
430
+ //记录当前的运行情况
431
+ this.rundata = {
432
+ conn: 0,
433
+ platform: os.platform(),
434
+ host: '',
435
+ port: 0,
436
+ cpuLast: {user:0,system:0},
437
+ cpuTime: {user:0,system:0},
438
+ mem: {
439
+ rss: 0,
440
+ heapTotal: 0,
441
+ heapUsed: 0,
442
+ external: 0,
443
+ //rss + external
444
+ total: 0,
445
+ arrayBuffers: 0
446
+ },
447
+ cpus: 0
448
+ };
449
+
450
+ /**
451
+ * 用于限制在daemon模式,子进程如果真的超出最大内存限制则会重启子进程。
452
+ * 因为RSS包括了Buffer的占用,所以maxrss包括Buffer默认的最大限制。
453
+ * Node.js 14+运行时堆内存限制已经突破了4G。
454
+ * Buffer的单个块上限也早就突破了2G限制。
455
+ * 注意:Node.js利用Buffer没有理论内存上限的限制。
456
+ * */
457
+ this.totalmem = os.totalmem();
458
+ this.topmem = 4345036800;
459
+
460
+ if (this.topmem >= this.totalmem) {
461
+ this.topmem = parseInt(this.totalmem * 0.9);
462
+ }
463
+
464
+ this.secure = {
465
+ //而超过diemem则直接kill 这限制的是对heap的使用
466
+ //parseInt(this.topmem * (0.5 + this.config.memFactor) )
467
+ diemem : this.topmem,
468
+
469
+ //parseInt(this.topmem * (0.5 + this.config.memFactor) )
470
+ maxmem : parseInt(this.topmem * (0.5 + this.config.memFactor)),
471
+
472
+ maxrss : parseInt(this.totalmem * (0.52 + this.config.memFactor) )
473
+ };
474
+
475
+ //运行时服务,需要在全局添加一些服务插件可以放在此处。
476
+ //如果需要把app相关配置信息,router等传递给请求上下文可以放在此处。
477
+ Object.defineProperty(this, 'service', {
478
+ enumerable: false,
479
+ writable: false,
480
+ configurable: false,
481
+ value: Object.create(null)
482
+ });
483
+
484
+ this.bodyparser = new Bodyparser({
485
+ maxFormKey: this.config.maxFormKey,
486
+ maxFiles: this.config.maxFiles,
487
+ maxFormLength: this.config.maxFormLength
488
+ });
489
+
490
+ this.router = new Router(this.config);
491
+ this.router.__subapp__ = makeSubAppForGroup.bind(this,this);
492
+
493
+ //连接过滤和计数以及超时控制。
494
+ this.connfilter = connfilter;
495
+
496
+ if (this.config.http2) {
497
+ if (this.config.allowHTTP1) {
498
+ this.config.server.allowHTTP1 = true;
499
+ } else if (this.config.server.allowHTTP1) {
500
+ this.config.allowHTTP1 = true;
501
+ }
502
+ }
503
+
504
+ if (this.config.http2 && !this.config.allowHTTP1) {
505
+ this.midware = new Middleware2(this.config);
506
+ } else {
507
+ this.midware = new Middleware1(this.config);
508
+ }
509
+
510
+ this.eventTable = {};
511
+ this.server = {};
512
+ this.__pre_mids__ = [];
513
+ let opts = {
514
+ config: this.config,
515
+ events: this.eventTable,
516
+ router: this.router,
517
+ midware: this.midware,
518
+ service: this.service,
519
+ isWorker: this.isWorker,
520
+ };
521
+
522
+ if (this.config.http2 && !this.config.allowHTTP1) {
523
+ this.httpServ = new Httpt(opts);
524
+ } else {
525
+ this.httpServ = new Http1(opts);
526
+ if (this.config.http2 && this.config.allowHTTP1) {
527
+ let hc = new Httpc();
528
+ hc.init(this);
529
+ }
530
+ }
531
+
532
+ this.logger = null;
533
+ this.monitor = null;
534
+
535
+ this.__init__();
536
+ }
537
+
538
+ __init__() {
539
+ if (this.__init_flag__) return false;
540
+ //workers记录了在cluster模式,每个worker的启动时间,这可以在disconnect事件中检测。
541
+ Object.defineProperties(this, {
542
+ workers: {
543
+ enumerable: false,
544
+ configurable: false,
545
+ writable: false,
546
+ value: Object.create(null)
547
+ },
548
+ workerCount: {
549
+ enumerable: false,
550
+ configurable: false,
551
+ writable: false,
552
+ value: {
553
+ total : 0,
554
+ cur : 0,
555
+ max : 0,
556
+ canAutoFork: true,
557
+ }
558
+ },
559
+
560
+ msgEvent: {
561
+ enumerable: false,
562
+ configurable: false,
563
+ writable: false,
564
+ value: Object.create(null)
565
+ }
566
+ });
567
+
568
+ //如果worker运行在很短时间内退出说明可能存在问题,这时候则终止master进程并立即给出错误信息。
569
+ //注意这是在运行开始就要判断并解决的问题。设置值最好不低于100,也不要太高。
570
+ this.workerErrorTime = 500;
571
+ this.errorBreakCount = 0;
572
+ this.keepWorkersTimer = null;
573
+
574
+ if (this.config.globalLog) {
575
+ this.logger = new loggermsg(this.config);
576
+ if (this.isMaster) {
577
+ this.logger._checkBeforeInit();
578
+ this.logger.init();
579
+ this.setMsgEvent('_log', this.logger.msgEvent());
580
+ }
581
+ }
582
+
583
+ if (this.config.showLoadInfo) {
584
+ this.monitor = new Monitor({
585
+ config : this.config,
586
+ secure : this.secure,
587
+ workers : this.workers,
588
+ rundata : this.rundata,
589
+ workerCount : this.workerCount
590
+ });
591
+
592
+ if (this.isMaster) {
593
+ this.setMsgEvent('_load', this.monitor.msgEvent());
594
+ }
595
+ }
596
+
597
+ if (this.config.strong === true) {
598
+ this.config.strong = {}
599
+ }
600
+
601
+ this.strong = null;
602
+ if (this.config.strong && this.config.strong.toString() === '[object Object]') {
603
+ this.strong = new Strong(this.config.strong);
604
+ this.strong.init();
605
+ }
606
+
607
+ this.ext = ext;
608
+ this.ext = ext;
609
+
610
+ this.context = (type = '') => {
611
+ if (type === 1 || type === '1') return new Context1();
612
+ else if (type === 2 || type === '2') return new Context2();
613
+ return (this.config.http2 && !this.config.allowHTTP1) ? new Context2() : new Context1();
614
+ };
615
+
616
+ this.initMsgEvent();
617
+ this.__init_flag__ = true;
618
+ }
619
+
620
+ /**
621
+ *
622
+ * @param {string} path 路由字符串
623
+ * @param {function} cb 回调函数
624
+ * @param {object|string} options 选项
625
+ */
626
+ get(path, cb, options='') {
627
+ return this.router.get(path, cb, options)
628
+ }
629
+
630
+ /**
631
+ *
632
+ * @param {string} path 路由字符串
633
+ * @param {function} cb 回调函数
634
+ * @param {object|string} options 选项
635
+ */
636
+ post(path, cb, options='') {
637
+ return this.router.post(path, cb, options)
638
+ }
639
+
640
+ /**
641
+ *
642
+ * @param {string} path 路由字符串
643
+ * @param {function} cb 回调函数
644
+ * @param {object|string} options 选项
645
+ */
646
+ put(path, cb, options='') {
647
+ return this.router.put(path, cb, options)
648
+ }
649
+
650
+ /**
651
+ *
652
+ * @param {string} path 路由字符串
653
+ * @param {function} cb 回调函数
654
+ * @param {object|string} options 选项
655
+ */
656
+ delete(path, cb, options='') {
657
+ return this.router.delete(path, cb, options)
658
+ }
659
+
660
+ /**
661
+ *
662
+ * @param {string} path 路由字符串
663
+ * @param {function} cb 回调函数
664
+ * @param {object|string} options 选项
665
+ */
666
+ options(path, cb, options='') {
667
+ return this.router.options(path, cb, options)
668
+ }
669
+
670
+ /**
671
+ *
672
+ * @param {string} path 路由字符串
673
+ * @param {function} cb 回调函数
674
+ * @param {object|string} options 选项
675
+ */
676
+ patch(path, cb, options='') {
677
+ return this.router.patch(path, cb, options)
678
+ }
679
+
680
+ /**
681
+ *
682
+ * @param {string} path 路由字符串
683
+ * @param {function} cb 回调函数
684
+ * @param {object|string} options 选项
685
+ */
686
+ head(path, cb, options='') {
687
+ return this.router.head(path, cb, options)
688
+ }
689
+
690
+ /**
691
+ *
692
+ * @param {string} path 路由字符串
693
+ * @param {function} cb 回调函数
694
+ * @param {object|string} options 选项
695
+ */
696
+ trace(path, cb, options='') {
697
+ return this.router.trace(path, cb, options)
698
+ }
699
+
700
+ /**
701
+ *
702
+ * @param {string} grp_name 路由分组名称
703
+ * @param {function} cb 回调函数
704
+ * @param {boolean} prefix 是否作为前缀路经,默认为true,当检测到路由是合法的模式会自动设置为前缀路径
705
+ */
706
+ group(grp_name, cb, prefix=true) {
707
+ ;(typeof cb === 'boolean') && (prefix = cb);
708
+ return this.router.group(grp_name, cb, prefix)
709
+ }
710
+
711
+ /**
712
+ *
713
+ * @param {Array} mids
714
+ * @returns {object} - 属性值group函数
715
+ */
716
+ middleware(mids, options=null) {
717
+ let submid = {
718
+ group: (grp_name, cb, prefix=true) => {
719
+ ;(typeof cb === 'boolean') && (prefix = cb);
720
+
721
+ let subapp = makeSubAppForGroup(this, grp_name);
722
+ //如果数组的一个元素还是数组,那么第一个是中间件,第二个是选项
723
+ subapp.__mids__.push({
724
+ mids: mids,
725
+ options: options
726
+ });
727
+
728
+ subapp.__group__ = grp_name.trim();
729
+
730
+ return this.router.__group__(grp_name, cb, subapp, prefix, this.router.__subapp__);
731
+ }
732
+ }
733
+
734
+ return submid;
735
+ }
736
+
737
+ /**
738
+ * @param {array} marr method数组,示例['GET','HEAD']。
739
+ * @param {string} path 路由字符串。
740
+ * @param {function} callback 请求处理回调函数,必须是async声明。
741
+ * @param {string} name 请求命名,默认为空字符串,可以不写。
742
+ */
743
+ map(marr, path, callback, name = '') {
744
+ return this.router.map(marr, name, callback, name);
745
+ }
746
+
747
+ /**
748
+ * @param {string} path 路由字符串。
749
+ * @param {function} callback 请求处理回调函数,必须是async声明。
750
+ * @param {string} name 请求命名,默认为空字符串,可以不写。
751
+ */
752
+ any(path, callback, name = '') {
753
+ return this.router.any(name, callback, name);
754
+ }
755
+
756
+ initMsgEvent() {
757
+ this.setMsgEvent('_eaddr', (w, msg, handle = undefined) => {
758
+ let errmsg = '\x1b[1;35m端口已被使用,请先停止正在运行的进程。\n在Linux/Unix上,可通过\n\t'
759
+ +'ps -e -o user,pid,ppid,comm,args | grep node | grep -v grep\n'
760
+ +' 或\n\tss -utlp\n查看相关进程。在Windows上通过任务管理器查找并结束相关进程。\x1b[0m';
761
+
762
+ console.error(errmsg);
763
+ sigExit();
764
+ }, 'once');
765
+
766
+ this.setMsgEvent('_route-table', (w, msg, handle = undefined) => {
767
+ console.log(msg.route);
768
+ console.log('PID:', process.pid, msg.listen, msg.protocol);
769
+ console.log(_topbit_server_running);
770
+ }, 'once');
771
+
772
+ this.setMsgEvent('_server-error', (w, msg, handle=undefined) => {
773
+ let hintText = '出现这种情况说明遇到了错误情况不得不终止服务,请根据错误提示信息排查解决。';
774
+ console.error(`\x1b[1;35m${msg.message}\n${hintText}\x1b[0m`);
775
+ sigExit();
776
+ }, 'once');
777
+
778
+ }
779
+
780
+ /**
781
+ * @param {string} - evt
782
+ * @param {function} - callback
783
+ * */
784
+ on(evt, callback) {
785
+ if (evt === 'requestError') {
786
+ if (typeof callback === 'function') {
787
+ this.httpServ.requestError = callback;
788
+ }
789
+ return;
790
+ }
791
+
792
+ if (!this.eventTable[evt]) {
793
+ this.eventTable[evt] = [ callback ];
794
+ } else {
795
+ this.eventTable[evt].push(callback);
796
+ }
797
+ }
798
+
799
+ /**
800
+ * @param {function} midcall
801
+ * @param {object} options 支持选项:group、name。
802
+ */
803
+ add(midcall, options={}) {
804
+ this.midware.add(midcall, options);
805
+ return this;
806
+ }
807
+
808
+ /**
809
+ * @param {function} midcall
810
+ * @param {object} options 支持选项:group、name、pre。
811
+ */
812
+ use(midcall, options={}) {
813
+ if (typeof options === 'object' && options.pre !== undefined) {
814
+ if (options.pre) {
815
+ return this.pre(midcall, options);
816
+ }
817
+ }
818
+
819
+ this.midware.addCache(midcall, options);
820
+ return this;
821
+ }
822
+
823
+ /**
824
+ * @param {function} midcall
825
+ * @param {object} options 支持选项:group、name。
826
+ */
827
+ pre(midcall, options={}) {
828
+ this.__pre_mids__.push({
829
+ callback: midcall,
830
+ options: options
831
+ });
832
+
833
+ return this;
834
+ }
835
+
836
+ /**
837
+ *
838
+ * @param {string|symbol} key
839
+ * @param {string|object|array|boolean|number|function} serv
840
+ */
841
+ addService(key, serv) {
842
+ if (typeof key === 'object') {
843
+ for (let k in key) {
844
+ this.service[k] = key[k];
845
+ }
846
+ return this;
847
+ }
848
+
849
+ this.service[key] = serv;
850
+ return this;
851
+ }
852
+
853
+ getService(key) {
854
+ return this.service[key] || null;
855
+ }
856
+
857
+ clearService() {
858
+ for (let k in this.service) delete this.service[k];
859
+ }
860
+
861
+ /**
862
+ * 根据配置情况确定运行HTTP/1.1还是HTTP/2
863
+ * @param {number} port 端口号
864
+ * @param {string} host IP地址,可以是IPv4或IPv6
865
+ * 0.0.0.0 对应使用IPv6则是::
866
+ */
867
+ run(port=2368, host='0.0.0.0') {
868
+ if (typeof port === 'object') {
869
+ if (port.host && typeof port.host === 'string') host = port.host;
870
+ if (port.port && typeof port.port === 'number' && port.port > 0 && port.port <= 65535) port = port.port;
871
+ }
872
+
873
+ if (this.config.server.SNICallback && typeof this.config.server.SNICallback === 'function' && !this.config.https) {
874
+ this.config.https = true;
875
+ }
876
+
877
+ this.router.argsRouteSort();
878
+
879
+ this.rundata.host = (typeof port == 'number' ? host : '');
880
+ this.rundata.port = port;
881
+
882
+ //如果没有添加路由则添加默认路由
883
+ if (this.router.count === 0) {
884
+ this.router.get('/*', async c => {
885
+ c.setHeader('content-type', 'text/html; charset=utf-8').send(_topbit_home_page)
886
+ })
887
+ }
888
+
889
+ //如果发现更改了service指向,则让this.httpServ.service重新指向this.service。
890
+ if (this.service !== this.httpServ.service) {
891
+ this.httpServ.service = this.service;
892
+ }
893
+
894
+ this.midware.addFromCache();
895
+
896
+ this.config.parseBody && this.add(this.bodyparser);
897
+
898
+ this.add(this.httpServ);
899
+
900
+ let m = null;
901
+ while((m = this.__pre_mids__.pop()) !== undefined) {
902
+ this.add(m.callback, m.options);
903
+ }
904
+
905
+ //必须放在最后,用于返回最终数据。
906
+ this.midware.addFinal();
907
+
908
+ if (this.config.useLimit) {
909
+ let connlimit = new this.connfilter(this.limit, this.rundata);
910
+ this.on('connection', connlimit.callback);
911
+ } else {
912
+ this.on('connection', (sock) => {
913
+ this.rundata.conn++;
914
+ sock.on('close', () => {
915
+ this.rundata.conn--;
916
+ });
917
+
918
+ });
919
+ }
920
+
921
+ /**
922
+ * 输出路由表,如果是启用了cluster,则通过发送消息的方式让master进程输出。
923
+ * */
924
+ if (this.config.debug) {
925
+ if (typeof port === 'string' && port.indexOf('.sock') > 0) {
926
+ host = '';
927
+ }
928
+
929
+ let is_https = this.config.https;
930
+
931
+ let protocol = this.config.http2
932
+ ? ('http2' + (is_https ? '[https]' : ''))
933
+ : (is_https ? 'https' : 'http');
934
+
935
+ if (cluster.isMaster) {
936
+ this.router.printTable();
937
+ console.log(`PID: ${process.pid}, Listen ${host}:${port}, Protocol: ${protocol}`);
938
+ console.log(_topbit_server_running);
939
+ } else if (process.send && typeof process.send === 'function') {
940
+ process.send({type:'_route-table',
941
+ route: this.router.getTable(),
942
+ listen: `Listen: ${host}${host.length > 0 ? ':' : ''}${port}, `,
943
+ protocol: `Protocol: ${protocol}`
944
+ });
945
+ }
946
+ }
947
+
948
+ this.server = this.httpServ.run(port, host);
949
+ return this.server;
950
+ }
951
+ //run end
952
+
953
+ /**
954
+ * @param {string} evt
955
+ * @param {function} callback
956
+ * @param {string} mode always | once,默认always。
957
+ * */
958
+ setMsgEvent(evt, callback, mode='always') {
959
+ if (cluster.isWorker) {
960
+ return;
961
+ }
962
+
963
+ if (typeof callback !== 'function') {
964
+ console.error('setMsgEvent: callback not a function');
965
+ return false;
966
+ }
967
+
968
+ this.msgEvent[evt] = {
969
+ count: 0,
970
+ mode: mode,
971
+ callback: callback
972
+ };
973
+ return true;
974
+ }
975
+
976
+ getMsgEvent(evt) {
977
+ if (this.msgEvent[evt]) return this.msgEvent[evt];
978
+ return null;
979
+ }
980
+
981
+ removeMsgEvent(evt) {
982
+ if (this.msgEvent[evt]) {
983
+ let e = this.msgEvent[evt];
984
+ delete this.msgEvent[evt];
985
+ return e;
986
+ }
987
+
988
+ return false;
989
+ }
990
+
991
+ /**
992
+ * worker用于快速发送消息给master进程。
993
+ * @param {string} evtname 要发送消息的事件名称。
994
+ * @param {string|object} msg 要发送的消息。
995
+ * */
996
+ send(evtname, msg, handle=undefined) {
997
+ if (!cluster.isWorker || !process.send || typeof process.send !== 'function') return;
998
+
999
+ if (!msg) return;
1000
+
1001
+ let typn = typeof msg;
1002
+
1003
+ if (typn === 'string' || typn === 'number') msg = {msg: msg};
1004
+
1005
+ msg.type = evtname;
1006
+
1007
+ process.send(msg, handle);
1008
+ }
1009
+
1010
+ workerMsg(callback) {
1011
+ if (cluster.isWorker && callback && typeof callback === 'function') {
1012
+ process.on('message', callback);
1013
+ }
1014
+ }
1015
+
1016
+ daemonMessage() {
1017
+ if (cluster.isWorker) {
1018
+ return;
1019
+ }
1020
+
1021
+ let self = this;
1022
+ for (let k in this.msgEvent) {
1023
+ this.msgEvent[k].count = 0;
1024
+ }
1025
+
1026
+ cluster.on('message', (worker, msg, handle) => {
1027
+ try {
1028
+ if (typeof msg === 'object' && msg.type !== undefined) {
1029
+ if (self.msgEvent[msg.type] === undefined) {
1030
+ return;
1031
+ }
1032
+ let devt = self.msgEvent[msg.type];
1033
+ if (devt.mode === 'once' && devt.count > 0) {
1034
+ return;
1035
+ }
1036
+
1037
+ if (devt.count < 30000000000) {
1038
+ devt.count += 1;
1039
+ }
1040
+
1041
+ devt.callback(worker, msg, handle);
1042
+ }
1043
+
1044
+ } catch (err) {
1045
+ self.config.debug && console.error(err);
1046
+ }
1047
+ });
1048
+ }
1049
+
1050
+ autoWorker(max) {
1051
+ if (typeof max === 'number' && max > 0) {
1052
+ this.workerCount.max = max;
1053
+ } else {
1054
+ throw new Error('autoWorker参数必须是一个大于0的数字,表示最大允许创建多少个子进程处理请求。');
1055
+ }
1056
+ }
1057
+
1058
+ /**
1059
+ * 调度类型,默认不做任何设置,采用Node.js cluster模块的默认设置。
1060
+ * @param {string} sch
1061
+ */
1062
+ sched(sch = '') {
1063
+ if (sch === 'rr') {
1064
+ cluster.schedulingPolicy = cluster.SCHED_RR;
1065
+ } else if (sch === 'none') {
1066
+ cluster.schedulingPolicy = cluster.SCHED_NONE;
1067
+ } else {
1068
+ return cluster.schedulingPolicy;
1069
+ }
1070
+ }
1071
+
1072
+ _checkDaemonArgs() {
1073
+ if (process.argv.indexOf('--daemon') > 0) {
1074
+ } else if (this.config.daemon) {
1075
+ let args = process.argv.slice(1);
1076
+ args.push('--daemon');
1077
+
1078
+ const serv = spawn(process.argv[0], args, {
1079
+ detached: true,
1080
+ stdio: ['ignore', 1, 2]
1081
+ });
1082
+
1083
+ serv.unref();
1084
+ process.exit(0);
1085
+ }
1086
+ }
1087
+
1088
+ workerEventHandle() {
1089
+ cluster.on('listening', (worker, addr) => {
1090
+ this.workerCount.canAutoFork = true;
1091
+ this.workerCount.cur += 1;
1092
+
1093
+ let onerr = err => {
1094
+ this.config.errorHandle(err, '--ERR-WORKER--');
1095
+ };
1096
+
1097
+ worker.on('error', onerr);
1098
+ worker.process.on('error', onerr);
1099
+
1100
+ this.workers[ worker.id ] = {
1101
+ startTime : Date.now(),
1102
+ address : addr,
1103
+ id : worker.id,
1104
+ pid : worker.process.pid,
1105
+ conn: 0,
1106
+ mem: {
1107
+ rss : 0,
1108
+ heapTotal: 0,
1109
+ heapUsed: 0,
1110
+ external: 0,
1111
+ total: 0
1112
+ },
1113
+ cpu: {user:0, system:0},
1114
+ cputm : 1000
1115
+ };
1116
+
1117
+ });
1118
+
1119
+ let exitTip = () => {
1120
+ setTimeout(() => {
1121
+ let errmsg = `worker进程在限制的最短时间内(${this.workerErrorTime}ms)退出,请检测代码是否存在错误。`;
1122
+ console.error(errmsg);
1123
+ process.kill(0, 'SIGTERM');
1124
+ }, 15);
1125
+ };
1126
+
1127
+ cluster.on('disconnect', worker => {
1128
+ worker.kill('SIGTERM');
1129
+ });
1130
+
1131
+ cluster.on('exit', (worker, code, signal) => {
1132
+ this.workerCount.canAutoFork = true;
1133
+ this.workerCount.cur -= 1;
1134
+ let w = this.workers[worker.id];
1135
+ if (w) {
1136
+ let tm = Date.now();
1137
+ if (tm - w.startTime <= this.workerErrorTime && this.errorBreakCount <= 0 && code) {
1138
+ exitTip();
1139
+ } else {
1140
+ delete this.workers[w.id];
1141
+ }
1142
+ this.errorBreakCount = 1;
1143
+ } else {
1144
+ exitTip();
1145
+ }
1146
+ });
1147
+
1148
+ }
1149
+
1150
+ keepWorkers() {
1151
+ let keepworkers = () => {
1152
+ let num_dis = this.workerCount.total - Object.keys(cluster.workers).length;
1153
+
1154
+ if (num_dis <= 0) return;
1155
+
1156
+ for (let i = 0; i < num_dis; i++) {
1157
+ cluster.fork();
1158
+ }
1159
+ };
1160
+
1161
+ process.on('SIGCHLD', (sig) => {
1162
+ if (this.workerCount.cur >= this.workerCount.total) {
1163
+ return;
1164
+ }
1165
+
1166
+ cluster.fork();
1167
+
1168
+ //测试kill多个子进程会有信号丢失的情况,设置定时器做最后的检测。
1169
+ if (this.keepWorkersTimer === null) {
1170
+ this.keepWorkersTimer = setTimeout (() => {
1171
+ this.keepWorkersTimer = null;
1172
+ keepworkers();
1173
+ }, 2000);
1174
+ }
1175
+
1176
+ });
1177
+
1178
+ if (process.platform === 'win32') {
1179
+ setInterval(() => {
1180
+ keepworkers();
1181
+ }, 1024);
1182
+ }
1183
+
1184
+ }
1185
+
1186
+ /**
1187
+ * 这个函数是可以用于运维部署,此函数默认会根据CPU核数创建对应的子进程处理请求。
1188
+ * @param {number} port 端口号
1189
+ * @param {string} IP地址,IPv4或IPv6,如果检测为数字,则会把数字赋值给num。
1190
+ * @param {number} num,要创建的子进程数量,0表示自动,这时候根据CPU核心数量创建。
1191
+ */
1192
+ daemon(port=2368, host='0.0.0.0', num=0) {
1193
+ if (typeof host === 'number') {
1194
+ num = host;
1195
+ host = '0.0.0.0';
1196
+ }
1197
+
1198
+ if (typeof port === 'object') {
1199
+ if (port.host && typeof port.host === 'string') host = port.host;
1200
+ if (port.worker && typeof port.worker === 'number') num = port.worker;
1201
+ if (port.port && typeof port.port === 'number' && port.port > 0 && port.port <= 65535) port = port.port;
1202
+ }
1203
+
1204
+ //确保自动创建的worker在终止时不会误认为是系统错误。
1205
+ setTimeout(() => {
1206
+ this.errorBreakCount += 1;
1207
+ }, this.workerErrorTime + 120);
1208
+
1209
+ this._checkDaemonArgs();
1210
+
1211
+ if (cluster.isMaster) {
1212
+ let osCPUS = os.cpus().length;
1213
+ if (num > (osCPUS * 2) ) {
1214
+ num = 0;
1215
+ }
1216
+
1217
+ if (num <= 0) {
1218
+ num = osCPUS;
1219
+ //如果CPU核心数超过2个,则使用核心数-1的子进程处理请求。
1220
+ if (num > 2) {
1221
+ num -= 1;
1222
+ }
1223
+ }
1224
+
1225
+ this.workerCount.total = num;
1226
+ this.rundata.port = port;
1227
+ //根据num设定secure的内存限制。
1228
+ if (num > 1) {
1229
+ for (let k in this.secure) {
1230
+ this.secure[k] = parseInt(this.secure[k] * (0.45 + 1 / num));
1231
+ }
1232
+ }
1233
+
1234
+ this.rundata.host = (typeof port === 'number' ? host : '');
1235
+
1236
+ if (typeof this.config.loadInfoFile !== 'string') {
1237
+ this.config.loadInfoFile = '';
1238
+ }
1239
+
1240
+ if (typeof this.config.pidFile === 'string' && this.config.pidFile.length > 0) {
1241
+ fs.writeFile(this.config.pidFile, `${process.pid}`, (err) => {
1242
+ err && console.error(err);
1243
+ });
1244
+ }
1245
+
1246
+ this.daemonMessage();
1247
+
1248
+ //clear router and service
1249
+ this.clearService();
1250
+ this.router.clear();
1251
+ this.midware.midGroup = {};
1252
+
1253
+ this.workerEventHandle();
1254
+
1255
+ this.keepWorkers();
1256
+
1257
+ for (let i = 0; i < num; i++) {
1258
+ cluster.fork();
1259
+ }
1260
+
1261
+ return this;
1262
+
1263
+ } else if (cluster.isWorker) {
1264
+
1265
+ if (this.config.showLoadInfo) {
1266
+ this.monitor.workerSend();
1267
+ }
1268
+
1269
+ this.server = this.run(port, host);
1270
+ return this.server;
1271
+ }
1272
+ }
1273
+
1274
+ get isMaster() {
1275
+ return cluster.isPrimary || cluster.isMaster;
1276
+ }
1277
+
1278
+ get isPrimary() {
1279
+ return cluster.isPrimary || cluster.isMaster;
1280
+ }
1281
+
1282
+ get isWorker() {
1283
+ return cluster.isWorker;
1284
+ }
1285
+
1286
+ }
1287
+
1288
+ Topbit.Loader = TopbitLoader;
1289
+ Topbit.Token = TopbitToken;
1290
+
1291
+ module.exports = Topbit;