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/httpc.js ADDED
@@ -0,0 +1,251 @@
1
+ 'use strict'
2
+
3
+ const http2 = require('node:http2')
4
+ const fs = require('node:fs')
5
+ const process = require('node:process');
6
+ const checkHeaderLimit = require('./headerLimit.js')
7
+ const sendmsg = require('./sendmsg.js');
8
+
9
+ let Httpc = function () {
10
+ if ( !(this instanceof Httpc) ) {
11
+ return new Httpc()
12
+ }
13
+
14
+ this.logger = null
15
+ this.config = null
16
+ this.fpurl = null
17
+ this.host = ''
18
+ }
19
+
20
+ /**
21
+ *
22
+ * @param {object} app titbit实例
23
+ */
24
+
25
+ Httpc.prototype.init = function (app) {
26
+ app.config.server.allowHTTP1 = true
27
+
28
+ app.httpServ.run = this.run
29
+
30
+ app.httpServ.onRequest = this.onRequest
31
+ }
32
+
33
+ Httpc.prototype.onRequest = function () {
34
+ let self = this
35
+
36
+ let callback = (req, res) => {
37
+ req.on('error', (err) => {
38
+ self.requestError(err, req, req.headers)
39
+ })
40
+
41
+ res.on('error', (err) => {
42
+ self.requestError(err, res, req.headers)
43
+ })
44
+
45
+ req.on('aborted', err => {
46
+ err && self.requestError(err, req, req.headers)
47
+ !err && req.destroy()
48
+ })
49
+
50
+ let remote_ip = req.socket.remoteAddress || ''
51
+
52
+ if (req.url.length > self.config.maxUrlLength) {
53
+ req.url = req.url.substring(0, self.config.maxUrlLength)
54
+ }
55
+
56
+ if (self.config.globalLog) {
57
+
58
+ let real_ip = '-'
59
+ if (self.config.realIP) {
60
+ real_ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || '-'
61
+ }
62
+
63
+ res.on('finish', () => {
64
+ if (!req || !res) return;
65
+
66
+ checkHeaderLimit(req.headers, 'user-agent', 111)
67
+
68
+ if (req.httpVersion[0] === '1') {
69
+ checkHeaderLimit(req.headers, 'host', 300)
70
+
71
+ self.logger({
72
+ method : req.method,
73
+ status : res.statusCode,
74
+ ip : remote_ip,
75
+ link: `https://${req.headers.host || self.host}${req.url}`,
76
+ agent : req.headers['user-agent'] || '-',
77
+ real_ip : real_ip,
78
+ })
79
+
80
+ } else {
81
+
82
+ if (res.stream.rstCode == http2.constants.NGHTTP2_NO_ERROR) {
83
+ checkHeaderLimit(req.headers, ':authority', 300)
84
+
85
+ self.logger({
86
+ method : req.method,
87
+ status : res.statusCode,
88
+ ip : remote_ip,
89
+ link : `https://${req.authority}${req.url}`,
90
+ agent : req.headers['user-agent'] || '-',
91
+ real_ip : real_ip,
92
+ })
93
+
94
+ }
95
+
96
+ }
97
+
98
+ })
99
+
100
+ }
101
+
102
+ let urlobj = self.fpurl(req.url, self.config.autoDecodeQuery,
103
+ self.config.fastParseQuery,
104
+ self.config.maxQuery)
105
+
106
+ let rt = self.router.findRealPath(urlobj.path, req.method)
107
+
108
+ if (rt === null) {
109
+ res.statusCode = 404
110
+ res.end(self.config.notFound)
111
+ return
112
+ }
113
+
114
+ let ctx = self.ctxpool.getctx() || new self.Context()
115
+
116
+ if (req.httpVersion[0] === '1') {
117
+ ctx.version = '1.1'
118
+ ctx.major = 1
119
+ ctx.host = req.headers.host || self.host
120
+ } else {
121
+ ctx.version = '2'
122
+ ctx.major = 2
123
+ ctx.host = req.authority
124
+ }
125
+
126
+ ctx.bodyLength = 0
127
+ ctx.maxBody = self.config.maxBody
128
+ ctx.service = self.service
129
+
130
+ ctx.method = req.method
131
+ ctx.protocol = 'https'
132
+ ctx.ip = remote_ip
133
+
134
+ ctx.port = req.socket.remotePort
135
+ ctx.req = req
136
+ ctx.res = res
137
+ ctx.stream = res
138
+
139
+ ctx.headers = req.headers
140
+
141
+ ctx.path = urlobj.path
142
+ ctx.query = urlobj.query
143
+ ctx.routepath = rt.key
144
+ ctx.requestCall = rt.reqcall.reqCall
145
+ ctx.name = rt.reqcall.name
146
+ ctx.group = rt.reqcall.group
147
+ ctx.param = rt.args
148
+ rt = null
149
+
150
+ return self.midware.run(ctx).finally(()=>{
151
+ ctx.stream = null
152
+ self.ctxpool.free(ctx)
153
+ ctx = null
154
+ })
155
+
156
+ }
157
+
158
+ return callback
159
+ }
160
+
161
+ /**
162
+ * 运行HTTP/1.1服务
163
+ * @param {number} port 端口号
164
+ * @param {string} host IP地址,可以是IPv4或IPv6
165
+ * 0.0.0.0 对应使用IPv6则是::
166
+ */
167
+ Httpc.prototype.run = function (port, host) {
168
+ let self = this
169
+ let serv = null
170
+
171
+ if (this.config.key && this.config.cert) {
172
+ try {
173
+ this.config.server.key = fs.readFileSync(this.config.key)
174
+ this.config.server.cert = fs.readFileSync(this.config.cert)
175
+ } catch (err) {
176
+ !this.isWorker && console.error(err)
177
+ sendmsg('_server-error', err.message, {autoExit: true, exitCode: 1})
178
+ }
179
+ } else if (!this.config.server.SNICallback || typeof this.config.server.SNICallback !== 'function') {
180
+ !this.isWorker && console.error('APLN协议需要启用HTTPS,请设置cert和key 或 server.SNICallback。')
181
+ sendmsg('_server-error', '请设置cert和key或server.SNICallback。', {autoExit: true, exitCode: 1})
182
+ }
183
+
184
+ this.config.server.allowHTTP1 = true
185
+
186
+ serv = http2.createSecureServer(this.config.server, this.onRequest())
187
+
188
+ serv.on('tlsClientError', (err, tls) => {
189
+ self.config.errorHandle(err, '--ERR-TLS-CLIENT--');
190
+ !tls.destroyed && tls.destroy();
191
+ })
192
+
193
+ serv.on('secureConnection', (sock) => {
194
+ sock.on('error', err => {
195
+ self.config.errorHandle(err, '--ERR-CONNECTION--')
196
+ })
197
+ })
198
+
199
+ serv.on('clientError', (err, sock) => {
200
+ !sock.destroyed && sock.destroy()
201
+ })
202
+
203
+ serv.on('unknownProtocol', tls_sock => {
204
+ !tls_sock.destroyed && tls_sock.destroy()
205
+ })
206
+
207
+ serv.on('error', (e) => {
208
+ if (e.code === 'EADDRINUSE') {
209
+ if (process.send !== undefined && typeof process.send === 'function') {
210
+ process.send({type: '_eaddr'}, (err) => {})
211
+ } else {
212
+ console.error('该端口已被使用,请先停止进程')
213
+ process.exit(1)
214
+ }
215
+ } else {
216
+ self.config.errorHandle(e, '--ERR--')
217
+ }
218
+ })
219
+
220
+ serv.setTimeout(this.config.timeout, (sock) => {
221
+ !sock.destroyed && sock.destroy()
222
+ })
223
+
224
+ for (let k in this.events) {
225
+ for (let ecall of this.events[k]) {
226
+ if (typeof ecall !== 'function') {
227
+ continue
228
+ }
229
+
230
+ serv.on(k, ecall)
231
+ }
232
+ }
233
+
234
+ this.events = {}
235
+
236
+ //说明是使用unix socket模式监听服务
237
+ if (typeof port === 'string' && port.indexOf('.sock') > 0) {
238
+ serv.listen(port)
239
+ } else {
240
+ this.host = host
241
+ if (port !== 443) {
242
+ this.host += `:${port}`
243
+ }
244
+
245
+ serv.listen(port, host)
246
+ }
247
+
248
+ return serv
249
+ }
250
+
251
+ module.exports = Httpc
@@ -0,0 +1,354 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * {
5
+ * '-a' : {
6
+ * name: 'a',
7
+ * type: 'number|string|int|float|bool',
8
+ * min: 0,
9
+ * max: 100,
10
+ * match: RegExp,
11
+ * default : any
12
+ * },
13
+ *
14
+ * '$2': {
15
+ *
16
+ * },
17
+ * {
18
+ * '--port=' : {
19
+ * name : 'port',
20
+ * type : 'int',
21
+ * min: 2000,
22
+ * max: 5000,
23
+ * default: 3456
24
+ * }
25
+ * }
26
+ * }
27
+ *
28
+ */
29
+
30
+ function setVal (a, ainfo, obj, next) {
31
+
32
+ if (ainfo.type && ['int', 'float', 'number'].indexOf(ainfo.type) >= 0) {
33
+ if (isNaN(next)) {
34
+ return {
35
+ ok: false,
36
+ message: `${a} 类型错误,要求参数必须是数字类型:${next}`
37
+ }
38
+ }
39
+
40
+ if (ainfo.type === 'int' || ainfo.type === 'number') {
41
+ next = parseInt(next)
42
+ } else {
43
+ next = parseFloat(next)
44
+ }
45
+ }
46
+
47
+ if (ainfo.min !== undefined) {
48
+ if (next < ainfo.min) {
49
+ return {
50
+ ok: false,
51
+ message: `${a} 数值不能低于 ${ainfo.min}`
52
+ }
53
+ }
54
+ }
55
+
56
+ if (ainfo.max !== undefined) {
57
+ if (next > ainfo.max) {
58
+ return {
59
+ ok: false,
60
+ message: `${a} 数值不能大于 ${ainfo.max}`
61
+ }
62
+ }
63
+ }
64
+
65
+ if (ainfo.match && ainfo.match instanceof RegExp) {
66
+ if (!ainfo.match.test(next)) {
67
+ return {
68
+ ok: false,
69
+ message: `${a} 数值无法匹配 ${next}`
70
+ }
71
+ }
72
+ }
73
+
74
+ let val = next
75
+
76
+ if (ainfo.callback && typeof ainfo.callback === 'function') {
77
+ val = ainfo.callback(next)
78
+ if (val === undefined) val = next
79
+ }
80
+
81
+ obj[ ainfo.name || a ] = val
82
+
83
+ return {
84
+ ok: true,
85
+ message: ''
86
+ }
87
+
88
+ }
89
+
90
+ function checkAndSet (a, ainfo, obj, next) {
91
+
92
+ if (typeof ainfo === 'boolean') {
93
+ obj[a] = true
94
+ return {
95
+ ok: true,
96
+ message: '',
97
+ op: 'none'
98
+ }
99
+ }
100
+
101
+ if (ainfo.type === 'bool' || ainfo.type === 'boolean') {
102
+ obj[ ainfo.name || a ] = true
103
+
104
+ return {
105
+ ok: true,
106
+ message: '',
107
+ op: 'none'
108
+ }
109
+ }
110
+
111
+ if (next === null) {
112
+ return {
113
+ ok: false,
114
+ message: `${a} 必须携带参数。`
115
+ }
116
+ }
117
+
118
+ let r = setVal(a, ainfo, obj, next)
119
+
120
+ if (!r.ok && ainfo.default !== undefined) {
121
+ r.ok = true
122
+ }
123
+
124
+ if (r.ok) {
125
+ r.op = 'next'
126
+
127
+ if (a[a.length - 1] === '=') {
128
+ r.op = 'none'
129
+ }
130
+ }
131
+
132
+ return r
133
+ }
134
+
135
+ function setAutoDefault (opts, k) {
136
+ switch (opts[k].type) {
137
+ case 'number':
138
+ case 'int':
139
+ case 'float':
140
+ opts[k].default = 0
141
+ if (opts[k].min)
142
+ opts[k].default = opts[k].min;
143
+ break
144
+
145
+ case 'string':
146
+ opts[k].default = ''
147
+ break
148
+
149
+ case 'bool':
150
+ case 'boolean':
151
+ opts[k].default = false
152
+ break
153
+ }
154
+ }
155
+
156
+ /*
157
+ * opts['@autoDefault'] = true 表示自动设定默认值
158
+ *
159
+ * */
160
+
161
+ function parseArgv (options = null, obj = null) {
162
+ if (!options) options = {}
163
+
164
+ if (obj === null) obj = {}
165
+
166
+ let opts = {}
167
+
168
+ for (let k in options) {
169
+ opts[k] = options[k]
170
+ }
171
+
172
+ let autoDefault = false
173
+
174
+ if (opts['@autoDefault'] === undefined) opts['@autoDefault'] = true;
175
+
176
+ if (opts['@autoDefault'] !== undefined) {
177
+ autoDefault = !!opts['@autoDefault']
178
+ delete opts['@autoDefault']
179
+ }
180
+
181
+ let commands = []
182
+ if (opts['@command'] !== undefined) {
183
+ if (typeof opts['@command'] === 'string') {
184
+ opts['@command'].split(' ').filter(p => p.length > 0).forEach(a => {
185
+ commands.push(a.trim())
186
+ })
187
+ } else if (Array.isArray(opts['@command'])) {
188
+ commands = opts['@command'];
189
+ }
190
+ delete opts['@command'];
191
+ }
192
+
193
+ let defaultCommand = ''
194
+ if (opts['@defaultCommand'] !== undefined && commands.indexOf(opts['@defaultCommand']) >= 0) {
195
+ defaultCommand = opts['@defaultCommand'];
196
+ delete opts['@defaultCommand'];
197
+ }
198
+
199
+ let userCommand = null
200
+ let commandFromInput = false
201
+ if (commands.length > 0) {
202
+ if (process.argv.length < 3) {
203
+ if (defaultCommand) userCommand = defaultCommand;
204
+ else {
205
+ return {
206
+ ok: false,
207
+ message: '请使用子命令:' + commands.join('|'),
208
+ args: obj
209
+ }
210
+ }
211
+ } else if (commands.indexOf(process.argv[2]) < 0) {
212
+ if (defaultCommand) userCommand = defaultCommand;
213
+ else {
214
+ return {
215
+ ok: false,
216
+ message: '不支持的子命令',
217
+ args: obj
218
+ }
219
+ }
220
+ } else {
221
+ commandFromInput = true
222
+ userCommand = process.argv[2]
223
+ }
224
+ }
225
+
226
+ let tmp_val
227
+
228
+ for (let k in opts) {
229
+ if (typeof opts[k] === 'string' && opts[k].trim().length > 0) {
230
+ opts[k] = {
231
+ name: opts[k].trim(),
232
+ type: 'boolean',
233
+ default: false
234
+ }
235
+ }
236
+
237
+ if (typeof opts[k] !== 'object' || opts[k].toString() !== '[object Object]') {
238
+
239
+ opts[k] = {
240
+ type : 'boolean',
241
+ name : k,
242
+ default: false
243
+ }
244
+
245
+ } else if (opts[k].type === undefined) {
246
+
247
+ if (k.indexOf('=') > 0) {
248
+ opts[k].type = 'string'
249
+ } else if (opts[k].match || opts[k].callback) {
250
+ opts[k].type = 'string'
251
+ } else {
252
+ if (opts[k].min !== undefined || opts[k].max !== undefined) {
253
+ opts[k].type = 'int'
254
+ } else if (opts[k].default !== undefined) {
255
+ tmp_val = typeof opts[k].default
256
+ if (tmp_val === 'number' || tmp_val === 'boolean' || tmp_val === 'string') {
257
+ opts[k].type = tmp_val
258
+ } else {
259
+ opts[k].type = 'string'
260
+ }
261
+ } else {
262
+ opts[k].type = 'bool'
263
+ }
264
+ }
265
+
266
+ }
267
+
268
+ autoDefault && opts[k].default === undefined && setAutoDefault(opts, k)
269
+
270
+ if (opts[k].type === 'bool' || opts[k].type === 'boolean') {
271
+ obj[ opts[k].name || k ] = false
272
+ } else if (opts[k].default !== undefined) {
273
+ obj[ opts[k].name || k ] = opts[k].default
274
+ }
275
+
276
+ }
277
+
278
+ for (let k in opts) {
279
+ if (opts[k].alias && typeof opts[k].alias === 'string' && opts[k].alias !== k) {
280
+ opts[opts[k].alias] = opts[k]
281
+ }
282
+ }
283
+
284
+ let a
285
+ let next
286
+ let next_end = process.argv.length - 1
287
+ let r = ''
288
+ let i = 2
289
+ let offset = 1
290
+ if (commands.length > 0 && commandFromInput) {
291
+ i++
292
+ offset = 2
293
+ }
294
+
295
+ let ind = 0
296
+ let pos_key = ''
297
+ let aList = []
298
+
299
+ while (i < process.argv.length) {
300
+
301
+ //先检测是否存在对位置的引用
302
+ pos_key = '$' + `${i-offset}`
303
+ if (opts[pos_key]) {
304
+ r = checkAndSet(pos_key, opts[pos_key], obj, process.argv[i])
305
+ if (!r.ok) {
306
+ r.args = obj
307
+ return r
308
+ }
309
+ i++
310
+ continue
311
+ }
312
+
313
+ a = process.argv[i]
314
+
315
+ next = i < next_end ? process.argv[i+1] : null
316
+
317
+ ind = a.indexOf('=')
318
+
319
+ if (ind > 0) {
320
+ a = a.substring(0, ind+1)
321
+ next = process.argv[i].substring(ind+1)
322
+ }
323
+
324
+ if (opts[a]) {
325
+ r = checkAndSet(a, opts[a], obj, next)
326
+ if (!r.ok) {
327
+ r.args = obj
328
+ return r
329
+ }
330
+
331
+ if (r.op === 'next') {
332
+ i += 2
333
+ continue
334
+ }
335
+
336
+ } else {
337
+ if (a[0] !== '-') {
338
+ a[0] !== '\\' ? aList.push(a) : aList.push(a.substring(1))
339
+ }
340
+ }
341
+
342
+ i++
343
+ }
344
+
345
+ return {
346
+ ok: true,
347
+ message: '',
348
+ args: obj,
349
+ list: aList,
350
+ command: userCommand
351
+ }
352
+ }
353
+
354
+ module.exports = parseArgv
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const zlib = require('zlib')
4
+ const fs = require('fs')
5
+
6
+ const fsp = fs.promises
7
+
8
+ let zipdata = async (pathfile, isData=false) => {
9
+ let d
10
+
11
+ if (isData) {
12
+ d = pathfile
13
+ } else {
14
+ d = await fsp.readFile(pathfile)
15
+ }
16
+
17
+ return await new Promise((rv, rj) => {
18
+ zlib.gzip(d, (err, zipdata) => {
19
+ if (err) {
20
+ rj (err)
21
+ }
22
+
23
+ rv(zipdata)
24
+ })
25
+ })
26
+ }
27
+
28
+ zipdata.unzip = async (pathfile, isData = false) => {
29
+ let d
30
+ if (isData) {
31
+ d = pathfile
32
+ } else {
33
+ d = await fsp.readFile(pathfile)
34
+ }
35
+
36
+ return new Promise((rv, rj) => {
37
+ zlib.unzip(d, (err, data) => {
38
+ if (err) rj(err)
39
+
40
+ rv(data)
41
+ })
42
+ })
43
+ }
44
+
45
+ module.exports = zipdata