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
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs')
4
+ const tls = require('node:tls')
5
+
6
+ class SNI {
7
+ constructor(certs = {}) {
8
+ this.certs = {}
9
+
10
+ if (typeof certs !== 'object') {
11
+ throw new Error('必须传递key-value形式的配置,key是域名,value是证书路径')
12
+ }
13
+
14
+ let t = '';
15
+ for (let h in certs) {
16
+ t = certs[h]
17
+
18
+ if (t.key === undefined || t.cert === undefined) {
19
+ console.error(`${h} 没有设置key和cert`)
20
+ continue
21
+ }
22
+
23
+ try {
24
+ this.certs[h] = {
25
+ key : fs.readFileSync(t.key),
26
+ cert : fs.readFileSync(t.cert)
27
+ }
28
+ } catch (err) {
29
+ console.error(h, err.message)
30
+ continue
31
+ }
32
+
33
+ }
34
+ }
35
+
36
+ callback() {
37
+ return (servername, cb) => {
38
+ return cb(null, tls.createSecureContext(this.certs[servername]))
39
+ }
40
+ }
41
+
42
+ init(app) {
43
+ app.config.server.SNICallback = this.callback()
44
+ }
45
+
46
+ }
47
+
48
+ module.exports = SNI
@@ -0,0 +1,293 @@
1
+ 'use strict'
2
+
3
+ function fmtMessage(msg, withEnd=true) {
4
+ let text = ''
5
+
6
+ if (Array.isArray(msg)) {
7
+ let textarr = []
8
+
9
+ for (let m of msg) {
10
+ text = fmtMessage(m, false)
11
+ text && textarr.push(text)
12
+ }
13
+ if (textarr.length === 0) return ''
14
+
15
+ return textarr.join('\n\n') + '\n\n'
16
+ }
17
+
18
+ if (msg === null || msg === undefined || msg === '') {
19
+ return ''
20
+ }
21
+
22
+ let typ = typeof msg
23
+
24
+ if (typ === 'number') {
25
+ msg = `${msg}`
26
+ typ = 'string'
27
+ }
28
+
29
+ if (typ === 'object') {
30
+ if (!(msg.event || msg.data || msg.retry || msg.id)) return ''
31
+
32
+ if (msg.data === undefined) msg.data = ''
33
+
34
+ let datatype = typeof msg.data
35
+
36
+ switch (datatype) {
37
+ case 'number':
38
+ msg.data = msg.data.toString()
39
+ break
40
+ case 'object':
41
+ msg.data = JSON.stringify(msg.data).replaceAll('\n', '%0A')
42
+ break
43
+ case 'function':
44
+ msg.data = msg.data.toString().replaceAll('\n', '%0A')
45
+ break
46
+ case 'string':
47
+ msg.data = msg.data.replaceAll('\n', '%0A')
48
+ break
49
+
50
+ default:
51
+ msg.data = `${msg.data}`
52
+ }
53
+
54
+ text = `event: ${msg.event || 'message'}\ndata: ${msg.data}\n`
55
+ if (msg.id) {
56
+ text += `id: ${msg.id}\n`
57
+ }
58
+
59
+ if (msg.retry) {
60
+ text += `retry: ${msg.retry}\n`
61
+ }
62
+ } else if (typ === 'string') {
63
+ if (msg[0] !== ':') {
64
+ text = `data: ${msg.replaceAll('\n', '%0A')}\n`
65
+ } else {
66
+ text = msg.replaceAll('\n', '%0A') + '\n'
67
+ }
68
+ } else if (typ === 'function') {
69
+ text = `event: function\ndata: ${msg.toString().replaceAll('\n', '%0A')}\n`
70
+ }
71
+
72
+ if (withEnd) {
73
+ text += `\n\n`
74
+ }
75
+
76
+ return text
77
+ }
78
+
79
+ class SSE {
80
+
81
+ constructor(options = {}) {
82
+ this.timer = null
83
+ this.handle = null
84
+ this.timeSlice = 1000
85
+
86
+ this.retry = 0
87
+ this.timeout = 15000
88
+
89
+ this.fmtMsg = fmtMessage
90
+
91
+ this.handleClose = null
92
+ this.handleError = null
93
+
94
+ this.mode = 'timer'
95
+
96
+ for (let k in options) {
97
+ switch (k) {
98
+ case 'timeSlice':
99
+ case 'timeout':
100
+ case 'retry':
101
+ if (typeof options[k] === 'number' && options[k] >= 0) {
102
+ this[k] = options[k]
103
+ }
104
+ break
105
+
106
+ case 'handle':
107
+ case 'handleClose':
108
+ case 'handleError':
109
+ if (typeof options[k] === 'function') this[k] = optionsp[k]
110
+ break
111
+
112
+ case 'mode':
113
+ if (['timer', 'generator', 'yield'].indexOf(options[k]) >= 0)
114
+ this[k] = options[k]
115
+ break
116
+ }
117
+ }
118
+
119
+ }
120
+
121
+ async interval(ctx) {
122
+ if (!this.handle || typeof this.handle !== 'function') {
123
+ throw new Error('请设置handle为要处理的函数,然后再次运行。')
124
+ }
125
+
126
+ let self = this
127
+
128
+ if (self.timer) {
129
+ clearInterval(this.timer)
130
+ this.timer = null
131
+ }
132
+
133
+ return new Promise((rv, rj) => {
134
+ ctx.res.on('error', err => {
135
+ clearInterval(self.timer)
136
+ self.timer = null
137
+ rj(err)
138
+ })
139
+
140
+ ctx.res.on('close', () => {
141
+ clearInterval(self.timer)
142
+ self.timer = null
143
+ rv('sse closed')
144
+ })
145
+
146
+ self.timer = setInterval(async () => {
147
+ ctx.box.sseCount += 1
148
+ if (self.timeout > 0 && ctx.box.sseCount * self.timeSlice > self.timeout) {
149
+ if (self.retry > 0) {
150
+ ctx.sendmsg({data: 'timeout', retry: self.retry})
151
+ }
152
+ return ctx.res.end()
153
+ }
154
+
155
+ try {
156
+ await self.handle(ctx)
157
+ } catch (err) {
158
+ clearInterval(self.timer)
159
+ self.timer = null
160
+ rj(err)
161
+ }
162
+ }, self.timeSlice || 1000)
163
+ })
164
+
165
+ }
166
+
167
+ async moment(t) {
168
+ return new Promise((rv) => {
169
+ setTimeout(rv, t)
170
+ })
171
+ }
172
+
173
+ gn(ctx) {
174
+ if (!this.handle || typeof this.handle !== 'function') {
175
+ throw new Error('请设置handle为要处理的函数,然后再次运行。')
176
+ }
177
+
178
+ let self = this
179
+
180
+ ctx.box.sseNext = true
181
+
182
+ ctx.res.on('error', err => {
183
+ ctx.box.sseNext = false
184
+ ctx.box.sseError = err
185
+ })
186
+
187
+ ctx.res.on('close', () => {
188
+ ctx.box.sseNext = false
189
+ })
190
+
191
+ return async function * () {
192
+ while (true) {
193
+ let tm = Date.now()
194
+
195
+ if (self.timeout > 0 && (tm - ctx.box.sseTime) > self.timeout) {
196
+ if (self.retry > 0) {
197
+ ctx.sendmsg({data: 'timeout', retry: self.retry})
198
+ }
199
+ return
200
+ }
201
+ ctx.box.sseCount += 1
202
+ try {
203
+ await self.handle(ctx)
204
+ } catch (err) {
205
+ ctx.box.sseNext = false
206
+ ctx.box.sseError = err
207
+ }
208
+
209
+ if (ctx.box.sseNext) {
210
+ yield tm
211
+ } else {
212
+ break
213
+ }
214
+ }
215
+ }
216
+
217
+ }
218
+
219
+ async rungn(ctx) {
220
+ let yn = this.gn(ctx)
221
+ let r
222
+ let y = yn()
223
+
224
+ while (true) {
225
+ r = await y.next()
226
+
227
+ if (r.done) break
228
+
229
+ if (this.timeSlice > 0) await this.moment(this.timeSlice)
230
+ }
231
+
232
+ if (ctx.box.sseError) {
233
+ if (this.handleError && typeof this.handleError === 'function')
234
+ this.handleError(ctx.box.sseError, ctx)
235
+ else throw ctx.box.sseError
236
+ } else if (this.handleClose && typeof this.handleClose === 'function') {
237
+ this.handleClose(ctx)
238
+ }
239
+ }
240
+
241
+ autoRun(ctx) {
242
+ if (this.mode === 'timer') {
243
+ return this.interval(ctx)
244
+ .then(data => {
245
+ if (typeof this.handleClose === 'function') this.handleClose(ctx)
246
+ })
247
+ .catch(err => {
248
+ if (typeof this.handleError === 'function') {
249
+ this.handleError(err, ctx)
250
+ } else {
251
+ throw err
252
+ }
253
+ })
254
+ } else {
255
+ ctx.box.sseTime = Date.now()
256
+ return this.rungn(ctx)
257
+ }
258
+ }
259
+
260
+ mid() {
261
+ let self = this
262
+
263
+ return async (ctx, next) => {
264
+ ctx.setHeader('content-type', 'text/event-stream;charset=utf-8').sendHeader()
265
+ ctx.sse = self
266
+ //用于统计是否超时断开并发送retry
267
+ ctx.box.sseCount = 0
268
+ if (!ctx.sendmsg || typeof ctx.sendmsg !== 'function') {
269
+ ctx.sendmsg = (msg, cb=undefined) => {
270
+ let emsg = fmtMessage(msg)
271
+ if (emsg) return ctx.res.write(emsg, cb)
272
+ }
273
+ }
274
+
275
+ ctx.res.setTimeout(self.timeout, () => {
276
+ if (ctx.res.writable) ctx.res.end()
277
+ })
278
+
279
+ //http2协议需要设置session超时,否则如果默认的服务超时设置比self.timeout短,会导致无法收到消息。
280
+ if (ctx.major == 2 && ctx.res.session && ctx.res.session.listenerCount) {
281
+ //http2的session会保持连接,如果stream超时关闭后,session可能会维持连接,此时有可能会复用session。
282
+ if (ctx.res.session.listenerCount('timeout') < 2) {
283
+ ctx.res.session.setTimeout(self.timeout, () => {})
284
+ }
285
+ }
286
+
287
+ await self.autoRun(ctx)
288
+ }
289
+ }
290
+
291
+ }
292
+
293
+ module.exports = SSE
@@ -0,0 +1,111 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+
5
+ class Timing {
6
+
7
+ constructor(options = {}) {
8
+
9
+ /**
10
+ * route保存所有路由的最近耗时记录。
11
+ * maxLimit是保存记录上限。
12
+ */
13
+
14
+ this.route = {
15
+ GET : new Map(),
16
+ POST : new Map(),
17
+ PUT : new Map(),
18
+ DELETE : new Map(),
19
+ OPTIONS : new Map(),
20
+ PATCH : new Map()
21
+ }
22
+
23
+ this.maxLimit = 100
24
+
25
+ this.test = false
26
+
27
+ this.logfile = ''
28
+
29
+ if (typeof options !== 'object') {
30
+ options = {}
31
+ }
32
+
33
+ for (let k in options) {
34
+ switch (k) {
35
+ case 'test':
36
+ this.test = options[k]
37
+ break
38
+
39
+ case 'logfile':
40
+ this.logfile = options[k]
41
+ break
42
+ }
43
+ }
44
+
45
+ }
46
+
47
+ mid() {
48
+ let self = this
49
+
50
+ return async (c, next) => {
51
+
52
+ let start_time = Date.now()
53
+
54
+ await next()
55
+
56
+ let end_time = Date.now()
57
+
58
+ let time_consume = end_time - start_time
59
+
60
+ if (self.route[c.method] !== undefined) {
61
+ if (!self.route[c.method].has(c.routepath)) {
62
+ self.route[c.method].set(c.routepath, {
63
+ total : 1,
64
+ consume: time_consume
65
+ })
66
+ } else {
67
+ let t = self.route[c.method].get(c.routepath)
68
+ if (t.total > self.maxLimit) {
69
+ t.total = 0
70
+ t.consume = 0
71
+ }
72
+
73
+ t.total += 1
74
+ t.consume += time_consume
75
+
76
+ }
77
+
78
+ }
79
+
80
+ let last = self.route[c.method].get(c.routepath)
81
+
82
+ if (self.test) {
83
+
84
+ console.log(c.method, c.path, time_consume, 'ms')
85
+
86
+ console.log(c.method, c.path,'\n',
87
+ ` Count: ${last.total} Total: ${last.consume} ms `,
88
+ `Average: ${(last.consume/last.total).toFixed(2)} ms`
89
+ )
90
+ }
91
+
92
+ if (self.logfile) {
93
+
94
+ let log_text = `${c.method} - ${c.path} - count: ${last.total}, `
95
+ + `total: ${last.consume}ms, `
96
+ + `average: ${(last.consume/last.total).toFixed(2)}ms\n`
97
+
98
+ fs.writeFile(self.logfile, log_text, {flag: 'a+'}, err => {
99
+ if (self.test && err) {
100
+ console.error(err)
101
+ }
102
+ })
103
+ }
104
+
105
+ }
106
+
107
+ }
108
+
109
+ }
110
+
111
+ module.exports = Timing
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs')
4
+ const crypto = require('node:crypto')
5
+
6
+ function extName(fname) {
7
+ let ind = fname.length - 2
8
+
9
+ while (ind > 0 && fname[ind] !== '.') {
10
+ ind -= 1
11
+ }
12
+
13
+ if (ind <= 0) return ''
14
+
15
+ return fname.substring(ind)
16
+ }
17
+
18
+ let fmtbits = (n) => {
19
+ return n < 10 ? `0${n}` : n
20
+ }
21
+
22
+ function makeName(filename = '') {
23
+ let tm = new Date()
24
+
25
+ let orgname = `${tm.getFullYear()}-${fmtbits(tm.getMonth()+1)}-${fmtbits(tm.getDate())}_`
26
+ + `${fmtbits(tm.getHours())}-${fmtbits(tm.getMinutes())}-${fmtbits(tm.getSeconds())}`
27
+ + `_${tm.getMilliseconds()}${parseInt(Math.random() * 1000) + 1}${parseInt(Math.random() * 100000) + 10000}`
28
+
29
+ if (filename) return (orgname + extName(filename))
30
+
31
+ return orgname
32
+ }
33
+
34
+ async function moveFile(target, filename = null) {
35
+ if (!this || !this.rawBody) return false
36
+
37
+ if (!filename) filename = makeName(this.filename || '')
38
+
39
+ let ds = ''
40
+ if (target[target.length-1] !== '/') ds = '/'
41
+
42
+ let pathfile = `${target}${ds}${filename}`
43
+
44
+ let fd = await new Promise((rv, rj) => {
45
+ fs.open(pathfile, 'w+', 0o644, (err, fd) => {
46
+ if (err) {
47
+ rj(err)
48
+ } else {
49
+ rv(fd)
50
+ }
51
+ })
52
+ })
53
+
54
+ return new Promise((rv, rj) => {
55
+ fs.write(fd, this.rawBody, this.start, this.length,
56
+ (err, bytesWritten, buffer) => {
57
+ if (err) {
58
+ rj(err)
59
+ } else {
60
+ rv(filename)
61
+ }
62
+ })
63
+ })
64
+ .finally(() => {
65
+ fs.close(fd, (err) => {})
66
+ })
67
+
68
+ }
69
+
70
+ function getFile(name, ind=0) {
71
+ if (!this || !this.files) return null
72
+
73
+ if (this.files[name] === undefined) {
74
+ return ind < 0 ? [] : null
75
+ }
76
+
77
+ if (ind >= this.files[name].length) {
78
+ return null
79
+ }
80
+
81
+ let flist = this.files[name]
82
+
83
+ if (ind < 0) {
84
+ for (let i = 0; i < flist.length; i++) {
85
+ if (flist[i].toFile === undefined) {
86
+ flist[i].rawBody = this.rawBody
87
+ flist[i].toFile = moveFile
88
+ }
89
+ }
90
+ return flist
91
+ }
92
+
93
+ if (flist[ind].toFile === undefined) {
94
+ flist[ind].rawBody = this.rawBody
95
+ flist[ind].toFile = moveFile
96
+ }
97
+
98
+ return flist[ind]
99
+ }
100
+
101
+
102
+ class ToFile {
103
+
104
+ constructor() {
105
+
106
+ }
107
+
108
+ mid() {
109
+ let self = this
110
+ return async (c, next) => {
111
+ if (!c.isUpload) {
112
+ return await next()
113
+ }
114
+
115
+ c.getFile = getFile
116
+ await next()
117
+ }
118
+
119
+ }
120
+
121
+ }
122
+
123
+ module.exports = ToFile