@maiyunnet/kebab 2.0.3 → 2.0.5

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/index.d.ts +14 -0
  2. package/index.js +1 -1
  3. package/lib/buffer.d.ts +25 -0
  4. package/lib/captcha.d.ts +12 -0
  5. package/lib/consistent.d.ts +20 -0
  6. package/lib/core.d.ts +85 -0
  7. package/lib/crypto.d.ts +47 -0
  8. package/lib/db.d.ts +88 -0
  9. package/lib/dns.d.ts +75 -0
  10. package/lib/fs.d.ts +49 -0
  11. package/lib/jwt.d.ts +30 -0
  12. package/lib/kv.d.ts +111 -0
  13. package/lib/lan.d.ts +10 -0
  14. package/lib/net/formdata.d.ts +23 -0
  15. package/lib/net/request.d.ts +23 -0
  16. package/lib/net/response.d.ts +14 -0
  17. package/lib/net.d.ts +65 -0
  18. package/lib/s3.d.ts +35 -0
  19. package/lib/scan.d.ts +31 -0
  20. package/lib/session.d.ts +22 -0
  21. package/lib/sql.d.ts +57 -0
  22. package/lib/ssh/sftp.d.ts +40 -0
  23. package/lib/ssh/shell.d.ts +13 -0
  24. package/lib/ssh.d.ts +24 -0
  25. package/lib/text.d.ts +40 -0
  26. package/lib/time.d.ts +22 -0
  27. package/lib/ws.d.ts +87 -0
  28. package/lib/zip.d.ts +40 -0
  29. package/lib/zlib.d.ts +25 -0
  30. package/main.d.ts +1 -0
  31. package/package.json +1 -1
  32. package/sys/child.d.ts +1 -0
  33. package/sys/cmd.d.ts +1 -0
  34. package/sys/ctr.d.ts +96 -0
  35. package/sys/master.d.ts +1 -0
  36. package/sys/mod.d.ts +205 -0
  37. package/sys/route.d.ts +31 -0
  38. package/tsconfig.json +1 -1
  39. package/www/example/ctr/main.d.ts +4 -0
  40. package/www/example/ctr/middle.d.ts +6 -0
  41. package/www/example/ctr/test.d.ts +94 -0
  42. package/www/example/mod/test.d.ts +20 -0
  43. package/www/example/mod/testdata.d.ts +9 -0
  44. package/www/example/ws/mproxy.d.ts +4 -0
  45. package/www/example/ws/rproxy.d.ts +4 -0
  46. package/www/example/ws/test.d.ts +7 -0
  47. package/index.ts +0 -33
  48. package/lib/buffer.ts +0 -152
  49. package/lib/captcha.ts +0 -63
  50. package/lib/consistent.ts +0 -219
  51. package/lib/core.ts +0 -880
  52. package/lib/crypto.ts +0 -384
  53. package/lib/db.ts +0 -719
  54. package/lib/dns.ts +0 -405
  55. package/lib/fs.ts +0 -527
  56. package/lib/jwt.ts +0 -276
  57. package/lib/kv.ts +0 -1489
  58. package/lib/lan.ts +0 -87
  59. package/lib/net/formdata.ts +0 -166
  60. package/lib/net/request.ts +0 -150
  61. package/lib/net/response.ts +0 -59
  62. package/lib/net.ts +0 -662
  63. package/lib/s3.ts +0 -235
  64. package/lib/scan.ts +0 -364
  65. package/lib/session.ts +0 -230
  66. package/lib/sql.ts +0 -1149
  67. package/lib/ssh/sftp.ts +0 -508
  68. package/lib/ssh/shell.ts +0 -123
  69. package/lib/ssh.ts +0 -191
  70. package/lib/text.ts +0 -626
  71. package/lib/time.ts +0 -254
  72. package/lib/ws.ts +0 -523
  73. package/lib/zip.ts +0 -447
  74. package/lib/zlib.ts +0 -350
  75. package/main.ts +0 -27
  76. package/sys/child.ts +0 -678
  77. package/sys/cmd.ts +0 -225
  78. package/sys/ctr.ts +0 -904
  79. package/sys/master.ts +0 -355
  80. package/sys/mod.ts +0 -1871
  81. package/sys/route.ts +0 -1113
  82. package/www/example/ctr/main.ts +0 -9
  83. package/www/example/ctr/middle.ts +0 -26
  84. package/www/example/ctr/test.ts +0 -3218
  85. package/www/example/mod/test.ts +0 -47
  86. package/www/example/mod/testdata.ts +0 -30
  87. package/www/example/ws/mproxy.ts +0 -16
  88. package/www/example/ws/rproxy.ts +0 -14
  89. package/www/example/ws/test.ts +0 -36
package/sys/child.ts DELETED
@@ -1,678 +0,0 @@
1
- /**
2
- * Project: Kebab, User: JianSuoQiYue
3
- * Date: 2019-5-3 23:54
4
- * Last: 2020-3-31 15:01:07, 2020-4-9 22:28:50, 2022-07-22 14:19:46, 2022-9-29 22:11:07, 2023-5-1 18:26:57, 2024-1-12 13:32:00, 2024-3-4 16:49:19
5
- */
6
- import * as http2 from 'http2';
7
- import * as tls from 'tls';
8
- import * as net from 'net';
9
- import * as http from 'http';
10
- import * as crypto from 'crypto';
11
- // --- 库和定义 ---
12
- import * as fs from '~/lib/fs';
13
- import * as lCore from '~/lib/core';
14
- import * as lText from '~/lib/text';
15
- import * as sCtr from '~/sys/ctr';
16
- import * as kebab from '~/index';
17
- // --- 初始化 ---
18
- import * as sRoute from '~/sys/route';
19
- import * as types from '~/types';
20
-
21
- /** --- 10 秒往主线程发送一次心跳的 Timer --- */
22
- const hbTimer = setInterval(function() {
23
- if (!process.connected || !process.send) {
24
- return;
25
- }
26
- process.send({
27
- action: 'hbtime',
28
- pid: process.pid
29
- });
30
- }, 10_000);
31
-
32
- /** --- 加载的证书列表(path: { sc, cert }) --- */
33
- const certList: Array<{
34
- 'sc': tls.SecureContext;
35
- 'cert': crypto.X509Certificate;
36
- }> = [];
37
-
38
- /** --- server: index --- */
39
- let certHostIndex: Record<string, number> = {};
40
-
41
- /** --- 当前的虚拟主机配置列表 - 读取于 conf/vhost/*.json --- */
42
- let vhosts: types.IVhost[] = [];
43
-
44
- /** --- http 服务器 --- */
45
- let httpServer: http.Server;
46
-
47
- /** --- http2 服务器 --- */
48
- let http2Server: http2.Http2SecureServer;
49
-
50
- /** --- 当前使用中的连接 --- */
51
- const linkCount: Record<string, number> = {};
52
-
53
- /**
54
- * --- 最终调用执行的函数块,创建 http 服务器等 ---
55
- */
56
- async function run(): Promise<void> {
57
- // --- 加载 全局、vhosts、sni 证书 ---
58
- await reload();
59
-
60
- // --- 创建服务器并启动(支持 http2/https/http/websocket) ---
61
- http2Server = http2.createSecureServer({
62
- // eslint-disable-next-line @typescript-eslint/naming-convention
63
- 'SNICallback': (servername, cb) => {
64
- const i = certHostIndex[servername];
65
- if (i !== undefined) {
66
- cb(null, certList[i].sc);
67
- return;
68
- }
69
- // --- 查找 servername ---
70
- for (let i = 0; i < certList.length; ++i) {
71
- if (!certList[i].cert.checkHost(servername)) {
72
- continue;
73
- }
74
- // --- 找到 ---
75
- cb(null, certList[i].sc);
76
- certHostIndex[servername] = i;
77
- return;
78
- }
79
- const err = new Error('CERT NOT FOUND');
80
- cb(err);
81
- },
82
- 'ciphers': 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS',
83
- 'allowHTTP1': true
84
- }, function(req: http2.Http2ServerRequest, res: http2.Http2ServerResponse): void {
85
- const host = (req.headers[':authority'] ?? req.headers['host'] ?? '');
86
- if (!host) {
87
- req.socket.destroy();
88
- return;
89
- }
90
- const key = host + req.url;
91
- (async function() {
92
- if (!linkCount[key]) {
93
- linkCount[key] = 0;
94
- }
95
- ++linkCount[key];
96
- await requestHandler(req, res, true);
97
- --linkCount[key];
98
- if (!linkCount[key]) {
99
- delete linkCount[key];
100
- }
101
- })().catch(async function(e) {
102
- await lCore.log({
103
- 'path': '',
104
- 'urlFull': '',
105
- 'hostname': '',
106
- 'req': req,
107
- 'get': {},
108
- 'cookie': {},
109
- 'headers': {}
110
- }, '[child][http2][request]' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
111
- --linkCount[key];
112
- if (!linkCount[key]) {
113
- delete linkCount[key];
114
- }
115
- });
116
- }).on('tlsClientError', (err, socket) => {
117
- socket.destroy();
118
- }).on('upgrade', function(req: http.IncomingMessage, socket: net.Socket): void {
119
- const host = (req.headers['host'] ?? '');
120
- if (!host) {
121
- req.socket.destroy();
122
- return;
123
- }
124
- const key = host + (req.url ?? '');
125
- (async function() {
126
- if (!linkCount[key]) {
127
- linkCount[key] = 0;
128
- }
129
- ++linkCount[key];
130
- await upgradeHandler(req, socket, true);
131
- --linkCount[key];
132
- if (!linkCount[key]) {
133
- delete linkCount[key];
134
- }
135
- })().catch(async function(e) {
136
- await lCore.log({
137
- 'path': '',
138
- 'urlFull': '',
139
- 'hostname': '',
140
- 'req': req,
141
- 'get': {},
142
- 'cookie': {},
143
- 'headers': {}
144
- }, '[child][http2][upgrade]' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
145
- --linkCount[key];
146
- if (!linkCount[key]) {
147
- delete linkCount[key];
148
- }
149
- });
150
- }).listen(lCore.globalConfig.httpsPort);
151
- httpServer = http.createServer(function(req: http.IncomingMessage, res: http.ServerResponse): void {
152
- const host = (req.headers['host'] ?? '');
153
- if (!host) {
154
- req.socket.destroy();
155
- return;
156
- }
157
- const key = host + (req.url ?? '');
158
- (async function() {
159
- if (!linkCount[key]) {
160
- linkCount[key] = 0;
161
- }
162
- ++linkCount[key];
163
- await requestHandler(req, res, false);
164
- --linkCount[key];
165
- if (!linkCount[key]) {
166
- delete linkCount[key];
167
- }
168
- })().catch(async function(e) {
169
- await lCore.log({
170
- 'path': '',
171
- 'urlFull': '',
172
- 'hostname': '',
173
- 'req': req,
174
- 'get': {},
175
- 'cookie': {},
176
- 'headers': {}
177
- }, '[child][http][request]' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
178
- --linkCount[key];
179
- if (!linkCount[key]) {
180
- delete linkCount[key];
181
- }
182
- });
183
- }).on('upgrade', function(req: http.IncomingMessage, socket: net.Socket): void {
184
- const host = (req.headers['host'] ?? '');
185
- if (!host) {
186
- req.socket.destroy();
187
- return;
188
- }
189
- const key = host + (req.url ?? '');
190
- (async function() {
191
- if (!linkCount[key]) {
192
- linkCount[key] = 0;
193
- }
194
- ++linkCount[key];
195
- await upgradeHandler(req, socket, false);
196
- --linkCount[key];
197
- if (!linkCount[key]) {
198
- delete linkCount[key];
199
- }
200
- })().catch(async function(e) {
201
- await lCore.log({
202
- 'path': '',
203
- 'urlFull': '',
204
- 'hostname': '',
205
- 'req': req,
206
- 'get': {},
207
- 'cookie': {},
208
- 'headers': {}
209
- }, '[child][http][upgrade]' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
210
- --linkCount[key];
211
- if (!linkCount[key]) {
212
- delete linkCount[key];
213
- }
214
- });
215
- }).listen(lCore.globalConfig.httpPort);
216
- }
217
-
218
- /**
219
- * --- http / https / http2 请求的回调函数 ---
220
- * @param req 请求对象
221
- * @param res 响应对象
222
- * @param https 是否是 ssl
223
- */
224
- async function requestHandler(
225
- req: http2.Http2ServerRequest | http.IncomingMessage,
226
- res: http2.Http2ServerResponse | http.ServerResponse,
227
- https: boolean
228
- ): Promise<void> {
229
- // --- 打开计时器 ---
230
- const timer = {
231
- 'timer': {} as NodeJS.Timeout,
232
- 'timeout': 30_000,
233
- 'callback': () => {
234
- if (!req.socket.writable) {
235
- return;
236
- }
237
- if (res.headersSent) {
238
- // --- 已经开始输出的,需要用户自行处理 ---
239
- return;
240
- }
241
- res.setHeader('content-length', 37);
242
- res.writeHead(504);
243
- res.end('<h1>504 Gateway Timeout</h1><hr>Kebab');
244
- }
245
- };
246
- timer.timer = setTimeout(timer.callback, timer.timeout);
247
- // --- 设置服务器名版本 ---
248
- res.setHeader('Server', 'Kebab/' + kebab.VER);
249
- // --- 当前 uri ---
250
- let host = req.headers[':authority'];
251
- if (host === undefined || typeof host !== 'string') {
252
- host = req.headers['host'];
253
- if (host === undefined) {
254
- req.socket.destroy();
255
- return;
256
- }
257
- }
258
- const uri = lText.parseUrl(`http${https ? 's' : ''}://${host}${req.url ?? ''}`);
259
- /** --- 当前的 vhost 配置文件 --- */
260
- const vhost = getVhostByHostname(uri.hostname ?? '');
261
- if (!vhost) {
262
- req.socket.destroy();
263
- return;
264
- /*
265
- const text = '<h1>Kebab: No permissions</h1>host: ' + (req.headers[':authority'] as string | undefined ?? req.headers['host'] ?? '') + '<br>url: ' + (lText.htmlescape(req.url ?? ''));
266
- res.setHeader('content-type', 'text/html; charset=utf-8');
267
- res.setHeader('content-length', Buffer.byteLength(text));
268
- res.writeHead(403);
269
- res.end(text);
270
- return;
271
- */
272
- }
273
- const vhostRoot = vhost.root.replace(/\${example}/g, kebab.ROOT_PATH + 'www/example/');
274
- /** --- 网站绝对根目录,末尾带 / --- */
275
- let rootPath = lText.isRealPath(vhostRoot) ? vhostRoot : kebab.WWW_CWD + vhostRoot;
276
- if (!rootPath.endsWith('/')) {
277
- rootPath += '/';
278
- }
279
- /** --- 请求的路径部分,前导带 / 末尾不一定,用户怎么请求就是什么 --- */
280
- let path = uri.pathname ?? '/';
281
- if (path !== '/') {
282
- // --- 去除 ../ ./ 之类的 ---
283
- path = lText.urlResolve('/', path);
284
- }
285
- /** --- [''] / ['', 'abc', 'def'] --- */
286
- const pathList = path.split('/');
287
- /** --- 当前处理的路径,前导不带 / 末尾带 /,例如 abc/ --- */
288
- let now = '';
289
- for (let i = 0; i < pathList.length; ++i) {
290
- const item = pathList[i];
291
- if (item !== '') {
292
- // --- 判断 item 是文件还是文件夹 ---
293
- /** --- 'abc' / 'def.json' --- */
294
- let stat = await fs.stats(rootPath + now + item);
295
- if (!stat) {
296
- res.setHeader('content-type', 'text/html; charset=utf-8');
297
- res.setHeader('content-length', 22);
298
- res.writeHead(404);
299
- res.end('<h1>404 Not found</h1><hr>Kebab');
300
- return;
301
- }
302
- if (stat.isDirectory()) {
303
- now += item + '/';
304
- // --- 判断是不是动态层 ---
305
- stat = await fs.stats(rootPath + now + 'kebab.json');
306
- if (stat) {
307
- // --- 动态层,交给 Route 处理器 ---
308
- try {
309
- if (await sRoute.run({
310
- 'req': req,
311
- 'res': res,
312
- 'uri': uri,
313
- 'rootPath': rootPath + now,
314
- 'urlBase': '/' + now,
315
- 'path': path.slice(('/' + pathList.slice(0, i).join('/')).length + 1),
316
- 'timer': timer
317
- })) {
318
- return;
319
- }
320
- }
321
- catch (e: types.Json) {
322
- await lCore.log({
323
- 'path': path.slice(('/' + pathList.slice(0, i).join('/')).length + 1),
324
- 'urlFull': (uri.protocol ?? '') + '//' + (uri.host ?? '') + '/' + now,
325
- 'hostname': uri.hostname ?? '',
326
- 'req': req,
327
- 'get': uri.query ? lText.queryParse(uri.query) : {},
328
- 'cookie': {},
329
- 'headers': {}
330
- }, '(E01)' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
331
- res.setHeader('content-type', 'text/html; charset=utf-8');
332
- res.setHeader('content-length', 25);
333
- res.writeHead(500);
334
- res.end('<h1>500 Server Error</h1><hr>Kebab');
335
- return;
336
- }
337
- }
338
- }
339
- else {
340
- // --- 文件,直接输出并结束 ---
341
- await fs.readToResponse(rootPath + now + item, req, res, stat);
342
- return;
343
- }
344
- }
345
- else {
346
- // --- item 为空,但是 i 不是 0,则是最后一个空,直接 break ---
347
- if (i > 0) {
348
- break;
349
- }
350
- // --- 本层是根,判断根是不是动态层 ---
351
- const stat = await fs.stats(rootPath + now + 'kebab.json');
352
- if (stat) {
353
- // --- 动态层,交给 Route 处理器 ---
354
- try {
355
- if (await sRoute.run({
356
- 'req': req,
357
- 'res': res,
358
- 'uri': uri,
359
- 'rootPath': rootPath + now,
360
- 'urlBase': '/' + now,
361
- 'path': path.slice(1),
362
- 'timer': timer
363
- })) {
364
- return;
365
- }
366
- }
367
- catch (e: types.Json) {
368
- await lCore.log({
369
- 'path': path.slice(1),
370
- 'urlFull': (uri.protocol ?? '') + '//' + (uri.host ?? '') + '/' + now,
371
- 'hostname': uri.hostname ?? '',
372
- 'req': req,
373
- 'get': uri.query ? lText.queryParse(uri.query) : {},
374
- 'cookie': {},
375
- 'headers': {}
376
- }, '(E02)' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
377
- res.setHeader('content-type', 'text/html; charset=utf-8');
378
- res.setHeader('content-length', 25);
379
- res.writeHead(500);
380
- res.end('<h1>500 Server Error</h1><hr>Kebab');
381
- return;
382
- }
383
- }
384
- }
385
- }
386
- // --- 最后一层是目录,且不是动态层,判断有没有主页,有则输出,没有则 403 出错 ---
387
- const indexFiles: string[] = ['index.html', 'index.htm'];
388
- for (const indexFile of indexFiles) {
389
- const stat = await fs.isFile(rootPath + now + indexFile);
390
- if (stat) {
391
- await fs.readToResponse(rootPath + now + indexFile, req, res, stat);
392
- return;
393
- }
394
- }
395
- res.setHeader('content-type', 'text/html; charset=utf-8');
396
- res.setHeader('content-length', 22);
397
- res.writeHead(403);
398
- res.end('<h1>403 Forbidden</h1><hr>Kebab');
399
- }
400
-
401
- /**
402
- * --- WebSocket 响应 handler ---
403
- * @param req 请求对象
404
- * @param socket socket 对象
405
- * @param https 是否是 https
406
- */
407
- async function upgradeHandler(req: http.IncomingMessage, socket: net.Socket, https: boolean): Promise<void> {
408
- socket.removeAllListeners('error');
409
- // --- 当前 uri ---
410
- const uri = lText.parseUrl(`ws${https ? 's' : ''}://${req.headers['host'] ?? ''}${req.url ?? ''}`);
411
- /** --- 当前的 vhost 配置文件 --- */
412
- const vhost = getVhostByHostname(uri.hostname ?? '');
413
- if (!vhost) {
414
- socket.destroy();
415
- return;
416
- }
417
- const vhostRoot = vhost.root.replace(/\${example}/g, kebab.ROOT_PATH + 'www/example/');
418
- /** --- 网站绝对根目录,末尾带 / --- */
419
- let rootPath = lText.isRealPath(vhostRoot) ? vhostRoot : kebab.WWW_CWD + vhostRoot;
420
- if (!rootPath.endsWith('/')) {
421
- rootPath += '/';
422
- }
423
- /** --- 请求的路径部分,前导带 / 末尾不一定,用户怎么请求就是什么 --- */
424
- let path = uri.pathname ?? '/';
425
- if (path !== '/') {
426
- // --- 去除 ../ ./ 之类的 ---
427
- path = lText.urlResolve('/', path);
428
- }
429
- /** --- [''] / ['', 'abc', 'def'] --- */
430
- const pathList = path.split('/');
431
- /** --- 当前处理的路径,前导不带 / 末尾带 /,例如 abc/ --- */
432
- let now = '';
433
- for (let i = 0; i < pathList.length; ++i) {
434
- const item = pathList[i];
435
- if (item !== '') {
436
- // --- 判断 item 是文件还是文件夹 ---
437
- /** --- 'abc' / 'def.json' --- */
438
- let stat = await fs.stats(rootPath + now + item);
439
- if (!stat) {
440
- socket.destroy();
441
- return;
442
- }
443
- if (stat.isDirectory()) {
444
- now += item + '/';
445
- // --- 判断是不是动态层 ---
446
- stat = await fs.stats(rootPath + now + 'kebab.json');
447
- if (stat) {
448
- // --- 动态层,交给 Route 处理器 ---
449
- if (await sRoute.run({
450
- 'req': req,
451
- 'socket': socket,
452
- 'uri': uri,
453
- 'rootPath': rootPath + now,
454
- 'urlBase': '/' + now,
455
- 'path': path.slice(('/' + pathList.slice(0, i).join('/')).length + 1)
456
- })) {
457
- return;
458
- }
459
- }
460
- }
461
- else {
462
- // --- 文件,报错 ---
463
- socket.destroy();
464
- return;
465
- }
466
- }
467
- else {
468
- // --- item 为空,但是 i 不是 0,则是最后一个空,直接 break ---
469
- if (i > 0) {
470
- break;
471
- }
472
- // --- 判断根是不是动态层 ---
473
- const stat = await fs.stats(rootPath + now + 'kebab.json');
474
- if (stat) {
475
- // --- 动态层,交给 Route 处理器 ---
476
- if (await sRoute.run({
477
- 'req': req,
478
- 'socket': socket,
479
- 'uri': uri,
480
- 'rootPath': rootPath + now,
481
- 'urlBase': '/' + now,
482
- 'path': path.slice(1)
483
- })) {
484
- return;
485
- }
486
- }
487
- }
488
- }
489
- // --- 最后一层,又不是动态层 ---
490
- socket.destroy();
491
- }
492
-
493
- /**
494
- * --- 加载/重载 vhosts 信息、重新加载全局 config、重新加载证书。(清除动态 kebab.json 信息、data 信息、语言包信息) ---
495
- */
496
- async function reload(): Promise<void> {
497
- // --- 清除全局 config,并重新加载全局信息 ---
498
- const configContent = await fs.getContent(kebab.CONF_CWD + 'config.json', 'utf8');
499
- if (!configContent) {
500
- throw `File '${kebab.CONF_CWD}config.json' not found.`;
501
- }
502
- const config = lText.parseJson(configContent);
503
- for (const key in lCore.globalConfig) {
504
- delete lCore.globalConfig[key];
505
- }
506
- for (const key in config) {
507
- lCore.globalConfig[key] = config[key];
508
- }
509
- // --- 重新加载 VHOST 信息(/conf/vhost/) ---
510
- const files = await fs.readDir(kebab.VHOST_CWD);
511
- vhosts = [];
512
- for (const file of files) {
513
- if (!file.name.endsWith('.json')) {
514
- continue;
515
- }
516
- const fstr = await fs.getContent(kebab.VHOST_CWD + file.name, 'utf8');
517
- if (!fstr) {
518
- continue;
519
- }
520
- let list: types.IVhost | types.IVhost[] = lText.parseJson(fstr);
521
- if (!Array.isArray(list)) {
522
- list = [list];
523
- }
524
- for (const item of list) {
525
- vhosts.push(item);
526
- }
527
- }
528
- // --- 重新加载证书对 ---
529
- certList.length = 0;
530
- try {
531
- const certConfig = await fs.getContent(kebab.CONF_CWD + 'cert.json', 'utf8');
532
- if (certConfig) {
533
- const certs = lText.parseJson(certConfig);
534
- for (const item of certs) {
535
- const key = await fs.getContent(lText.isRealPath(item.key) ? item.key : kebab.CERT_CWD + item.key, 'utf8');
536
- const cert = await fs.getContent(lText.isRealPath(item.cert) ? item.cert : kebab.CERT_CWD + item.cert, 'utf8');
537
- if (!cert || !key) {
538
- continue;
539
- }
540
- const certo = new crypto.X509Certificate(cert);
541
- const sc = tls.createSecureContext({
542
- 'key': key,
543
- 'cert': cert
544
- });
545
- certList.push({
546
- 'cert': certo,
547
- 'sc': sc
548
- });
549
- }
550
- }
551
- }
552
- catch {
553
- // --- NOTHING ---
554
- }
555
- certHostIndex = {};
556
- // --- 其他操作 ---
557
- sRoute.clearKebabConfigs();
558
- sCtr.clearLocaleData();
559
- }
560
-
561
- // --- 接收主进程回传信号,主要用来 reload,restart ---
562
- process.on('message', function(msg: types.Json) {
563
- (async function() {
564
- switch (msg.action) {
565
- case 'reload': {
566
- await reload();
567
- // eslint-disable-next-line no-console
568
- console.log(`[child] Worker ${process.pid} reload execution succeeded.`);
569
- break;
570
- }
571
- case 'stop': {
572
- // --- 需要停止监听,等待已有连接全部断开,然后关闭线程 ---
573
- httpServer.close();
574
- http2Server.close();
575
- clearInterval(hbTimer);
576
- // --- 等待连接全部断开 ---
577
- /** --- 当前已等待时间,等待不超过 1 小时 --- */
578
- let waiting = 0;
579
- while (true) {
580
- if (!Object.keys(linkCount).length) {
581
- break;
582
- }
583
- // --- 有长连接,等待中 ---
584
- const str: string[] = [];
585
- for (const key in linkCount) {
586
- str.push(key + ':' + linkCount[key].toString());
587
- }
588
- // eslint-disable-next-line no-console
589
- console.log(`[child] Worker ${process.pid} busy: ${str.join(',')}.`);
590
- await lCore.log({
591
- 'path': '',
592
- 'urlFull': '',
593
- 'hostname': '',
594
- 'req': null,
595
- 'get': {},
596
- 'cookie': {},
597
- 'headers': {}
598
- }, `[child] Worker ${process.pid} busy: ${str.join(',')}.`, '-error');
599
- await lCore.sleep(30_000);
600
- waiting += 30_000;
601
- if (waiting > 3600_000) {
602
- break;
603
- }
604
- }
605
- // --- 链接全部断开 ---
606
- process.exit();
607
- break;
608
- }
609
- case 'global': {
610
- if (msg.data === undefined || msg.data === null) {
611
- delete lCore.global[msg.key];
612
- break;
613
- }
614
- if (msg.key === '__init__') {
615
- // --- 初始化 ---
616
- for (const key in msg.data) {
617
- lCore.global[key] = msg.data[key];
618
- }
619
- break;
620
- }
621
- lCore.global[msg.key] = msg.data;
622
- break;
623
- }
624
- }
625
- })().catch(async function(e) {
626
- await lCore.log({
627
- 'path': '',
628
- 'urlFull': '',
629
- 'hostname': '',
630
- 'req': null,
631
- 'get': {},
632
- 'cookie': {},
633
- 'headers': {}
634
- }, '[child][process][message]' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
635
- });
636
- });
637
-
638
- /**
639
- * --- 获取匹配的 vhost 对象 ---
640
- * --- 如果有精准匹配,以精准匹配为准,否则为 2 级泛匹配(vSub),最后全局泛匹配(vGlobal) ---
641
- * @param hostname 当前的 hostname,不带端口
642
- */
643
- function getVhostByHostname(hostname: string): types.IVhost | null {
644
- let vGlobal!: types.IVhost, vSub!: types.IVhost;
645
- for (const vhost of vhosts) {
646
- for (let domain of vhost.domains) {
647
- if (domain === '*') {
648
- // --- 全局泛匹配 ---
649
- vGlobal = vhost;
650
- }
651
- else if (domain.includes('*')) {
652
- // --- 2 级泛匹配 ---
653
- domain = domain.replace(/\*/, '^.+?').replace(/\./, '\\.');
654
- if (new RegExp(domain + '$').test(hostname)) {
655
- vSub = vhost;
656
- }
657
- }
658
- else if (domain === hostname) {
659
- // --- 完全匹配 ---
660
- return vhost;
661
- }
662
- }
663
- }
664
- if (vSub) {
665
- return vSub;
666
- }
667
- if (vGlobal) {
668
- return vGlobal;
669
- }
670
- return null;
671
- }
672
-
673
- run().catch(function(e): void {
674
- /* eslint-disable no-console */
675
- console.log('[child] ------ [Process fatal Error] ------');
676
- console.log(e);
677
- /* eslint-enable */
678
- });