@maiyunnet/kebab 2.0.7 → 2.0.9
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.d.ts +11 -1
- package/index.js +13 -1
- package/lib/buffer.d.ts +25 -0
- package/lib/buffer.js +30 -5
- package/lib/captcha.d.ts +15 -0
- package/lib/captcha.js +20 -0
- package/lib/consistent.d.ts +51 -0
- package/lib/consistent.js +59 -0
- package/lib/core.d.ts +134 -0
- package/lib/core.js +176 -0
- package/lib/crypto.d.ts +75 -6
- package/lib/crypto.js +206 -38
- package/lib/db.d.ts +104 -0
- package/lib/db.js +126 -0
- package/lib/dns.d.ts +51 -0
- package/lib/dns.js +54 -2
- package/lib/fs.d.ts +100 -0
- package/lib/fs.js +118 -0
- package/lib/jwt.d.ts +43 -0
- package/lib/jwt.js +45 -0
- package/lib/kv.d.ts +362 -0
- package/lib/kv.js +377 -0
- package/lib/lan.d.ts +6 -0
- package/lib/lan.js +7 -0
- package/lib/net/formdata.d.ts +38 -0
- package/lib/net/formdata.js +43 -0
- package/lib/net/request.d.ts +62 -0
- package/lib/net/request.js +57 -0
- package/lib/net/response.d.ts +21 -0
- package/lib/net/response.js +16 -0
- package/lib/net.d.ts +86 -0
- package/lib/net.js +140 -0
- package/lib/s3.d.ts +52 -0
- package/lib/s3.js +51 -0
- package/lib/scan.d.ts +52 -0
- package/lib/scan.js +84 -0
- package/lib/session.d.ts +31 -0
- package/lib/session.js +52 -1
- package/lib/sql.d.ts +176 -0
- package/lib/sql.js +287 -2
- package/lib/ssh/sftp.d.ts +106 -0
- package/lib/ssh/sftp.js +106 -0
- package/lib/ssh/shell.d.ts +37 -0
- package/lib/ssh/shell.js +31 -0
- package/lib/ssh.d.ts +32 -0
- package/lib/ssh.js +32 -0
- package/lib/text.d.ts +131 -0
- package/lib/text.js +188 -0
- package/lib/time.d.ts +53 -0
- package/lib/time.js +55 -0
- package/lib/ws.d.ts +68 -0
- package/lib/ws.js +74 -0
- package/lib/zip.d.ts +53 -0
- package/lib/zip.js +73 -0
- package/lib/zlib.d.ts +76 -0
- package/lib/zlib.js +78 -0
- package/main.d.ts +5 -0
- package/main.js +12 -0
- package/package.json +3 -2
- package/sys/child.js +104 -0
- package/sys/cmd.js +28 -0
- package/sys/ctr.d.ts +166 -0
- package/sys/ctr.js +177 -0
- package/sys/master.js +63 -0
- package/sys/mod.d.ts +266 -0
- package/sys/mod.js +335 -0
- package/sys/route.d.ts +34 -0
- package/sys/route.js +164 -0
- package/www/example/ctr/test.d.ts +3 -0
- package/www/example/ctr/test.js +63 -1
- package/www/example/mod/test.js +14 -0
- package/www/example/mod/testdata.js +9 -0
- package/www/example/ws/test.js +1 -0
- package/.VSCodeCounter/2025-02-14_14-46-44/details.md +0 -82
- package/.VSCodeCounter/2025-02-14_14-46-44/diff-details.md +0 -15
- package/.VSCodeCounter/2025-02-14_14-46-44/diff.csv +0 -2
- package/.VSCodeCounter/2025-02-14_14-46-44/diff.md +0 -19
- package/.VSCodeCounter/2025-02-14_14-46-44/diff.txt +0 -22
- package/.VSCodeCounter/2025-02-14_14-46-44/results.csv +0 -69
- package/.VSCodeCounter/2025-02-14_14-46-44/results.json +0 -1
- package/.VSCodeCounter/2025-02-14_14-46-44/results.md +0 -48
- package/.VSCodeCounter/2025-02-14_14-46-44/results.txt +0 -118
- package/.vscode/tasks.json +0 -15
package/sys/route.js
CHANGED
|
@@ -39,9 +39,16 @@ exports.unlinkUploadFiles = unlinkUploadFiles;
|
|
|
39
39
|
exports.waitCtr = waitCtr;
|
|
40
40
|
exports.trimPost = trimPost;
|
|
41
41
|
exports.getFormData = getFormData;
|
|
42
|
+
/**
|
|
43
|
+
* Project: Kebab, User: JianSuoQiYue
|
|
44
|
+
* Date: 2019-4-15 13:40
|
|
45
|
+
* Last: 2020-4-14 13:52:00, 2022-09-07 01:43:31, 2023-12-29 17:24:03, 2024-2-7 00:28:50, 2024-6-6 15:15:54, 2025-6-13 19:23:53
|
|
46
|
+
*/
|
|
42
47
|
const http = __importStar(require("http"));
|
|
43
48
|
const stream = __importStar(require("stream"));
|
|
49
|
+
// --- 第三方 ---
|
|
44
50
|
const ws = __importStar(require("@litert/websocket"));
|
|
51
|
+
// --- 库和定义 ---
|
|
45
52
|
const lFs = __importStar(require("../lib/fs"));
|
|
46
53
|
const lZlib = __importStar(require("../lib/zlib"));
|
|
47
54
|
const lCore = __importStar(require("../lib/core"));
|
|
@@ -51,18 +58,29 @@ const lResponse = __importStar(require("../lib/net/response"));
|
|
|
51
58
|
const lWs = __importStar(require("../lib/ws"));
|
|
52
59
|
const sCtr = __importStar(require("./ctr"));
|
|
53
60
|
const kebab = __importStar(require("../index"));
|
|
61
|
+
/** --- 动态层 kebab.json 缓存(文件路径: 最终合并值) --- */
|
|
54
62
|
let kebabConfigs = {};
|
|
63
|
+
/**
|
|
64
|
+
* --- 清除已经加载的虚拟主机配置文件 ---
|
|
65
|
+
*/
|
|
55
66
|
function clearKebabConfigs() {
|
|
56
67
|
kebabConfigs = {};
|
|
57
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* --- 若为动态路径则执行此函数,此函数不进行判断 kebab.json 是否存在 ---
|
|
71
|
+
* @param data 传导的数据
|
|
72
|
+
*/
|
|
58
73
|
async function run(data) {
|
|
74
|
+
// --- 检测 path 是否是静态文件 ---
|
|
59
75
|
if (/^(stc\/.*|favicon.\w+?\??.*|apple[\w-]+?\.png\??.*|[\w-]+?\.txt\??.*|[\w-]+?\.html\??.*)/.test(data.path)) {
|
|
60
76
|
return false;
|
|
61
77
|
}
|
|
78
|
+
// --- 根据 res 还是 socket 进行初始化设置 ---
|
|
62
79
|
if (data.res) {
|
|
63
80
|
data.res.setHeader('expires', 'Mon, 26 Jul 1994 05:00:00 GMT');
|
|
64
81
|
data.res.setHeader('cache-control', 'no-store');
|
|
65
82
|
}
|
|
83
|
+
// --- 判断 kebab config 是否已经读取过 ---
|
|
66
84
|
if (!kebabConfigs[data.rootPath + 'kebab.json']) {
|
|
67
85
|
const configContent = await lFs.getContent(data.rootPath + 'kebab.json', 'utf8');
|
|
68
86
|
if (!configContent) {
|
|
@@ -81,6 +99,7 @@ async function run(data) {
|
|
|
81
99
|
if (routeContent) {
|
|
82
100
|
kebabConfigs[data.rootPath + 'kebab.json'].route = lText.parseJson(routeContent);
|
|
83
101
|
}
|
|
102
|
+
// --- 将全局的项目应用到 vhostConfigs 里,但当 vhostConfigs 有项,则不应用 ---
|
|
84
103
|
for (const name in lCore.globalConfig) {
|
|
85
104
|
if (typeof lCore.globalConfig[name] !== 'object') {
|
|
86
105
|
if (kebabConfigs[data.rootPath + 'kebab.json'][name] === undefined) {
|
|
@@ -98,6 +117,7 @@ async function run(data) {
|
|
|
98
117
|
}
|
|
99
118
|
}
|
|
100
119
|
}
|
|
120
|
+
// --- 加载 kebab config ---
|
|
101
121
|
const config = {};
|
|
102
122
|
const configData = kebabConfigs[data.rootPath + 'kebab.json'];
|
|
103
123
|
for (const name in configData) {
|
|
@@ -108,6 +128,7 @@ async function run(data) {
|
|
|
108
128
|
'qs': data.uri.query ?? '',
|
|
109
129
|
'startTime': process.hrtime.bigint(),
|
|
110
130
|
'startMemory': process.memoryUsage().rss,
|
|
131
|
+
// --- 环境判断 ---
|
|
111
132
|
'mobile': data.req.headers['user-agent'] ? data.req.headers['user-agent'].includes('mobile') : false,
|
|
112
133
|
'wechat': data.req.headers['user-agent'] ? data.req.headers['user-agent'].includes('micromessenger') : false,
|
|
113
134
|
'https': data.uri.protocol === 'https' ? true : false,
|
|
@@ -115,12 +136,14 @@ async function run(data) {
|
|
|
115
136
|
'hostname': data.uri.hostname ?? '',
|
|
116
137
|
'hostport': data.uri.port ? parseInt(data.uri.port) : (data.uri.protocol === 'https' ? 443 : 80),
|
|
117
138
|
'uri': data.uri,
|
|
139
|
+
// --- 服务端用的路径 ---
|
|
118
140
|
'rootPath': data.rootPath,
|
|
119
141
|
'ctrPath': data.rootPath + 'ctr/',
|
|
120
142
|
'viewPath': data.rootPath + 'view/',
|
|
121
143
|
'dataPath': data.rootPath + 'data/',
|
|
122
144
|
'modPath': data.rootPath + 'mod/',
|
|
123
145
|
'wsPath': data.rootPath + 'ws/',
|
|
146
|
+
// --- 前端用的路径 ---
|
|
124
147
|
'urlBase': data.urlBase,
|
|
125
148
|
'urlStc': data.urlBase + 'stc/',
|
|
126
149
|
'urlFull': (data.uri.protocol ?? '') + '//' + (data.uri.host ?? '') + data.urlBase,
|
|
@@ -133,13 +156,18 @@ async function run(data) {
|
|
|
133
156
|
if (!config.set.staticPathFull) {
|
|
134
157
|
config.set.staticPathFull = config.const.urlStcFull;
|
|
135
158
|
}
|
|
159
|
+
// --- data.path 是安全的,不会是 ../../ 来访问到了外层,已经做过处理 ---
|
|
136
160
|
let path = data.path;
|
|
161
|
+
// --- 如果为空则定义为 @ ---
|
|
137
162
|
if (path === '') {
|
|
138
163
|
path = '@';
|
|
139
164
|
}
|
|
165
|
+
// --- 检测是否托管语言页面 ---
|
|
140
166
|
const param = [];
|
|
141
167
|
if (config.lang && data.res) {
|
|
168
|
+
// --- 仅仅 res 模式支持 config.lang ---
|
|
142
169
|
if (path[2] !== '/') {
|
|
170
|
+
// --- 不管是首页还是 @ 页,都会到此处 ---
|
|
143
171
|
let isDirect = false;
|
|
144
172
|
if (!path) {
|
|
145
173
|
if (config.lang.direct.includes('@')) {
|
|
@@ -151,11 +179,13 @@ async function run(data) {
|
|
|
151
179
|
if (!path.startsWith(ditem)) {
|
|
152
180
|
continue;
|
|
153
181
|
}
|
|
182
|
+
// --- 放行 ---
|
|
154
183
|
isDirect = true;
|
|
155
184
|
break;
|
|
156
185
|
}
|
|
157
186
|
}
|
|
158
187
|
if (!isDirect) {
|
|
188
|
+
// --- 不放行 ---
|
|
159
189
|
const alang = data.req.headers['accept-language']?.toLowerCase() ?? config.lang.list[0];
|
|
160
190
|
const apath = config.const.path + (config.const.qs ? '?' + config.const.qs : '');
|
|
161
191
|
for (const lang of config.lang.list) {
|
|
@@ -181,6 +211,7 @@ async function run(data) {
|
|
|
181
211
|
}
|
|
182
212
|
}
|
|
183
213
|
else {
|
|
214
|
+
// --- 已经是含语言的路径了 --
|
|
184
215
|
param.push(path.slice(0, 2));
|
|
185
216
|
path = path.slice(3);
|
|
186
217
|
if (!path) {
|
|
@@ -188,6 +219,7 @@ async function run(data) {
|
|
|
188
219
|
}
|
|
189
220
|
}
|
|
190
221
|
}
|
|
222
|
+
// --- 检查路由表 ---
|
|
191
223
|
let match = null;
|
|
192
224
|
let pathLeft = '', pathRight = '';
|
|
193
225
|
for (const rule in config.route) {
|
|
@@ -213,6 +245,7 @@ async function run(data) {
|
|
|
213
245
|
pathLeft = getWsCtrName(path);
|
|
214
246
|
}
|
|
215
247
|
}
|
|
248
|
+
// --- 若文件名为保留的 middle 将不允许进行 ---
|
|
216
249
|
if (pathLeft.startsWith('middle')) {
|
|
217
250
|
const text = '[Error] Controller not found, path: ' + path + '.';
|
|
218
251
|
if (data.res) {
|
|
@@ -232,7 +265,9 @@ async function run(data) {
|
|
|
232
265
|
}
|
|
233
266
|
return true;
|
|
234
267
|
}
|
|
268
|
+
// --- 原始 GET ---
|
|
235
269
|
const get = data.uri.query ? lText.queryParse(data.uri.query) : {};
|
|
270
|
+
// --- Cookie ---
|
|
236
271
|
const cookies = {};
|
|
237
272
|
if (data.req.headers['cookie']) {
|
|
238
273
|
const hcookies = data.req.headers['cookie'].split(';');
|
|
@@ -241,20 +276,26 @@ async function run(data) {
|
|
|
241
276
|
cookies[co[0].trim()] = decodeURIComponent(co[1]);
|
|
242
277
|
}
|
|
243
278
|
}
|
|
279
|
+
// --- 处理 headers ---
|
|
244
280
|
const headers = {};
|
|
245
281
|
for (const key in data.req.headers) {
|
|
246
282
|
headers[key.toLowerCase()] = data.req.headers[key];
|
|
247
283
|
}
|
|
248
284
|
headers['authorization'] ??= '';
|
|
285
|
+
/** --- 开发者返回值 --- */
|
|
249
286
|
let rtn;
|
|
250
287
|
if (data.socket && data.req instanceof http.IncomingMessage) {
|
|
288
|
+
// --- socket 模式,判断真实控制器文件是否存在 ---
|
|
251
289
|
const filePath = config.const.wsPath + pathLeft + '.js';
|
|
252
290
|
if (!await lFs.isFile(filePath)) {
|
|
291
|
+
// --- 指定的控制器不存在 ---
|
|
253
292
|
data.socket?.destroy();
|
|
254
293
|
return true;
|
|
255
294
|
}
|
|
295
|
+
// --- 加载控制器文件 ---
|
|
256
296
|
const ctrCtr = (await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)))).default;
|
|
257
297
|
const cctr = new ctrCtr(config, data.req);
|
|
298
|
+
// --- 先处理 web socket 的情况 ---
|
|
258
299
|
let wsSocket;
|
|
259
300
|
try {
|
|
260
301
|
const options = await cctr.onUpgrade();
|
|
@@ -327,6 +368,7 @@ async function run(data) {
|
|
|
327
368
|
return;
|
|
328
369
|
}
|
|
329
370
|
if (typeof wrtn === 'object') {
|
|
371
|
+
// --- 返回的是数组,那么代表可能是 JSON,可能是对象序列 ---
|
|
330
372
|
wsSocket.writeResult(wrtn);
|
|
331
373
|
return;
|
|
332
374
|
}
|
|
@@ -337,6 +379,7 @@ async function run(data) {
|
|
|
337
379
|
break;
|
|
338
380
|
}
|
|
339
381
|
default: {
|
|
382
|
+
// --- nothing ---
|
|
340
383
|
}
|
|
341
384
|
}
|
|
342
385
|
}).on('drain', async () => {
|
|
@@ -368,22 +411,34 @@ async function run(data) {
|
|
|
368
411
|
if (!data.res) {
|
|
369
412
|
return true;
|
|
370
413
|
}
|
|
414
|
+
// --- 加载中间控制器 ---
|
|
371
415
|
const middleCtr = (await Promise.resolve(`${config.const.ctrPath + 'middle'}`).then(s => __importStar(require(s)))).default;
|
|
372
416
|
const middle = new middleCtr(config, data.req, data.res);
|
|
417
|
+
/** --- 可能存在的最终控制器 --- */
|
|
373
418
|
let cctr = null;
|
|
419
|
+
// --- 对信息进行初始化 ---
|
|
374
420
|
if (data.timer) {
|
|
375
421
|
middle.setPrototype('_timer', data.timer);
|
|
376
422
|
}
|
|
423
|
+
// --- 路由定义的参数序列 ---
|
|
377
424
|
middle.setPrototype('_param', param);
|
|
425
|
+
// --- action 名 ---
|
|
378
426
|
middle.setPrototype('_action', pathRight);
|
|
379
427
|
middle.setPrototype('_headers', headers);
|
|
380
428
|
middle.setPrototype('_get', get);
|
|
381
429
|
const rawPost = await getPost(data.req);
|
|
430
|
+
// --- 原始 POST ---
|
|
382
431
|
middle.setPrototype('_rawPost', rawPost[1]);
|
|
432
|
+
// --- 原始 input ---
|
|
383
433
|
middle.setPrototype('_input', rawPost[0]);
|
|
434
|
+
// --- 处理 POST 的值 JSON 上面就同时处理了 ---
|
|
435
|
+
// --- 格式化 post 数据 ---
|
|
436
|
+
// --- assign 是为了创建一份拷贝 ---
|
|
384
437
|
const post = Object.assign({}, rawPost[1]);
|
|
385
438
|
trimPost(post);
|
|
386
439
|
middle.setPrototype('_post', post);
|
|
440
|
+
// --- form data 格式交由用户自行获取,可以直接获取文件流,然后做他想做的事情 ---
|
|
441
|
+
// --- 执行中间控制器的 onLoad ---
|
|
387
442
|
try {
|
|
388
443
|
rtn = await middle.onLoad();
|
|
389
444
|
}
|
|
@@ -398,8 +453,11 @@ async function run(data) {
|
|
|
398
453
|
let cacheTTL = middle.getPrototype('_cacheTTL');
|
|
399
454
|
let httpCode = middle.getPrototype('_httpCode');
|
|
400
455
|
if (rtn === undefined || rtn === true) {
|
|
456
|
+
// --- 只有不返回或返回 true 时才加载控制文件 ---
|
|
457
|
+
// --- 判断真实控制器文件是否存在 ---
|
|
401
458
|
const filePath = config.const.ctrPath + pathLeft + '.js';
|
|
402
459
|
if (!await lFs.isFile(filePath)) {
|
|
460
|
+
// --- 指定的控制器不存在 ---
|
|
403
461
|
const text = '[Error] Controller not found, path: ' + path + '.';
|
|
404
462
|
if (config.route['#404']) {
|
|
405
463
|
data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
|
|
@@ -413,10 +471,13 @@ async function run(data) {
|
|
|
413
471
|
data.res.end(text);
|
|
414
472
|
return true;
|
|
415
473
|
}
|
|
474
|
+
// --- 加载控制器文件 ---
|
|
416
475
|
const ctrCtr = (await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)))).default;
|
|
417
476
|
cctr = new ctrCtr(config, data.req, data.res ?? data.socket);
|
|
477
|
+
// --- 对信息进行初始化 ---
|
|
418
478
|
cctr.setPrototype('_timer', middle.getPrototype('_timer'));
|
|
419
479
|
cctr.setPrototype('_waitInfo', middle.getPrototype('_waitInfo'));
|
|
480
|
+
// --- 路由定义的参数序列 ---
|
|
420
481
|
cctr.setPrototype('_param', param);
|
|
421
482
|
cctr.setPrototype('_action', middle.getPrototype('_action'));
|
|
422
483
|
cctr.setPrototype('_headers', headers);
|
|
@@ -435,12 +496,15 @@ async function run(data) {
|
|
|
435
496
|
cctr.setPrototype('_xsrf', middle.getPrototype('_xsrf'));
|
|
436
497
|
cctr.setPrototype('_httpCode', middle.getPrototype('_httpCode'));
|
|
437
498
|
await lCore.log(cctr, '', '-visit');
|
|
499
|
+
// --- 强制 HTTPS ---
|
|
438
500
|
if (config.set.mustHttps && !config.const.https) {
|
|
439
501
|
data.res.setHeader('location', data.req.url ?? '');
|
|
440
502
|
data.res.writeHead(302);
|
|
441
503
|
return true;
|
|
442
504
|
}
|
|
505
|
+
// --- 检测 action 是否存在,以及排除内部方法 ---
|
|
443
506
|
if (pathRight.startsWith('_') || pathRight === 'onUpgrade' || pathRight === 'onLoad' || pathRight === 'onData' || pathRight === 'onDrain' || pathRight === 'onClose' || pathRight === 'setPrototype' || pathRight === 'getPrototype' || pathRight === 'getAuthorization') {
|
|
507
|
+
// --- _ 开头的 action 是内部方法,不允许访问 ---
|
|
444
508
|
if (config.route['#404']) {
|
|
445
509
|
data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
|
|
446
510
|
data.res.writeHead(302);
|
|
@@ -471,8 +535,10 @@ async function run(data) {
|
|
|
471
535
|
data.res.end(text);
|
|
472
536
|
return true;
|
|
473
537
|
}
|
|
538
|
+
// --- 执行 onLoad 方法 ---
|
|
474
539
|
try {
|
|
475
540
|
rtn = await cctr.onLoad();
|
|
541
|
+
// --- 执行 action ---
|
|
476
542
|
if (rtn === undefined || rtn === true) {
|
|
477
543
|
rtn = await cctr[pathRight]();
|
|
478
544
|
rtn = await cctr.onUnload(rtn);
|
|
@@ -482,6 +548,7 @@ async function run(data) {
|
|
|
482
548
|
await sess.update();
|
|
483
549
|
}
|
|
484
550
|
}
|
|
551
|
+
// --- 获取 ctr 设置的 cache 和 hcode ---
|
|
485
552
|
cacheTTL = cctr.getPrototype('_cacheTTL');
|
|
486
553
|
httpCode = cctr.getPrototype('_httpCode');
|
|
487
554
|
}
|
|
@@ -495,15 +562,19 @@ async function run(data) {
|
|
|
495
562
|
return true;
|
|
496
563
|
}
|
|
497
564
|
}
|
|
565
|
+
// --- 设置缓存 ---
|
|
498
566
|
if (!data.res.headersSent && (cacheTTL > 0)) {
|
|
499
567
|
data.res.setHeader('expires', lTime.format(0, 'D, d M Y H:i:s', Date.now() + cacheTTL * 1000) + ' GMT');
|
|
500
568
|
data.res.setHeader('cache-control', 'max-age=' + cacheTTL.toString());
|
|
501
569
|
}
|
|
570
|
+
// --- 设置自定义 hcode ---
|
|
502
571
|
if (httpCode === 0) {
|
|
503
572
|
httpCode = 200;
|
|
504
573
|
}
|
|
574
|
+
// --- 判断返回值 ---
|
|
505
575
|
if (rtn === undefined || typeof rtn === 'boolean' || rtn === null) {
|
|
506
576
|
if (data.res.headersSent) {
|
|
577
|
+
// --- 已经自行输出过 writeHead,可能自行处理了内容,如 pipe,则不再 writeHead ---
|
|
507
578
|
}
|
|
508
579
|
else {
|
|
509
580
|
if (data.res.getHeader('location')) {
|
|
@@ -514,12 +585,15 @@ async function run(data) {
|
|
|
514
585
|
}
|
|
515
586
|
}
|
|
516
587
|
if (!data.res.writableEnded) {
|
|
588
|
+
// --- 如果当前还没结束,则强制关闭连接,一切 pipe 请自行在方法中 await,否则会被中断 ---
|
|
517
589
|
data.res.end('');
|
|
518
590
|
}
|
|
519
591
|
await waitCtr(cctr ?? middle);
|
|
520
592
|
return true;
|
|
521
593
|
}
|
|
522
594
|
if (typeof rtn === 'string' || rtn instanceof Buffer) {
|
|
595
|
+
// --- 返回的是纯字符串,直接输出 ---
|
|
596
|
+
/** --- 当前的压缩对象 --- */
|
|
523
597
|
let compress = null;
|
|
524
598
|
if (!data.res.headersSent) {
|
|
525
599
|
if (!data.res.getHeader('content-type')) {
|
|
@@ -545,7 +619,9 @@ async function run(data) {
|
|
|
545
619
|
}
|
|
546
620
|
}
|
|
547
621
|
else if (rtn instanceof stream.Readable || rtn instanceof lResponse.Response) {
|
|
622
|
+
// --- 返回的是流,那就以管道的形式输出 ---
|
|
548
623
|
const stm = rtn instanceof stream.Readable ? rtn : rtn.getStream();
|
|
624
|
+
/** --- 当前的压缩对象 --- */
|
|
549
625
|
let compress = null;
|
|
550
626
|
if (!data.res.headersSent) {
|
|
551
627
|
if (!data.res.getHeader('content-type')) {
|
|
@@ -567,8 +643,11 @@ async function run(data) {
|
|
|
567
643
|
}
|
|
568
644
|
}
|
|
569
645
|
else if (typeof rtn === 'object') {
|
|
646
|
+
// --- 返回的是数组,那么代表可能是 JSON,可能是对象序列 ---
|
|
570
647
|
if (Array.isArray(rtn)) {
|
|
648
|
+
// --- [0, 'xxx'] 模式,或 [string, Readable] 模式 ---
|
|
571
649
|
if (rtn.length === 0) {
|
|
650
|
+
// --- 异常 ---
|
|
572
651
|
if (!data.res.headersSent) {
|
|
573
652
|
data.res.writeHead(500);
|
|
574
653
|
}
|
|
@@ -578,14 +657,17 @@ async function run(data) {
|
|
|
578
657
|
}
|
|
579
658
|
else {
|
|
580
659
|
if (typeof rtn[0] === 'number') {
|
|
660
|
+
// --- 1. ---
|
|
581
661
|
const json = { 'result': rtn[0] };
|
|
582
662
|
if (rtn[1] !== undefined) {
|
|
583
663
|
if (typeof rtn[1] === 'object') {
|
|
664
|
+
// --- [0, ...{'xx': 'xx'}] ---
|
|
584
665
|
for (let i = 1; i < rtn.length; ++i) {
|
|
585
666
|
Object.assign(json, rtn[i]);
|
|
586
667
|
}
|
|
587
668
|
}
|
|
588
669
|
else {
|
|
670
|
+
// --- [0, 'xxx'], [0, 'xxx', ...{'xx': 'xx'}] ---
|
|
589
671
|
json['msg'] = rtn[1];
|
|
590
672
|
for (let i = 2; i < rtn.length; ++i) {
|
|
591
673
|
Object.assign(json, rtn[i]);
|
|
@@ -593,6 +675,7 @@ async function run(data) {
|
|
|
593
675
|
}
|
|
594
676
|
}
|
|
595
677
|
const writeData = lText.stringifyJson(json);
|
|
678
|
+
/** --- 当前的压缩对象 --- */
|
|
596
679
|
let compress = null;
|
|
597
680
|
if (!data.res.headersSent) {
|
|
598
681
|
data.res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
@@ -616,6 +699,8 @@ async function run(data) {
|
|
|
616
699
|
}
|
|
617
700
|
}
|
|
618
701
|
else {
|
|
702
|
+
// --- 2. 字符串与 Readable 的组合 ---
|
|
703
|
+
/** --- 当前的压缩对象 --- */
|
|
619
704
|
let compress = null;
|
|
620
705
|
if (!data.res.headersSent) {
|
|
621
706
|
if (!data.res.getHeader('content-type')) {
|
|
@@ -629,6 +714,7 @@ async function run(data) {
|
|
|
629
714
|
}
|
|
630
715
|
if (!data.res.writableEnded) {
|
|
631
716
|
const passThrough = new stream.PassThrough();
|
|
717
|
+
// --- 先进行 pipe 绑定,之后 write 或 pipe 进 pass 的数据,直接就可以消费 ---
|
|
632
718
|
if (compress) {
|
|
633
719
|
passThrough.pipe(compress.compress).pipe(data.res);
|
|
634
720
|
}
|
|
@@ -641,7 +727,9 @@ async function run(data) {
|
|
|
641
727
|
}
|
|
642
728
|
}
|
|
643
729
|
else {
|
|
730
|
+
// --- 直接是个 json 对象 ---
|
|
644
731
|
const writeData = lText.stringifyJson(rtn);
|
|
732
|
+
/** --- 当前的压缩对象 --- */
|
|
645
733
|
let compress = null;
|
|
646
734
|
if (!data.res.headersSent) {
|
|
647
735
|
data.res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
@@ -666,6 +754,7 @@ async function run(data) {
|
|
|
666
754
|
}
|
|
667
755
|
}
|
|
668
756
|
else {
|
|
757
|
+
// --- 异常 ---
|
|
669
758
|
if (!data.res.headersSent) {
|
|
670
759
|
data.res.writeHead(500);
|
|
671
760
|
}
|
|
@@ -676,6 +765,10 @@ async function run(data) {
|
|
|
676
765
|
await waitCtr(cctr ?? middle);
|
|
677
766
|
return true;
|
|
678
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* --- 获取控制器 left 和 action ---
|
|
770
|
+
* @param path 相对路径
|
|
771
|
+
*/
|
|
679
772
|
function getPathLeftRight(path) {
|
|
680
773
|
const pathLio = path.lastIndexOf('/');
|
|
681
774
|
if (pathLio === -1) {
|
|
@@ -684,6 +777,10 @@ function getPathLeftRight(path) {
|
|
|
684
777
|
const right = path.slice(pathLio + 1);
|
|
685
778
|
return [path.slice(0, pathLio), right === '' ? 'index' : right];
|
|
686
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* --- 根据 path 获取 ws 的控制器类名, Mutton: false, Kebab: true ---
|
|
782
|
+
* @param path 路径
|
|
783
|
+
*/
|
|
687
784
|
function getWsCtrName(path) {
|
|
688
785
|
const pathLio = path.lastIndexOf('/');
|
|
689
786
|
if (pathLio === -1) {
|
|
@@ -691,6 +788,10 @@ function getWsCtrName(path) {
|
|
|
691
788
|
}
|
|
692
789
|
return path.slice(pathLio + 1).toLowerCase();
|
|
693
790
|
}
|
|
791
|
+
/**
|
|
792
|
+
* --- 删除本次请求所有已上传的临时文件, Mutton: false, Kebab: true ---
|
|
793
|
+
* @param cctr Ctr 对象 或 files
|
|
794
|
+
*/
|
|
694
795
|
async function unlinkUploadFiles(cctr) {
|
|
695
796
|
const cfiles = cctr instanceof sCtr.Ctr ? cctr.getPrototype('_files') : cctr;
|
|
696
797
|
for (const name in cfiles) {
|
|
@@ -703,18 +804,31 @@ async function unlinkUploadFiles(cctr) {
|
|
|
703
804
|
}
|
|
704
805
|
}
|
|
705
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* --- 等待异步任务结束,并删除临时文件,如果结束后还有事务没关闭,则会在本函数中打印控制台并且写入 log 文件 ---
|
|
809
|
+
* --- 此时其实已经给客户端返回了,此处等待不消耗客户端的等待时间 ---
|
|
810
|
+
* @param cctr 要等待的控制器 ---
|
|
811
|
+
*/
|
|
706
812
|
async function waitCtr(cctr) {
|
|
813
|
+
// --- 先判断异步任务是否结束 ---
|
|
707
814
|
const waitInfo = cctr.getPrototype('_waitInfo');
|
|
708
815
|
if (waitInfo.asyncTask.count) {
|
|
709
816
|
await waitInfo.asyncTask.callback();
|
|
710
817
|
}
|
|
818
|
+
// --- 判断事务 ---
|
|
711
819
|
if (waitInfo.transaction) {
|
|
820
|
+
// --- 有事务未关闭 ---
|
|
712
821
|
const msg = 'transaction(' + waitInfo.transaction + ') not be closed';
|
|
713
822
|
lCore.display('[ERROR][ROUTE][WAITCTR] ' + msg + ': ', cctr.getPrototype('_config').const.path);
|
|
714
823
|
await lCore.log(cctr, msg, '-error');
|
|
715
824
|
}
|
|
825
|
+
// --- 彻底结束,删除文件 ---
|
|
716
826
|
await unlinkUploadFiles(cctr);
|
|
717
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* --- 将 POST 数据的值执行 trim ---
|
|
830
|
+
* @param post
|
|
831
|
+
*/
|
|
718
832
|
function trimPost(post) {
|
|
719
833
|
for (const key in post) {
|
|
720
834
|
const val = post[key];
|
|
@@ -726,6 +840,10 @@ function trimPost(post) {
|
|
|
726
840
|
}
|
|
727
841
|
}
|
|
728
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* --- 内部使用,获取 post 对象,如果是文件上传(formdata)的情况则不获取 ---
|
|
845
|
+
* @param req 请求对象
|
|
846
|
+
*/
|
|
729
847
|
function getPost(req) {
|
|
730
848
|
return new Promise(function (resolve) {
|
|
731
849
|
const ct = req.headers['content-type'] ?? '';
|
|
@@ -733,6 +851,7 @@ function getPost(req) {
|
|
|
733
851
|
resolve(['', {}]);
|
|
734
852
|
return;
|
|
735
853
|
}
|
|
854
|
+
// --- json 或普通 post ---
|
|
736
855
|
let buffer = Buffer.from('');
|
|
737
856
|
req.on('data', function (chunk) {
|
|
738
857
|
buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
|
|
@@ -743,6 +862,7 @@ function getPost(req) {
|
|
|
743
862
|
resolve(['', {}]);
|
|
744
863
|
return;
|
|
745
864
|
}
|
|
865
|
+
// --- 判断 json 还是普通 ---
|
|
746
866
|
if (ct.includes('json')) {
|
|
747
867
|
try {
|
|
748
868
|
resolve([s, lText.parseJson(s)]);
|
|
@@ -756,6 +876,11 @@ function getPost(req) {
|
|
|
756
876
|
});
|
|
757
877
|
});
|
|
758
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* --- 获取 formdata 的 post ---
|
|
881
|
+
* @param req 请求头
|
|
882
|
+
* @param events 文件处理情况
|
|
883
|
+
*/
|
|
759
884
|
function getFormData(req, events = {}) {
|
|
760
885
|
return new Promise(function (resolve) {
|
|
761
886
|
const ct = req.headers['content-type'] ?? '';
|
|
@@ -763,54 +888,80 @@ function getFormData(req, events = {}) {
|
|
|
763
888
|
resolve({ 'post': {}, 'files': {} });
|
|
764
889
|
return;
|
|
765
890
|
}
|
|
891
|
+
/** --- boundary 位置 --- */
|
|
766
892
|
const clio = ct.lastIndexOf('boundary=');
|
|
767
893
|
if (clio === -1) {
|
|
768
894
|
resolve({ 'post': {}, 'files': {} });
|
|
769
895
|
return;
|
|
770
896
|
}
|
|
897
|
+
/** --- 最终返回 --- */
|
|
771
898
|
const rtn = {
|
|
772
899
|
'post': {},
|
|
773
900
|
'files': {}
|
|
774
901
|
};
|
|
902
|
+
/** --- 获取的 boundary 文本 --- */
|
|
775
903
|
const boundary = ct.slice(clio + 9);
|
|
904
|
+
// 一下变量是相对于 data 事件的全局变量 ---
|
|
905
|
+
/** --- 当前临时读入的还未处理的 buffer --- */
|
|
776
906
|
let buffer = Buffer.from('');
|
|
907
|
+
/** --- 状态机 --- */
|
|
777
908
|
let EState;
|
|
778
909
|
(function (EState) {
|
|
910
|
+
/** --- 判断头部是什么 --- */
|
|
779
911
|
EState[EState["WAIT"] = 0] = "WAIT";
|
|
912
|
+
/** --- 当前是文件 --- */
|
|
780
913
|
EState[EState["FILE"] = 1] = "FILE";
|
|
914
|
+
/** --- 当前是 post 数据 --- */
|
|
781
915
|
EState[EState["POST"] = 2] = "POST";
|
|
782
916
|
})(EState || (EState = {}));
|
|
917
|
+
/** --- 当前所处状态 --- */
|
|
783
918
|
let state = EState.WAIT;
|
|
919
|
+
/** --- 当前处理中的 post 的 name --- */
|
|
784
920
|
let name = '';
|
|
921
|
+
/** --- 当前处理中的 file 的 name --- */
|
|
785
922
|
let fileName = '';
|
|
923
|
+
/** --- 当前处理中的临时文件名 --- */
|
|
786
924
|
let ftmpName = '';
|
|
925
|
+
/** --- 当前正在写入的临时文件流 --- */
|
|
787
926
|
let ftmpStream;
|
|
927
|
+
/** --- 当前临时文件已写入的流大小 --- */
|
|
788
928
|
let ftmpSize = 0;
|
|
929
|
+
/** --- 当前正在写入的文件数量 --- */
|
|
789
930
|
let writeFileLength = 0;
|
|
931
|
+
/** --- 当前读取是否已经完全结束 --- */
|
|
790
932
|
let readEnd = false;
|
|
933
|
+
// --- 开始读取 ---
|
|
791
934
|
req.on('data', function (chunk) {
|
|
792
935
|
buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
|
|
793
936
|
while (true) {
|
|
794
937
|
switch (state) {
|
|
795
938
|
case EState.WAIT: {
|
|
939
|
+
/** --- 中断符位置 --- */
|
|
796
940
|
const io = buffer.indexOf('\r\n\r\n');
|
|
797
941
|
if (io === -1) {
|
|
798
942
|
return;
|
|
799
943
|
}
|
|
944
|
+
// --- 头部已经读取完毕 ---
|
|
800
945
|
const head = buffer.subarray(0, io).toString();
|
|
946
|
+
// --- 除头部外剩下的 buffer ---
|
|
801
947
|
buffer = buffer.subarray(io + 4);
|
|
948
|
+
// --- 获取 name ---
|
|
802
949
|
let match = /name="(.+?)"/.exec(head);
|
|
803
950
|
name = match ? match[1] : '';
|
|
951
|
+
// --- 判断是 post 还是文件 ---
|
|
804
952
|
match = /filename="(.+?)"/.exec(head);
|
|
805
953
|
if (!match) {
|
|
954
|
+
// --- 普通 post ---
|
|
806
955
|
state = EState.POST;
|
|
807
956
|
}
|
|
808
957
|
else {
|
|
958
|
+
// --- 文件 ---
|
|
809
959
|
++writeFileLength;
|
|
810
960
|
state = EState.FILE;
|
|
811
961
|
fileName = match[1];
|
|
812
962
|
const fr = events.onfilestart?.(name);
|
|
813
963
|
if (fr !== true) {
|
|
964
|
+
// --- 创建文件流 ---
|
|
814
965
|
const date = new Date();
|
|
815
966
|
ftmpName = date.getUTCFullYear().toString() +
|
|
816
967
|
(date.getUTCMonth() + 1).toString().padStart(2, '0') +
|
|
@@ -827,10 +978,12 @@ function getFormData(req, events = {}) {
|
|
|
827
978
|
break;
|
|
828
979
|
}
|
|
829
980
|
case EState.POST: {
|
|
981
|
+
// --- POST 模式 ---
|
|
830
982
|
const io = buffer.indexOf('\r\n--' + boundary);
|
|
831
983
|
if (io === -1) {
|
|
832
984
|
return;
|
|
833
985
|
}
|
|
986
|
+
// --- 找到结束标语,写入 POST ---
|
|
834
987
|
const val = buffer.subarray(0, io).toString();
|
|
835
988
|
if (rtn.post[name]) {
|
|
836
989
|
if (Array.isArray(rtn.post[name])) {
|
|
@@ -843,13 +996,16 @@ function getFormData(req, events = {}) {
|
|
|
843
996
|
else {
|
|
844
997
|
rtn.post[name] = val;
|
|
845
998
|
}
|
|
999
|
+
// --- 重置状态机 ---
|
|
846
1000
|
state = EState.WAIT;
|
|
847
1001
|
buffer = buffer.subarray(io + 4 + boundary.length);
|
|
848
1002
|
break;
|
|
849
1003
|
}
|
|
850
1004
|
case EState.FILE: {
|
|
1005
|
+
// --- FILE 模式 ---
|
|
851
1006
|
const io = buffer.indexOf('\r\n--' + boundary);
|
|
852
1007
|
if (io === -1) {
|
|
1008
|
+
// --- 没找到结束标语,将预留 boundary 长度之前的写入到文件 ---
|
|
853
1009
|
const writeBuffer = buffer.subarray(0, -boundary.length - 4);
|
|
854
1010
|
if (ftmpName === '') {
|
|
855
1011
|
events.onfiledata?.(writeBuffer);
|
|
@@ -861,6 +1017,7 @@ function getFormData(req, events = {}) {
|
|
|
861
1017
|
buffer = buffer.subarray(-boundary.length - 4);
|
|
862
1018
|
return;
|
|
863
1019
|
}
|
|
1020
|
+
// --- 找到结束标语,结束标语之前的写入文件,之后的重新放回 buffer ---
|
|
864
1021
|
const writeBuffer = buffer.subarray(0, io);
|
|
865
1022
|
if (ftmpName === '') {
|
|
866
1023
|
events.onfileend?.();
|
|
@@ -871,13 +1028,17 @@ function getFormData(req, events = {}) {
|
|
|
871
1028
|
ftmpStream.end(() => {
|
|
872
1029
|
--writeFileLength;
|
|
873
1030
|
if (!readEnd) {
|
|
1031
|
+
// --- request 没读完,不管 ---
|
|
874
1032
|
return;
|
|
875
1033
|
}
|
|
876
1034
|
if (writeFileLength) {
|
|
1035
|
+
// --- req 读完了但文件还没写完,不管 ---
|
|
877
1036
|
return;
|
|
878
1037
|
}
|
|
1038
|
+
// --- 文件也写完了 ---
|
|
879
1039
|
resolve(rtn);
|
|
880
1040
|
});
|
|
1041
|
+
// --- POST 部分 ---
|
|
881
1042
|
let fname = fileName.replace(/\\/g, '/');
|
|
882
1043
|
const nlio = fname.lastIndexOf('/');
|
|
883
1044
|
if (nlio !== -1) {
|
|
@@ -901,6 +1062,7 @@ function getFormData(req, events = {}) {
|
|
|
901
1062
|
rtn.files[name] = val;
|
|
902
1063
|
}
|
|
903
1064
|
}
|
|
1065
|
+
// --- 重置状态机 ---
|
|
904
1066
|
state = EState.WAIT;
|
|
905
1067
|
buffer = buffer.subarray(io + 4 + boundary.length);
|
|
906
1068
|
break;
|
|
@@ -914,8 +1076,10 @@ function getFormData(req, events = {}) {
|
|
|
914
1076
|
req.on('end', function () {
|
|
915
1077
|
readEnd = true;
|
|
916
1078
|
if (writeFileLength) {
|
|
1079
|
+
// --- 文件没写完 ---
|
|
917
1080
|
return;
|
|
918
1081
|
}
|
|
1082
|
+
// --- 文件写完了 ----
|
|
919
1083
|
resolve(rtn);
|
|
920
1084
|
});
|
|
921
1085
|
});
|