@maiyunnet/kebab 2.0.2 → 2.0.4
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.
- package/index.js +1 -1
- package/lib/sql.js +1 -3
- package/lib/text.js +5 -1
- package/package.json +1 -1
- package/tsconfig.json +1 -1
- package/index.ts +0 -33
- package/lib/buffer.ts +0 -152
- package/lib/captcha.ts +0 -63
- package/lib/consistent.ts +0 -219
- package/lib/core.ts +0 -880
- package/lib/crypto.ts +0 -384
- package/lib/db.ts +0 -719
- package/lib/dns.ts +0 -405
- package/lib/fs.ts +0 -527
- package/lib/jwt.ts +0 -276
- package/lib/kv.ts +0 -1489
- package/lib/lan.ts +0 -87
- package/lib/net/formdata.ts +0 -166
- package/lib/net/request.ts +0 -150
- package/lib/net/response.ts +0 -59
- package/lib/net.ts +0 -662
- package/lib/s3.ts +0 -235
- package/lib/scan.ts +0 -364
- package/lib/session.ts +0 -230
- package/lib/sql.ts +0 -1151
- package/lib/ssh/sftp.ts +0 -508
- package/lib/ssh/shell.ts +0 -123
- package/lib/ssh.ts +0 -191
- package/lib/text.ts +0 -615
- package/lib/time.ts +0 -254
- package/lib/ws.ts +0 -523
- package/lib/zip.ts +0 -447
- package/lib/zlib.ts +0 -350
- package/main.ts +0 -27
- package/sys/child.ts +0 -678
- package/sys/cmd.ts +0 -225
- package/sys/ctr.ts +0 -904
- package/sys/master.ts +0 -355
- package/sys/mod.ts +0 -1871
- package/sys/route.ts +0 -1113
- package/types/index.d.ts +0 -283
- package/www/example/ctr/main.ts +0 -9
- package/www/example/ctr/middle.ts +0 -26
- package/www/example/ctr/test.ts +0 -3218
- package/www/example/mod/test.ts +0 -47
- package/www/example/mod/testdata.ts +0 -30
- package/www/example/ws/mproxy.ts +0 -16
- package/www/example/ws/rproxy.ts +0 -14
- 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
|
-
});
|