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/src/monitor.js ADDED
@@ -0,0 +1,380 @@
1
+ 'use strict';
2
+
3
+ const cluster = require('node:cluster');
4
+ const os = require('node:os');
5
+ const fs = require('node:fs');
6
+ const process = require('node:process');
7
+
8
+ class Monitor {
9
+
10
+ constructor(options) {
11
+ this.config = options.config
12
+ this.workers = options.workers
13
+ this.rundata = options.rundata
14
+ this.secure = options.secure
15
+
16
+ this.workerCount = options.workerCount
17
+
18
+ this.rundata.cpus = os.cpus().length
19
+
20
+ this.loadCount = 0
21
+ this.loadInfo = {}
22
+
23
+ this.sendInterval = null
24
+
25
+ this.loadCache = null
26
+
27
+ this.cpuLastTime = Date.now() - 1
28
+
29
+ this.cpuNowTime = Date.now()
30
+
31
+ //this.cpuPercentFactor = 10 * this.rundata.cpus;
32
+ this.cpuHighRatio = 0
33
+
34
+ this.cooling = 0
35
+
36
+ //用于自动检测是否需要创建worker的节拍记录。
37
+ this.clock = 0
38
+
39
+ //worker进程发送请求状况的时间片。
40
+ this.timeSlice = this.config.monitorTimeSlice
41
+
42
+ this.maxLife = 500
43
+
44
+ this.maxCooling = 100000
45
+
46
+ this.life = 20
47
+
48
+ this.maxRate = parseInt(this.config.maxLoadRate * 10)
49
+
50
+ this.loadfd = null
51
+
52
+ }
53
+
54
+ autoWorker() {
55
+ if (this.clock < this.workerCount.cur) {
56
+ this.clock += 1
57
+ return;
58
+ }
59
+
60
+ this.clock = 0
61
+
62
+ if (this.workerCount.cur < this.workerCount.max) {
63
+ let cpuratio = 0
64
+
65
+ for (let k in this.workers) {
66
+ cpuratio = (this.workers[k].cpu.user + this.workers[k].cpu.system) / this.workers[k].cputm
67
+
68
+ if (cpuratio >= this.maxRate) {
69
+ this.cpuHighRatio += 1
70
+ } else {
71
+ if (this.cpuHighRatio > 0) {
72
+ this.cpuHighRatio -= 1
73
+ }
74
+ break
75
+ }
76
+ }
77
+ }
78
+
79
+ if (this.cpuHighRatio >= this.workerCount.cur) {
80
+
81
+ this.cpuHighRatio -= 1
82
+
83
+ if (this.workerCount.cur < this.workerCount.max) {
84
+ if (this.workerCount.canAutoFork) {
85
+ this.workerCount.canAutoFork = false
86
+
87
+ cluster.fork()
88
+
89
+ if (this.life < this.maxLife) {
90
+ this.life += 5
91
+ } else {
92
+ this.life = 25
93
+ }
94
+
95
+ this.cooling += this.life
96
+ }
97
+
98
+ } else {
99
+ //此时升温,表示负载高,不要kill多余的进程。
100
+ if (this.cooling < this.maxCooling) {
101
+ this.cooling += 20 + parseInt( Math.random() * 60 )
102
+ }
103
+ }
104
+
105
+ } else {
106
+
107
+ if (this.workerCount.cur > this.workerCount.total) {
108
+ if (this.cooling > 0) {
109
+ this.cooling -= 1
110
+ } else {
111
+ for (let k in this.workers) {
112
+ if (this.workers[k].conn === 0) {
113
+
114
+ if (cluster.workers[k]) {
115
+ cluster.workers[k].send({type: '_disconnect'})
116
+ cluster.workers[k].disconnect()
117
+ }
118
+
119
+ break
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ }
126
+
127
+ }
128
+
129
+ msgEvent() {
130
+ if (this.config.loadInfoFile.length > 0 && this.config.loadInfoFile !== '--mem') {
131
+ fs.open(this.config.loadInfoFile, 'w+', 0o644, (err, fd) => {
132
+ if (!err) {
133
+ this.loadfd = fd
134
+ } else {
135
+ this.config.debug && this.config.errorHandle(err, '--ERR-OPEN-FILE--')
136
+ }
137
+ })
138
+ }
139
+
140
+ let self = this
141
+
142
+ return (w, msg, handle = undefined) => {
143
+ if (self.checkMem(w, msg)) {
144
+ if (self.workerCount.max > self.workerCount.total) {
145
+ self.autoWorker();
146
+ }
147
+
148
+ self.showLoadInfo(msg, w.id);
149
+ }
150
+ }
151
+ }
152
+
153
+ workerSend() {
154
+ if (this.sendInterval) return;
155
+
156
+ let will_disconnect = false
157
+
158
+ process.on('message', (msg) => {
159
+ if (msg.type === '_disconnect') {
160
+ will_disconnect = true
161
+ }
162
+ })
163
+
164
+ let MAX_MEM_COUNT = 28
165
+
166
+ let mem_count = MAX_MEM_COUNT
167
+
168
+ this.sendInterval = setInterval(() => {
169
+ if (will_disconnect) return;
170
+
171
+ this.cpuLastTime = this.cpuNowTime
172
+ this.cpuNowTime = Date.now()
173
+
174
+ this.rundata.cpuTime = process.cpuUsage(this.rundata.cpuLast)
175
+ if (mem_count < MAX_MEM_COUNT) {
176
+ this.rundata.mem.rss = process.memoryUsage.rss()
177
+ mem_count += 1
178
+ } else {
179
+ this.rundata.mem = process.memoryUsage()
180
+ mem_count = 0
181
+ }
182
+
183
+ this.rundata.mem.total = this.rundata.mem.rss + this.rundata.mem.external
184
+
185
+ process.send({
186
+ type : '_load',
187
+ pid : process.pid,
188
+ cpu : this.rundata.cpuTime,
189
+ cputm : this.cpuNowTime - this.cpuLastTime,
190
+ mem : this.rundata.mem,
191
+ conn : this.rundata.conn
192
+ }, err => {
193
+ err && this.config.errorHandle(err, '--ERR-WORKER-SEND--')
194
+ //if (err.code === 'ERR_IPC_CHANNEL_CLOSED') {}
195
+ })
196
+
197
+ this.rundata.cpuLast = process.cpuUsage()
198
+
199
+ }, this.timeSlice)
200
+
201
+ }
202
+
203
+ showLoadInfo(w, id) {
204
+ if (this.config.loadInfoType == '--null') {
205
+ return
206
+ }
207
+
208
+ let wk = this.workers[id]
209
+
210
+ if (!wk) {
211
+ return
212
+ }
213
+
214
+ wk.cpu.user = w.cpu.user;
215
+ wk.cpu.system = w.cpu.system;
216
+ wk.mem.rss = w.mem.rss;
217
+ wk.mem.heapTotal = w.mem.heapTotal;
218
+ wk.mem.heapUsed = w.mem.heapUsed;
219
+ wk.mem.external = w.mem.external;
220
+ wk.mem.total = w.mem.total;
221
+ wk.mem.arrayBuffers = w.mem.arrayBuffers;
222
+
223
+ wk.conn = w.conn;
224
+ wk.cputm = w.cputm;
225
+
226
+ this.loadCount += 1;
227
+ if (this.loadCount < this.workerCount.cur) {
228
+ return ;
229
+ }
230
+
231
+ let loadText = this.fmtLoadInfo( this.config.loadInfoType );
232
+
233
+ if (this.config.loadInfoFile === '--mem') {
234
+ this.loadCache = loadText;
235
+ }
236
+ else if (this.loadfd !== null) {
237
+
238
+ fs.write(this.loadfd, loadText, 0, (err, bytes, data) => {
239
+ err && this.config.debug && this.config.errorHandle(err, '--ERR-WRITE-FILE--');
240
+ if (!err) {
241
+ fs.ftruncate(this.loadfd, bytes, e => {});
242
+ }
243
+ });
244
+
245
+ } else if (process.ppid > 1 && !this.config.daemon && !this.config.loadInfoFile) {
246
+ console.clear();
247
+ //只有没有开启守护进程才会输出到屏幕
248
+ console.log(loadText);
249
+ }
250
+
251
+ this.loadCount = 0;
252
+
253
+ }
254
+
255
+ checkMem(w, msg) {
256
+ if (this.secure.maxrss > 0 && this.secure.maxrss <= msg.mem.rss) {
257
+ //process.kill(msg.pid, 'SIGTERM');
258
+ w.send({type: '_disconnect'});
259
+ w.disconnect();
260
+ return false;
261
+ }
262
+
263
+ if (this.secure.diemem > 0 && this.secure.diemem <= msg.mem.total) {
264
+ w.send({type: '_disconnect'});
265
+ w.disconnect();
266
+ return false;
267
+ }
268
+
269
+ if (this.secure.maxmem > 0
270
+ && this.secure.maxmem <= msg.mem.rss
271
+ && msg.conn == 0)
272
+ {
273
+ w.send({type: '_disconnect'});
274
+ w.disconnect();
275
+ return false;
276
+ }
277
+ return true;
278
+ }
279
+
280
+ fmtLoadInfo(type = 'text') {
281
+ let oavg = os.loadavg();
282
+
283
+ let p = null;
284
+
285
+ if (type === 'text') {
286
+ let oscpu = `CPU Load avg 1m: ${oavg[0].toFixed(2)} `
287
+ + `5m: ${oavg[1].toFixed(2)} 15m: ${oavg[2].toFixed(2)}\n`;
288
+
289
+ let cols = ['PID CPU CONN MEM HEAP USED EXT TOTAL·M'];
290
+ let tmp = '';
291
+ let t = '';
292
+ let p = null;
293
+
294
+ for (let id in this.workers) {
295
+
296
+ p = this.workers[id];
297
+
298
+ tmp = [(`${p.pid}`).padEnd(9, ' ')];
299
+
300
+ t = p.cpu.user + p.cpu.system;
301
+
302
+ tmp.push((( t/(p.cputm * 10) ).toFixed(2) + '%').padEnd(8, ' '),
303
+
304
+ (`${p.conn}`).padEnd(8, ' '),
305
+
306
+ (p.mem.rss / 1048576).toFixed(1).padEnd(8, ' '),
307
+
308
+ (p.mem.heapTotal / 1048576).toFixed(1).padEnd(8, ' '),
309
+
310
+ (p.mem.heapUsed / 1048576).toFixed(1).padEnd(8, ' '),
311
+
312
+ (p.mem.external / 1048576).toFixed(1).padEnd(9, ' '),
313
+
314
+ (p.mem.total / 1048576).toFixed(1)
315
+ );
316
+
317
+ cols.push(tmp.join(''));
318
+ }
319
+
320
+ cols.push(`PID: ${process.pid} | Listen ${this.rundata.host}:${this.rundata.port}\n`);
321
+
322
+ return `${oscpu}${cols.join('\n')}`
323
+ +`HTTPS: ${this.config.https ? 'true' : 'false'}; HTTP/2: ${this.config.http2 ? 'true' : 'false'}\n`;
324
+ }
325
+
326
+ if (type === 'json') {
327
+ let loadjson = {
328
+ masterPid : process.pid,
329
+ listen : `${this.rundata.host}:${this.rundata.port}`,
330
+ CPULoadavg : {
331
+ '1m' : oavg[0].toFixed(2),
332
+ '5m' : oavg[1].toFixed(2),
333
+ '15m' : oavg[2].toFixed(2)
334
+ },
335
+ https: this.config.https,
336
+ http2: this.config.http2,
337
+ workers : []
338
+ };
339
+ for (let id in this.workers) {
340
+ p = this.workers[id];
341
+
342
+ loadjson.workers.push({
343
+ pid : p.pid,
344
+ cpu : `${((p.cpu.user + p.cpu.system)/ (p.cputm * 10) ).toFixed(2)}%`,
345
+ cputm : p.cputm,
346
+ mem : {
347
+ rss : (p.mem.rss / 1048576).toFixed(1),
348
+ heap : (p.mem.heapTotal / 1048576).toFixed(1),
349
+ heapused : (p.mem.heapUsed / 1048576).toFixed(1),
350
+ external : (p.mem.external / 1048576).toFixed(1),
351
+ },
352
+ conn : p.conn
353
+ });
354
+ }
355
+ return JSON.stringify(loadjson);
356
+ }
357
+
358
+ if (type === 'orgjson') {
359
+ let loadjson = {
360
+ masterPid : process.pid,
361
+ listen : `${this.rundata.host}:${this.rundata.port}`,
362
+ CPULoadavg : {
363
+ '1m' : oavg[0].toFixed(2),
364
+ '5m' : oavg[1].toFixed(2),
365
+ '15m' : oavg[2].toFixed(2)
366
+ },
367
+ https: this.config.https,
368
+ http2: this.config.http2,
369
+ workers : this.workers
370
+ };
371
+
372
+ return JSON.stringify(loadjson);
373
+ }
374
+
375
+ return '';
376
+ }
377
+
378
+ }
379
+
380
+ module.exports = Monitor
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ module.exports = async (ctx, upf, target) => {
6
+ let fd = await new Promise((rv, rj) => {
7
+ fs.open(target, 'w+', 0o644, (err, fd) => {
8
+ if (err) {
9
+ rj(err);
10
+ } else {
11
+ rv(fd);
12
+ }
13
+ });
14
+ });
15
+
16
+ return new Promise((rv, rj) => {
17
+ fs.write(fd, ctx.rawBody, upf.start, upf.length,
18
+ (err, bytesWritten, buffer) => {
19
+ if (err) {
20
+ rj(err);
21
+ } else {
22
+ rv(bytesWritten);
23
+ }
24
+ });
25
+ })
26
+ .finally(() => {
27
+ fs.close(fd, (err) => {});
28
+ });
29
+
30
+ };
@@ -0,0 +1,54 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ *
5
+ * @param {string} name 选项名称
6
+ * @param {any} val 输入值
7
+ * @param {object} config 配置
8
+ * @param {object} options 选项,给出限定范围,包括:
9
+ * min, max, list, type
10
+ *
11
+ */
12
+
13
+ function delayOutError(errinfo) {
14
+ setTimeout(() => {
15
+ console.error(`\x1b[33;7m${errinfo}\x1b[0m`);
16
+ }, 1200);
17
+ }
18
+
19
+ module.exports = (name, val, config, options) => {
20
+
21
+ if (options.type) {
22
+ if (options.type === 'array' && !Array.isArray(val)) {
23
+ delayOutError(`config ${name} 不符合类型约束,必须是数组。`);
24
+ return false
25
+ }
26
+
27
+ let vt = typeof val
28
+
29
+ if (Array.isArray(val)) vt = 'array'
30
+ else if (val instanceof Set) vt = 'set'
31
+ else if (val instanceof Map) vt = 'map'
32
+
33
+ if (Array.isArray(options.type)) {
34
+ if (options.type.indexOf(vt) < 0) {
35
+ delayOutError(`config ${name} 不符合类型约束,可以是${options.type.join()}之一,请检查。`);
36
+ return false
37
+ }
38
+ } else if (vt !== options.type) {
39
+ delayOutError(`config ${name} 不符合类型约束,要求必须是${options.type}类型。`);
40
+ return false
41
+ }
42
+ }
43
+
44
+ if (options.list && Array.isArray(options.list)) {
45
+ if (options.list.indexOf(val) < 0) return false
46
+ } else if (options.min !== undefined || options.max !== undefined) {
47
+ if (options.min !== undefined && options.min > val) return false
48
+ if (options.max !== undefined && options.max < val) return false
49
+ }
50
+
51
+ config[name] = val
52
+
53
+ return true
54
+ }
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ let saltArr = [
4
+ 'a','b','c','d','e','f','g',
5
+ 'h','i','j','k','l','m','n',
6
+ 'o','p','q','r','s','t','u',
7
+ 'v','w','x','y','z','1','2',
8
+ '3','4','5','6','7','8','9'
9
+ ];
10
+
11
+ module.exports = (length = 8, sarr = null) => {
12
+ let saltstr = '';
13
+ let ind = 0;
14
+
15
+ let arr = sarr || saltArr;
16
+
17
+ for(let i = 0; i < length; i++) {
18
+ ind = parseInt(Math.random() * arr.length);
19
+ saltstr += arr[ind];
20
+ }
21
+
22
+ return saltstr;
23
+ }