@maiyunnet/kebab 2.0.3 → 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/sys/route.ts DELETED
@@ -1,1113 +0,0 @@
1
- /**
2
- * Project: Kebab, User: JianSuoQiYue
3
- * Date: 2019-4-15 13:40
4
- * 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
5
- */
6
- import * as http from 'http';
7
- import * as http2 from 'http2';
8
- import * as net from 'net';
9
- import * as fs from 'fs';
10
- import * as stream from 'stream';
11
- // --- 第三方 ---
12
- import * as ws from '@litert/websocket';
13
- // --- 库和定义 ---
14
- import * as lFs from '~/lib/fs';
15
- import * as lZlib from '~/lib/zlib';
16
- import * as lCore from '~/lib/core';
17
- import * as lText from '~/lib/text';
18
- import * as lTime from '~/lib/time';
19
- import * as lResponse from '~/lib/net/response';
20
- import * as lWs from '~/lib/ws';
21
- import * as sCtr from './ctr';
22
- import * as kebab from '~/index';
23
- import * as types from '~/types';
24
-
25
- /** --- 动态层 kebab.json 缓存(文件路径: 最终合并值) --- */
26
- let kebabConfigs: Record<string, types.IConfig> = {};
27
-
28
- /**
29
- * --- 清除已经加载的虚拟主机配置文件 ---
30
- */
31
- export function clearKebabConfigs(): void {
32
- kebabConfigs = {};
33
- }
34
-
35
- /**
36
- * --- 若为动态路径则执行此函数,此函数不进行判断 kebab.json 是否存在 ---
37
- * @param data 传导的数据
38
- */
39
- export async function run(data: {
40
- 'req': http2.Http2ServerRequest | http.IncomingMessage;
41
- 'res'?: http2.Http2ServerResponse | http.ServerResponse;
42
- 'socket'?: net.Socket;
43
- 'uri': types.IUrlParse;
44
- /** --- 虚拟主机当前动态目录的绝对根目录,末尾带 / --- */
45
- 'rootPath': string;
46
- /** --- base url,如 /abc/vhost/,前后都带 / --- */
47
- 'urlBase': string;
48
- /** --- 前面不带 /,末尾不一定,以用户请求为准 --- */
49
- 'path': string;
50
- /** --- timeout timer --- */
51
- 'timer'?: {
52
- 'timer': NodeJS.Timeout;
53
- 'timeout': number;
54
- 'callback': () => void;
55
- };
56
- }): Promise<boolean> {
57
- // --- 检测 path 是否是静态文件 ---
58
- if (/^(stc\/.*|favicon.\w+?\??.*|apple[\w-]+?\.png\??.*|[\w-]+?\.txt\??.*|[\w-]+?\.html\??.*)/.test(data.path)) {
59
- return false;
60
- }
61
- // --- 根据 res 还是 socket 进行初始化设置 ---
62
- if (data.res) {
63
- data.res.setHeader('expires', 'Mon, 26 Jul 1994 05:00:00 GMT');
64
- data.res.setHeader('cache-control', 'no-store');
65
- }
66
- // --- 判断 kebab config 是否已经读取过 ---
67
- if (!kebabConfigs[data.rootPath + 'kebab.json']) {
68
- const configContent = await lFs.getContent(data.rootPath + 'kebab.json', 'utf8');
69
- if (!configContent) {
70
- if (data.res) {
71
- data.res.setHeader('content-length', 53);
72
- data.res.writeHead(500);
73
- data.res.end('<h1>500 File kebab.json can not be read</h1><hr>Kebab');
74
- }
75
- else {
76
- data.socket?.destroy();
77
- }
78
- return true;
79
- }
80
- kebabConfigs[data.rootPath + 'kebab.json'] = lText.parseJson(configContent);
81
- const routeContent = await lFs.getContent(data.rootPath + 'route.json', 'utf8');
82
- if (routeContent) {
83
- kebabConfigs[data.rootPath + 'kebab.json'].route = lText.parseJson(routeContent);
84
- }
85
- // --- 将全局的项目应用到 vhostConfigs 里,但当 vhostConfigs 有项,则不应用 ---
86
- for (const name in lCore.globalConfig) {
87
- if (typeof lCore.globalConfig[name] !== 'object') {
88
- if (kebabConfigs[data.rootPath + 'kebab.json'][name] === undefined) {
89
- kebabConfigs[data.rootPath + 'kebab.json'][name] = lCore.globalConfig[name];
90
- }
91
- continue;
92
- }
93
- for (const name2 in lCore.globalConfig[name]) {
94
- if (kebabConfigs[data.rootPath + 'kebab.json'][name] === undefined) {
95
- kebabConfigs[data.rootPath + 'kebab.json'][name] = {};
96
- }
97
- if (kebabConfigs[data.rootPath + 'kebab.json'][name][name2] === undefined) {
98
- kebabConfigs[data.rootPath + 'kebab.json'][name][name2] = lCore.globalConfig[name][name2];
99
- }
100
- }
101
- }
102
- }
103
- // --- 加载 kebab config ---
104
- const config: types.IConfig = {} as types.Json;
105
- const configData = kebabConfigs[data.rootPath + 'kebab.json'];
106
- for (const name in configData) {
107
- config[name] = configData[name];
108
- }
109
- config.const = {
110
- 'path': data.path,
111
- 'qs': data.uri.query ?? '',
112
- 'startTime': process.hrtime.bigint(),
113
- 'startMemory': process.memoryUsage().rss,
114
-
115
- // --- 环境判断 ---
116
-
117
- 'mobile': data.req.headers['user-agent'] ? data.req.headers['user-agent'].includes('mobile') : false,
118
- 'wechat': data.req.headers['user-agent'] ? data.req.headers['user-agent'].includes('micromessenger') : false,
119
- 'https': data.uri.protocol === 'https' ? true : false,
120
- 'host': data.uri.host ?? '',
121
- 'hostname': data.uri.hostname ?? '',
122
- 'hostport': data.uri.port ? parseInt(data.uri.port) : (data.uri.protocol === 'https' ? 443 : 80),
123
- 'uri': data.uri,
124
-
125
- // --- 服务端用的路径 ---
126
-
127
- 'rootPath': data.rootPath,
128
- 'ctrPath': data.rootPath + 'ctr/',
129
- 'viewPath': data.rootPath + 'view/',
130
- 'dataPath': data.rootPath + 'data/',
131
- 'modPath': data.rootPath + 'mod/',
132
- 'wsPath': data.rootPath + 'ws/',
133
-
134
- // --- 前端用的路径 ---
135
-
136
- 'urlBase': data.urlBase,
137
- 'urlStc': data.urlBase + 'stc/',
138
- 'urlFull': (data.uri.protocol ?? '') + '//' + (data.uri.host ?? '') + data.urlBase,
139
- 'urlStcFull': ''
140
- };
141
- config.const.urlStcFull = config.const.urlFull + 'stc/';
142
- if (!config.set.staticPath) {
143
- config.set.staticPath = config.const.urlStc;
144
- }
145
- if (!config.set.staticPathFull) {
146
- config.set.staticPathFull = config.const.urlStcFull;
147
- }
148
- // --- data.path 是安全的,不会是 ../../ 来访问到了外层,已经做过处理 ---
149
- let path = data.path;
150
- // --- 如果为空则定义为 @ ---
151
- if (path === '') {
152
- path = '@';
153
- }
154
- // --- 检测是否托管语言页面 ---
155
- const param: string[] = [];
156
- if (config.lang && data.res) {
157
- // --- 仅仅 res 模式支持 config.lang ---
158
- if (path[2] !== '/') {
159
- // --- 不管是首页还是 @ 页,都会到此处 ---
160
- let isDirect = false;
161
- if (!path) {
162
- if (config.lang.direct.includes('@')) {
163
- isDirect = true;
164
- }
165
- }
166
- else {
167
- for (const ditem of config.lang.direct) {
168
- if (!path.startsWith(ditem)) {
169
- continue;
170
- }
171
- // --- 放行 ---
172
- isDirect = true;
173
- break;
174
- }
175
- }
176
- if (!isDirect) {
177
- // --- 不放行 ---
178
- const alang = data.req.headers['accept-language']?.toLowerCase() ?? config.lang.list[0];
179
- const apath = config.const.path + (config.const.qs ? '?' + config.const.qs : '');
180
- for (const lang of config.lang.list) {
181
- let checkLang = lang;
182
- if (lang === 'sc') {
183
- checkLang = 'cn';
184
- }
185
- else if (lang === 'tc') {
186
- checkLang = 'zh';
187
- }
188
- if (!alang.includes(checkLang)) {
189
- continue;
190
- }
191
- data.res.setHeader('location', config.const.urlBase + lang + '/' + apath);
192
- data.res.writeHead(302);
193
- data.res.end('');
194
- return true;
195
- }
196
- data.res.setHeader('location', config.const.urlBase + config.lang.list[0] + '/' + apath);
197
- data.res.writeHead(302);
198
- data.res.end('');
199
- return true;
200
- }
201
- }
202
- else {
203
- // --- 已经是含语言的路径了 --
204
- param.push(path.slice(0, 2));
205
- path = path.slice(3);
206
- if (!path) {
207
- path = '@';
208
- }
209
- }
210
- }
211
- // --- 检查路由表 ---
212
- let match: RegExpExecArray | null = null;
213
- let pathLeft: string = '', pathRight: string = '';
214
- for (const rule in config.route) {
215
- const ruleVal = config.route[rule];
216
- if ((match = (new RegExp('^' + rule + '$')).exec(path))) {
217
- if (data.res) {
218
- [pathLeft, pathRight] = getPathLeftRight(ruleVal);
219
- }
220
- else {
221
- pathLeft = getWsCtrName(ruleVal);
222
- }
223
- for (let i = 1; i < match.length; ++i) {
224
- param.push(match[i]);
225
- }
226
- break;
227
- }
228
- }
229
- if (!match) {
230
- if (data.res) {
231
- [pathLeft, pathRight] = getPathLeftRight(path);
232
- }
233
- else {
234
- pathLeft = getWsCtrName(path);
235
- }
236
- }
237
- // --- 若文件名为保留的 middle 将不允许进行 ---
238
- if (pathLeft.startsWith('middle')) {
239
- const text = '[Error] Controller not found, path: ' + path + '.';
240
- if (data.res) {
241
- if (config.route['#404']) {
242
- data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
243
- data.res.writeHead(302);
244
- data.res.end('');
245
- return true;
246
- }
247
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
248
- data.res.setHeader('content-length', Buffer.byteLength(text));
249
- data.res.writeHead(404);
250
- data.res.end(text);
251
- }
252
- else {
253
- data.socket?.destroy();
254
- }
255
- return true;
256
- }
257
-
258
- // --- 原始 GET ---
259
- const get = data.uri.query ? lText.queryParse(data.uri.query) : {};
260
- // --- Cookie ---
261
- const cookies: Record<string, string> = {};
262
- if (data.req.headers['cookie']) {
263
- const hcookies: string[] = data.req.headers['cookie'].split(';');
264
- for (const cookie of hcookies) {
265
- const co: string[] = cookie.split('=');
266
- cookies[co[0].trim()] = decodeURIComponent(co[1]);
267
- }
268
- }
269
- // --- 处理 headers ---
270
- const headers: http.IncomingHttpHeaders = {};
271
- for (const key in data.req.headers) {
272
- headers[key.toLowerCase()] = data.req.headers[key];
273
- }
274
- headers['authorization'] ??= '';
275
- /** --- 开发者返回值 --- */
276
- let rtn: types.Json;
277
-
278
- if (data.socket && data.req instanceof http.IncomingMessage) {
279
- // --- socket 模式,判断真实控制器文件是否存在 ---
280
- const filePath = config.const.wsPath + pathLeft + '.js';
281
- if (!await lFs.isFile(filePath)) {
282
- // --- 指定的控制器不存在 ---
283
- data.socket?.destroy();
284
- return true;
285
- }
286
- // --- 加载控制器文件 ---
287
- const ctrCtr: typeof sCtr.Ctr = (await import(filePath)).default;
288
- const cctr: sCtr.Ctr = new ctrCtr(config, data.req);
289
- // --- 先处理 web socket 的情况 ---
290
- let wsSocket: lWs.Socket;
291
- try {
292
- const options = await (cctr as types.Json).onUpgrade();
293
- wsSocket = lWs.createServer(data.req, data.socket, options);
294
- cctr.setPrototype('_socket', wsSocket);
295
- }
296
- catch (e: types.Json) {
297
- await lCore.log(cctr, lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
298
- data.socket.destroy();
299
- return true;
300
- }
301
- cctr.setPrototype('_param', param);
302
- cctr.setPrototype('_action', pathRight);
303
- cctr.setPrototype('_headers', headers);
304
- cctr.setPrototype('_get', get);
305
- cctr.setPrototype('_cookie', cookies);
306
-
307
- await lCore.log(cctr, '', '-visit');
308
-
309
- try {
310
- rtn = await (cctr as types.Json).onLoad();
311
- }
312
- catch (e: types.Json) {
313
- await lCore.log(cctr, lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
314
- data.socket.destroy();
315
- return true;
316
- }
317
- if (rtn === undefined || rtn === true) {
318
- await new Promise<void>(function(resolve) {
319
- wsSocket.on('message', async function(msg): Promise<void> {
320
- switch (msg.opcode) {
321
- case ws.EOpcode.CLOSE: {
322
- const r = await (cctr as types.Json)['onMessage'](msg.data, msg.opcode);
323
- if (r === false) {
324
- break;
325
- }
326
- wsSocket.end();
327
- break;
328
- }
329
- case ws.EOpcode.PING: {
330
- const r = await (cctr as types.Json)['onMessage'](msg.data, msg.opcode);
331
- if (r === false) {
332
- break;
333
- }
334
- wsSocket.pong();
335
- break;
336
- }
337
- case ws.EOpcode.BINARY:
338
- case ws.EOpcode.TEXT: {
339
- try {
340
- const r = await (cctr as types.Json)['onMessage'](msg.data, msg.opcode);
341
- if (r === false) {
342
- break;
343
- }
344
- const wrtn = await (cctr as types.Json)['onData'](msg.data, msg.opcode);
345
- if (wrtn === false) {
346
- wsSocket.end();
347
- return;
348
- }
349
- if (!wrtn) {
350
- return;
351
- }
352
- if (wrtn instanceof Buffer) {
353
- wsSocket.writeBinary(wrtn);
354
- return;
355
- }
356
- if (typeof wrtn === 'string') {
357
- if (!wrtn) {
358
- return;
359
- }
360
- wsSocket.writeText(wrtn);
361
- return;
362
- }
363
- if (typeof wrtn === 'object') {
364
- // --- 返回的是数组,那么代表可能是 JSON,可能是对象序列 ---
365
- wsSocket.writeResult(wrtn);
366
- return;
367
- }
368
- }
369
- catch (e: any) {
370
- await lCore.log(cctr, lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
371
- }
372
- break;
373
- }
374
- default: {
375
- // --- nothing ---
376
- }
377
- }
378
- }).on('drain', async () => {
379
- try {
380
- await (cctr as types.Json)['onDrain']();
381
- }
382
- catch (e: any) {
383
- await lCore.log(cctr, lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
384
- }
385
- }).on('error', async (e: any) => {
386
- await lCore.log(cctr, lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
387
- }).on('close', async () => {
388
- try {
389
- await (cctr as types.Json)['onClose']();
390
- }
391
- catch (e: any) {
392
- await lCore.log(cctr, lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
393
- }
394
- resolve();
395
- });
396
- });
397
- return true;
398
- }
399
- if (!wsSocket.ended) {
400
- wsSocket.end();
401
- }
402
- return true;
403
- }
404
- if (!data.res) {
405
- return true;
406
- }
407
- // --- 加载中间控制器 ---
408
- const middleCtr = (await import(config.const.ctrPath + 'middle')).default as typeof sCtr.Ctr;
409
- const middle: sCtr.Ctr = new middleCtr(config, data.req, data.res);
410
- /** --- 可能存在的最终控制器 --- */
411
- let cctr: sCtr.Ctr | null = null;
412
- // --- 对信息进行初始化 ---
413
- if (data.timer) {
414
- middle.setPrototype('_timer', data.timer);
415
- }
416
- // --- 路由定义的参数序列 ---
417
- middle.setPrototype('_param', param);
418
- // --- action 名 ---
419
- middle.setPrototype('_action', pathRight);
420
- middle.setPrototype('_headers', headers);
421
-
422
- middle.setPrototype('_get', get);
423
-
424
- const rawPost = await getPost(data.req);
425
- // --- 原始 POST ---
426
- middle.setPrototype('_rawPost', rawPost[1]);
427
- // --- 原始 input ---
428
- middle.setPrototype('_input', rawPost[0]);
429
- // --- 处理 POST 的值 JSON 上面就同时处理了 ---
430
- // --- 格式化 post 数据 ---
431
- // --- assign 是为了创建一份拷贝 ---
432
- const post = Object.assign({}, rawPost[1]);
433
- trimPost(post);
434
- middle.setPrototype('_post', post);
435
- // --- form data 格式交由用户自行获取,可以直接获取文件流,然后做他想做的事情 ---
436
-
437
- // --- 执行中间控制器的 onLoad ---
438
- try {
439
- rtn = await (middle.onLoad() as types.Json);
440
- }
441
- catch (e: types.Json) {
442
- await lCore.log(middle, '(E03)' + lText.stringifyJson((e.stack as string)).slice(1, -1), '-error');
443
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
444
- data.res.setHeader('content-length', 25);
445
- data.res.writeHead(500);
446
- data.res.end('<h1>500 Server Error</h1><hr>Kebab');
447
- return true;
448
- }
449
- let cacheTTL: number = middle.getPrototype('_cacheTTL');
450
- let httpCode: number = middle.getPrototype('_httpCode');
451
- if (rtn === undefined || rtn === true) {
452
- // --- 只有不返回或返回 true 时才加载控制文件 ---
453
- // --- 判断真实控制器文件是否存在 ---
454
- const filePath = config.const.ctrPath + pathLeft + '.js';
455
- if (!await lFs.isFile(filePath)) {
456
- // --- 指定的控制器不存在 ---
457
- const text = '[Error] Controller not found, path: ' + path + '.';
458
- if (config.route['#404']) {
459
- data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
460
- data.res.writeHead(302);
461
- data.res.end('');
462
- return true;
463
- }
464
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
465
- data.res.setHeader('content-length', Buffer.byteLength(text));
466
- data.res.writeHead(404);
467
- data.res.end(text);
468
- return true;
469
- }
470
- // --- 加载控制器文件 ---
471
- const ctrCtr: typeof sCtr.Ctr = (await import(filePath)).default;
472
- cctr = new ctrCtr(config, data.req, data.res ?? data.socket!);
473
- // --- 对信息进行初始化 ---
474
- cctr.setPrototype('_timer', middle.getPrototype('_timer'));
475
- cctr.setPrototype('_waitInfo', middle.getPrototype('_waitInfo'));
476
- // --- 路由定义的参数序列 ---
477
- cctr.setPrototype('_param', param);
478
- cctr.setPrototype('_action', middle.getPrototype('_action'));
479
- cctr.setPrototype('_headers', headers);
480
-
481
- cctr.setPrototype('_get', get);
482
- cctr.setPrototype('_rawPost', middle.getPrototype('_rawPost'));
483
- cctr.setPrototype('_input', middle.getPrototype('_input'));
484
- cctr.setPrototype('_files', middle.getPrototype('_files'));
485
- cctr.setPrototype('_post', middle.getPrototype('_post'));
486
-
487
- cctr.setPrototype('_cookie', cookies);
488
- cctr.setPrototype('_jwt', middle.getPrototype('_jwt'));
489
- if (!cctr.getPrototype('_sess') && middle.getPrototype('_sess')) {
490
- cctr.setPrototype('_session', middle.getPrototype('_session'));
491
- cctr.setPrototype('_sess', middle.getPrototype('_sess'));
492
- }
493
-
494
- cctr.setPrototype('_cacheTTL', middle.getPrototype('_cacheTTL'));
495
- cctr.setPrototype('_xsrf', middle.getPrototype('_xsrf'));
496
- cctr.setPrototype('_httpCode', middle.getPrototype('_httpCode'));
497
-
498
- await lCore.log(cctr, '', '-visit');
499
-
500
- // --- 强制 HTTPS ---
501
- if (config.set.mustHttps && !config.const.https) {
502
- data.res.setHeader('location', data.req.url ?? '');
503
- data.res.writeHead(302);
504
- return true;
505
- }
506
- // --- 检测 action 是否存在,以及排除内部方法 ---
507
- if (pathRight.startsWith('_') || pathRight === 'onUpgrade' || pathRight === 'onLoad' || pathRight === 'onData' || pathRight === 'onDrain' || pathRight === 'onClose' || pathRight === 'setPrototype' || pathRight === 'getPrototype' || pathRight === 'getAuthorization') {
508
- // --- _ 开头的 action 是内部方法,不允许访问 ---
509
- if (config.route['#404']) {
510
- data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
511
- data.res.writeHead(302);
512
- data.res.end('');
513
- return true;
514
- }
515
- const text = '[Error] Action not found, path: ' + path + '.';
516
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
517
- data.res.setHeader('content-length', Buffer.byteLength(text));
518
- data.res.writeHead(404);
519
- data.res.end(text);
520
- return true;
521
- }
522
- pathRight = pathRight.replace(/-([a-zA-Z0-9])/g, function(t, t1: string): string {
523
- return t1.toUpperCase();
524
- });
525
- if ((cctr as types.Json)[pathRight] === undefined) {
526
- if (config.route['#404']) {
527
- data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
528
- data.res.writeHead(302);
529
- data.res.end('');
530
- return true;
531
- }
532
- const text = '[Error] Action not found, path: ' + path + '.';
533
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
534
- data.res.setHeader('content-length', Buffer.byteLength(text));
535
- data.res.writeHead(404);
536
- data.res.end(text);
537
- return true;
538
- }
539
- // --- 执行 onLoad 方法 ---
540
- try {
541
- rtn = await (cctr.onLoad() as types.Json);
542
- // --- 执行 action ---
543
- if (rtn === undefined || rtn === true) {
544
- rtn = await (cctr as types.Json)[pathRight]();
545
- rtn = await (cctr.onUnload(rtn) as types.Json);
546
- rtn = await (middle.onUnload(rtn) as types.Json);
547
- const sess = cctr.getPrototype('_sess');
548
- if (sess) {
549
- await sess.update();
550
- }
551
- }
552
- // --- 获取 ctr 设置的 cache 和 hcode ---
553
- cacheTTL = cctr.getPrototype('_cacheTTL');
554
- httpCode = cctr.getPrototype('_httpCode');
555
- }
556
- catch (e: types.Json) {
557
- await lCore.log(cctr, '(E04)' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
558
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
559
- data.res.setHeader('content-length', 25);
560
- data.res.writeHead(500);
561
- data.res.end('<h1>500 Server Error</h1><hr>Kebab');
562
- await waitCtr(cctr);
563
- return true;
564
- }
565
- }
566
- // --- 设置缓存 ---
567
- if (!data.res.headersSent && (cacheTTL > 0)) {
568
- data.res.setHeader('expires', lTime.format(0, 'D, d M Y H:i:s', Date.now() + cacheTTL * 1000) + ' GMT');
569
- data.res.setHeader('cache-control', 'max-age=' + cacheTTL.toString());
570
- }
571
- // --- 设置自定义 hcode ---
572
- if (httpCode === 0) {
573
- httpCode = 200;
574
- }
575
- // --- 判断返回值 ---
576
- if (rtn === undefined || typeof rtn === 'boolean' || rtn === null) {
577
- if (data.res.headersSent) {
578
- // --- 已经自行输出过 writeHead,可能自行处理了内容,如 pipe,则不再 writeHead ---
579
- }
580
- else {
581
- if (data.res.getHeader('location')) {
582
- data.res.writeHead(302);
583
- }
584
- else {
585
- data.res.writeHead(httpCode);
586
- }
587
- }
588
- if (!data.res.writableEnded) {
589
- // --- 如果当前还没结束,则强制关闭连接,一切 pipe 请自行在方法中 await,否则会被中断 ---
590
- data.res.end('');
591
- }
592
- await waitCtr(cctr ?? middle);
593
- return true;
594
- }
595
- if (typeof rtn === 'string' || rtn instanceof Buffer) {
596
- // --- 返回的是纯字符串,直接输出 ---
597
- /** --- 当前的压缩对象 --- */
598
- let compress: lZlib.ICompress | null = null;
599
- if (!data.res.headersSent) {
600
- if (!data.res.getHeader('content-type')) {
601
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
602
- }
603
- if (Buffer.byteLength(rtn) >= 1024) {
604
- compress = lZlib.createCompress(data.req.headers['accept-encoding'] ?? '');
605
- if (compress) {
606
- data.res.setHeader('content-encoding', compress.type);
607
- }
608
- }
609
- data.res.writeHead(httpCode);
610
- }
611
- if (!data.res.writableEnded) {
612
- if (compress) {
613
- compress.compress.write(rtn);
614
- compress.compress.pipe(data.res);
615
- compress.compress.end();
616
- }
617
- else {
618
- data.res.end(rtn);
619
- }
620
- }
621
- }
622
- else if (rtn instanceof stream.Readable || rtn instanceof lResponse.Response) {
623
- // --- 返回的是流,那就以管道的形式输出 ---
624
- const stm = rtn instanceof stream.Readable ? rtn : rtn.getStream();
625
- /** --- 当前的压缩对象 --- */
626
- let compress: lZlib.ICompress | null = null;
627
- if (!data.res.headersSent) {
628
- if (!data.res.getHeader('content-type')) {
629
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
630
- }
631
- compress = lZlib.createCompress(data.req.headers['accept-encoding'] ?? '');
632
- if (compress) {
633
- data.res.setHeader('content-encoding', compress.type);
634
- }
635
- data.res.writeHead(httpCode);
636
- }
637
- if (!data.res.writableEnded) {
638
- if (compress) {
639
- stm.pipe(compress.compress).pipe(data.res);
640
- }
641
- else {
642
- stm.pipe(data.res);
643
- }
644
- }
645
- }
646
- else if (typeof rtn === 'object') {
647
- // --- 返回的是数组,那么代表可能是 JSON,可能是对象序列 ---
648
- if (Array.isArray(rtn)) {
649
- // --- [0, 'xxx'] 模式,或 [string, Readable] 模式 ---
650
- if (rtn.length === 0) {
651
- // --- 异常 ---
652
- if (!data.res.headersSent) {
653
- data.res.writeHead(500);
654
- }
655
- if (!data.res.writableEnded) {
656
- data.res.end('<h1>500 Internal server error</h1><hr>Kebab');
657
- }
658
- }
659
- else {
660
- if (typeof rtn[0] === 'number') {
661
- // --- 1. ---
662
- const json: Record<string, types.Json> = { 'result': rtn[0] };
663
- if (rtn[1] !== undefined) {
664
- if (typeof rtn[1] === 'object') {
665
- // --- [0, ...{'xx': 'xx'}] ---
666
- for (let i = 1; i < rtn.length; ++i) {
667
- Object.assign(json, rtn[i]);
668
- }
669
- }
670
- else {
671
- // --- [0, 'xxx'], [0, 'xxx', ...{'xx': 'xx'}] ---
672
- json['msg'] = rtn[1];
673
- for (let i = 2; i < rtn.length; ++i) {
674
- Object.assign(json, rtn[i]);
675
- }
676
- }
677
- }
678
- const writeData = lText.stringifyJson(json);
679
- /** --- 当前的压缩对象 --- */
680
- let compress: lZlib.ICompress | null = null;
681
- if (!data.res.headersSent) {
682
- data.res.setHeader('content-type', 'application/json; charset=utf-8');
683
- if (Buffer.byteLength(writeData) >= 1024) {
684
- compress = lZlib.createCompress(data.req.headers['accept-encoding'] ?? '');
685
- if (compress) {
686
- data.res.setHeader('content-encoding', compress.type);
687
- }
688
- }
689
- data.res.writeHead(httpCode);
690
- }
691
- if (!data.res.writableEnded) {
692
- if (compress) {
693
- compress.compress.write(writeData);
694
- compress.compress.pipe(data.res);
695
- compress.compress.end();
696
- }
697
- else {
698
- data.res.end(writeData);
699
- }
700
- }
701
- }
702
- else {
703
- // --- 2. 字符串与 Readable 的组合 ---
704
- /** --- 当前的压缩对象 --- */
705
- let compress: lZlib.ICompress | null = null;
706
- if (!data.res.headersSent) {
707
- if (!data.res.getHeader('content-type')) {
708
- data.res.setHeader('content-type', 'text/html; charset=utf-8');
709
- }
710
- compress = lZlib.createCompress(data.req.headers['accept-encoding'] ?? '');
711
- if (compress) {
712
- data.res.setHeader('content-encoding', compress.type);
713
- }
714
- data.res.writeHead(httpCode);
715
- }
716
- if (!data.res.writableEnded) {
717
- const passThrough = new stream.PassThrough();
718
- // --- 先进行 pipe 绑定,之后 write 或 pipe 进 pass 的数据,直接就可以消费 ---
719
- if (compress) {
720
- passThrough.pipe(compress.compress).pipe(data.res);
721
- }
722
- else {
723
- passThrough.pipe(data.res);
724
- }
725
- await lCore.passThroughAppend(passThrough, rtn);
726
- }
727
- }
728
- }
729
- }
730
- else {
731
- // --- 直接是个 json 对象 ---
732
- const writeData = lText.stringifyJson(rtn);
733
- /** --- 当前的压缩对象 --- */
734
- let compress: lZlib.ICompress | null = null;
735
- if (!data.res.headersSent) {
736
- data.res.setHeader('content-type', 'application/json; charset=utf-8');
737
- if (Buffer.byteLength(writeData) >= 1024) {
738
- compress = lZlib.createCompress(data.req.headers['accept-encoding'] ?? '');
739
- if (compress) {
740
- data.res.setHeader('content-encoding', compress.type);
741
- }
742
- }
743
- data.res.writeHead(httpCode);
744
- }
745
- if (!data.res.writableEnded) {
746
- if (compress) {
747
- compress.compress.write(writeData);
748
- compress.compress.pipe(data.res);
749
- compress.compress.end();
750
- }
751
- else {
752
- data.res.end(writeData);
753
- }
754
- }
755
- }
756
- }
757
- else {
758
- // --- 异常 ---
759
- if (!data.res.headersSent) {
760
- data.res.writeHead(500);
761
- }
762
- if (!data.res.writableEnded) {
763
- data.res.end('<h1>500 Internal server error</h1><hr>Kebab');
764
- }
765
- }
766
- await waitCtr(cctr ?? middle);
767
- return true;
768
- }
769
-
770
- /**
771
- * --- 获取控制器 left 和 action ---
772
- * @param path 相对路径
773
- */
774
- function getPathLeftRight(path: string): string[] {
775
- const pathLio = path.lastIndexOf('/');
776
- if (pathLio === -1) {
777
- return [path, 'index'];
778
- }
779
- const right = path.slice(pathLio + 1);
780
- return [path.slice(0, pathLio), right === '' ? 'index' : right];
781
- }
782
-
783
- /**
784
- * --- 根据 path 获取 ws 的控制器类名, Mutton: false, Kebab: true ---
785
- * @param path 路径
786
- */
787
- function getWsCtrName(path: string): string {
788
- const pathLio = path.lastIndexOf('/');
789
- if (pathLio === -1) {
790
- return path.toLowerCase();
791
- }
792
- return path.slice(pathLio + 1).toLowerCase();
793
- }
794
-
795
- /**
796
- * --- 删除本次请求所有已上传的临时文件, Mutton: false, Kebab: true ---
797
- * @param cctr Ctr 对象 或 files
798
- */
799
- export async function unlinkUploadFiles(
800
- cctr: sCtr.Ctr | Record<string, types.IPostFile | types.IPostFile[]>
801
- ): Promise<void> {
802
- const cfiles = cctr instanceof sCtr.Ctr ? cctr.getPrototype('_files') : cctr;
803
- for (const name in cfiles) {
804
- let files = cfiles[name];
805
- if (!Array.isArray(files)) {
806
- files = [files];
807
- }
808
- for (const file of files) {
809
- await lFs.unlink(file.path);
810
- }
811
- }
812
- }
813
-
814
- /**
815
- * --- 等待异步任务结束,并删除临时文件,如果结束后还有事务没关闭,则会在本函数中打印控制台并且写入 log 文件 ---
816
- * --- 此时其实已经给客户端返回了,此处等待不消耗客户端的等待时间 ---
817
- * @param cctr 要等待的控制器 ---
818
- */
819
- export async function waitCtr(cctr: sCtr.Ctr): Promise<void> {
820
- // --- 先判断异步任务是否结束 ---
821
- const waitInfo = cctr.getPrototype('_waitInfo');
822
- if (waitInfo.asyncTask.count) {
823
- await waitInfo.asyncTask.callback();
824
- }
825
- // --- 判断事务 ---
826
- if (waitInfo.transaction) {
827
- // --- 有事务未关闭 ---
828
- const msg = 'transaction(' + waitInfo.transaction + ') not be closed';
829
- lCore.display('[ERROR][ROUTE][WAITCTR] ' + msg + ': ', cctr.getPrototype('_config').const.path);
830
- await lCore.log(cctr, msg, '-error');
831
- }
832
- // --- 彻底结束,删除文件 ---
833
- await unlinkUploadFiles(cctr);
834
- }
835
-
836
- /**
837
- * --- 将 POST 数据的值执行 trim ---
838
- * @param post
839
- */
840
- export function trimPost(post: Record<string, types.Json>): void {
841
- for (const key in post) {
842
- const val = post[key];
843
- if (typeof val === 'string') {
844
- post[key] = post[key].trim();
845
- }
846
- else if (typeof val === 'object') {
847
- trimPost(post[key]);
848
- }
849
- }
850
- }
851
-
852
- /**
853
- * --- 内部使用,获取 post 对象,如果是文件上传(formdata)的情况则不获取 ---
854
- * @param req 请求对象
855
- */
856
- function getPost(req: http2.Http2ServerRequest | http.IncomingMessage): Promise<[string, Record<string, types.Json>]> {
857
- return new Promise(function(resolve) {
858
- const ct = req.headers['content-type'] ?? '';
859
- if (ct.includes('form-data')) {
860
- resolve(['', {}]);
861
- return;
862
- }
863
- // --- json 或普通 post ---
864
- let buffer: Buffer = Buffer.from('');
865
- req.on('data', function(chunk: Buffer) {
866
- buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
867
- });
868
- req.on('end', function() {
869
- const s = buffer.toString();
870
- if (!s) {
871
- resolve(['', {}]);
872
- return;
873
- }
874
- // --- 判断 json 还是普通 ---
875
- if (ct.includes('json')) {
876
- try {
877
- resolve([s, lText.parseJson(s)]);
878
- }
879
- catch {
880
- resolve(['', {}]);
881
- }
882
- return;
883
- }
884
- resolve([s, lText.queryParse(s)]);
885
- });
886
- });
887
- }
888
-
889
- /**
890
- * --- 获取 formdata 的 post ---
891
- * @param req 请求头
892
- * @param events 文件处理情况
893
- */
894
- export function getFormData(
895
- req: http2.Http2ServerRequest | http.IncomingMessage,
896
- events: {
897
- onfilestart?: (name: string) => boolean | undefined;
898
- onfiledata?: (chunk: Buffer) => void;
899
- onfileend?: () => void;
900
- } = {}
901
- ): Promise<{
902
- 'post': Record<string, types.Json>;
903
- 'files': Record<string, types.IPostFile | types.IPostFile[]>;
904
- } | false> {
905
- return new Promise(function(resolve) {
906
- const ct = req.headers['content-type'] ?? '';
907
- if (!ct) {
908
- resolve({ 'post': {}, 'files': {} });
909
- return;
910
- }
911
- /** --- boundary 位置 --- */
912
- const clio = ct.lastIndexOf('boundary=');
913
- if (clio === -1) {
914
- resolve({ 'post': {}, 'files': {} });
915
- return;
916
- }
917
- /** --- 最终返回 --- */
918
- const rtn: {
919
- 'post': Record<string, types.Json>;
920
- 'files': Record<string, types.IPostFile | types.IPostFile[]>;
921
- } = {
922
- 'post': {},
923
- 'files': {}
924
- };
925
- /** --- 获取的 boundary 文本 --- */
926
- const boundary = ct.slice(clio + 9);
927
-
928
- // 一下变量是相对于 data 事件的全局变量 ---
929
-
930
- /** --- 当前临时读入的还未处理的 buffer --- */
931
- let buffer: Buffer = Buffer.from('');
932
- /** --- 状态机 --- */
933
- enum EState {
934
- /** --- 判断头部是什么 --- */
935
- 'WAIT',
936
- /** --- 当前是文件 --- */
937
- 'FILE',
938
- /** --- 当前是 post 数据 --- */
939
- 'POST'
940
- }
941
- /** --- 当前所处状态 --- */
942
- let state: EState = EState.WAIT;
943
-
944
- /** --- 当前处理中的 post 的 name --- */
945
- let name: string = '';
946
-
947
- /** --- 当前处理中的 file 的 name --- */
948
- let fileName: string = '';
949
- /** --- 当前处理中的临时文件名 --- */
950
- let ftmpName: string = '';
951
- /** --- 当前正在写入的临时文件流 --- */
952
- let ftmpStream: fs.WriteStream;
953
- /** --- 当前临时文件已写入的流大小 --- */
954
- let ftmpSize: number = 0;
955
- /** --- 当前正在写入的文件数量 --- */
956
- let writeFileLength: number = 0;
957
- /** --- 当前读取是否已经完全结束 --- */
958
- let readEnd: boolean = false;
959
-
960
- // --- 开始读取 ---
961
- req.on('data', function(chunk: Buffer) {
962
- buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
963
- while (true) {
964
- switch (state) {
965
- case EState.WAIT: {
966
- /** --- 中断符位置 --- */
967
- const io = buffer.indexOf('\r\n\r\n');
968
- if (io === -1) {
969
- return;
970
- }
971
- // --- 头部已经读取完毕 ---
972
- const head: string = buffer.subarray(0, io).toString();
973
- // --- 除头部外剩下的 buffer ---
974
- buffer = buffer.subarray(io + 4);
975
- // --- 获取 name ---
976
- let match = /name="(.+?)"/.exec(head);
977
- name = match ? match[1] : '';
978
- // --- 判断是 post 还是文件 ---
979
- match = /filename="(.+?)"/.exec(head);
980
- if (!match) {
981
- // --- 普通 post ---
982
- state = EState.POST;
983
- }
984
- else {
985
- // --- 文件 ---
986
- ++writeFileLength;
987
- state = EState.FILE;
988
- fileName = match[1];
989
- const fr = events.onfilestart?.(name);
990
- if (fr !== true) {
991
- // --- 创建文件流 ---
992
- const date = new Date();
993
- ftmpName = date.getUTCFullYear().toString() +
994
- (date.getUTCMonth() + 1).toString().padStart(2, '0') +
995
- date.getUTCDate().toString().padStart(2, '0') +
996
- date.getUTCHours().toString().padStart(2, '0') +
997
- date.getUTCMinutes().toString().padStart(2, '0') + '_' + lCore.random() + '.ftmp';
998
- ftmpStream = lFs.createWriteStream(kebab.FTMP_CWD + ftmpName);
999
- ftmpSize = 0;
1000
- }
1001
- else {
1002
- ftmpName = '';
1003
- }
1004
- }
1005
- break;
1006
- }
1007
- case EState.POST: {
1008
- // --- POST 模式 ---
1009
- const io = buffer.indexOf('\r\n--' + boundary);
1010
- if (io === -1) {
1011
- return;
1012
- }
1013
- // --- 找到结束标语,写入 POST ---
1014
- const val = buffer.subarray(0, io).toString();
1015
- if (rtn.post[name]) {
1016
- if (Array.isArray(rtn.post[name])) {
1017
- (rtn.post[name] as string[]).push(val);
1018
- }
1019
- else {
1020
- rtn.post[name] = [rtn.post[name] as string, val];
1021
- }
1022
- }
1023
- else {
1024
- rtn.post[name] = val;
1025
- }
1026
- // --- 重置状态机 ---
1027
- state = EState.WAIT;
1028
- buffer = buffer.subarray(io + 4 + boundary.length);
1029
- break;
1030
- }
1031
- case EState.FILE: {
1032
- // --- FILE 模式 ---
1033
- const io = buffer.indexOf('\r\n--' + boundary);
1034
- if (io === -1) {
1035
- // --- 没找到结束标语,将预留 boundary 长度之前的写入到文件 ---
1036
- const writeBuffer = buffer.subarray(0, -boundary.length - 4);
1037
- if (ftmpName === '') {
1038
- events.onfiledata?.(writeBuffer);
1039
- }
1040
- else {
1041
- ftmpStream.write(writeBuffer);
1042
- ftmpSize += Buffer.byteLength(writeBuffer);
1043
- }
1044
- buffer = buffer.subarray(-boundary.length - 4);
1045
- return;
1046
- }
1047
- // --- 找到结束标语,结束标语之前的写入文件,之后的重新放回 buffer ---
1048
- const writeBuffer = buffer.subarray(0, io);
1049
- if (ftmpName === '') {
1050
- events.onfileend?.();
1051
- }
1052
- else {
1053
- ftmpStream.write(writeBuffer);
1054
- ftmpSize += Buffer.byteLength(writeBuffer);
1055
- ftmpStream.end(() => {
1056
- --writeFileLength;
1057
- if (!readEnd) {
1058
- // --- request 没读完,不管 ---
1059
- return;
1060
- }
1061
- if (writeFileLength) {
1062
- // --- req 读完了但文件还没写完,不管 ---
1063
- return;
1064
- }
1065
- // --- 文件也写完了 ---
1066
- resolve(rtn);
1067
- });
1068
- // --- POST 部分 ---
1069
- let fname = fileName.replace(/\\/g, '/');
1070
- const nlio = fname.lastIndexOf('/');
1071
- if (nlio !== -1) {
1072
- fname = fname.slice(nlio + 1);
1073
- }
1074
- const val: types.IPostFile = {
1075
- 'name': fname,
1076
- 'origin': fileName,
1077
- 'size': ftmpSize,
1078
- 'path': kebab.FTMP_CWD + ftmpName
1079
- };
1080
- if (rtn.files[name]) {
1081
- if (Array.isArray(rtn.files[name])) {
1082
- (rtn.files[name] as types.IPostFile[]).push(val);
1083
- }
1084
- else {
1085
- rtn.files[name] = [rtn.files[name] as types.IPostFile, val];
1086
- }
1087
- }
1088
- else {
1089
- rtn.files[name] = val;
1090
- }
1091
- }
1092
- // --- 重置状态机 ---
1093
- state = EState.WAIT;
1094
- buffer = buffer.subarray(io + 4 + boundary.length);
1095
- break;
1096
- }
1097
- }
1098
- }
1099
- });
1100
- req.on('error', function() {
1101
- resolve(false);
1102
- });
1103
- req.on('end', function() {
1104
- readEnd = true;
1105
- if (writeFileLength) {
1106
- // --- 文件没写完 ---
1107
- return;
1108
- }
1109
- // --- 文件写完了 ----
1110
- resolve(rtn);
1111
- });
1112
- });
1113
- }