@maiyunnet/kebab 3.2.18 → 3.2.20

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "3.2.18";
8
+ export declare const VER = "3.2.20";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  export declare const LIB_PATH: string;
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '3.2.18';
9
+ export const VER = '3.2.20';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
package/lib/core.d.ts CHANGED
@@ -242,3 +242,10 @@ export declare function debug(message?: any, ...optionalParams: any[]): void;
242
242
  * @param optionalParams 参数
243
243
  */
244
244
  export declare function display(message?: any, ...optionalParams: any[]): void;
245
+ /**
246
+ * --- 让 res 发送头部(前提是头部没有被发送才能调用本方法 ---
247
+ * @param res 响应对象
248
+ * @param statusCode 状态码
249
+ * @param headers 头部
250
+ */
251
+ export declare function writeHead(res: http2.Http2ServerResponse | http.ServerResponse, statusCode: number, headers?: http.OutgoingHttpHeaders): void;
package/lib/core.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * Last: 2020-4-11 22:34:58, 2022-10-2 14:13:06, 2022-12-28 20:33:24, 2023-12-15 11:49:02, 2024-7-2 15:23:35, 2025-6-13 19:45:53
5
5
  */
6
6
  import * as cp from 'child_process';
7
+ import * as http2 from 'http2';
7
8
  import * as stream from 'stream';
8
9
  import * as os from 'os';
9
10
  import * as kebab from '#kebab/index.js';
@@ -791,3 +792,17 @@ export function display(message, ...optionalParams) {
791
792
  // eslint-disable-next-line no-console
792
793
  console.log(message, ...optionalParams);
793
794
  }
795
+ /**
796
+ * --- 让 res 发送头部(前提是头部没有被发送才能调用本方法 ---
797
+ * @param res 响应对象
798
+ * @param statusCode 状态码
799
+ * @param headers 头部
800
+ */
801
+ export function writeHead(res, statusCode, headers) {
802
+ if (res instanceof http2.Http2ServerResponse) {
803
+ res.writeHead(statusCode, headers);
804
+ }
805
+ else {
806
+ res.writeHead(statusCode, headers);
807
+ }
808
+ }
package/lib/cron.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare function run(): void;
14
14
  export interface IRegular {
15
15
  /** --- 任务名称 --- */
16
16
  'name': string;
17
- /** --- 任务对象 --- */
17
+ /** --- 任务日期对象(系统时区) --- */
18
18
  'date': {
19
19
  /** --- -1, 1 - 12 --- */
20
20
  'month': number;
@@ -31,7 +31,7 @@ export interface IRegular {
31
31
  callback: (date: string) => void | Promise<void>;
32
32
  }
33
33
  export interface IRegularData extends IRegular {
34
- /** --- 上次执行时间字符串 --- */
34
+ /** --- 上次执行时间字符串,格式:YmdHi(系统时区) --- */
35
35
  'last': string;
36
36
  /** --- 总执行次数 --- */
37
37
  'count': number;
package/lib/fs.js CHANGED
@@ -424,19 +424,18 @@ export function createWriteStream(path, options) {
424
424
  * @param stat 文件的 stat(如果有)
425
425
  */
426
426
  export async function readToResponse(path, req, res, stat) {
427
- res.statusCode = 200;
428
427
  stat ??= await stats(path);
429
428
  if (!stat) {
430
429
  const content = '<h1>404 Not found</h1><hr>Kebab';
431
- res.statusCode = 404;
432
430
  res.setHeader('content-length', Buffer.byteLength(content));
431
+ lCore.writeHead(res, 404);
433
432
  res.end(content);
434
433
  return;
435
434
  }
436
435
  // --- 判断缓存以及 MIME 和编码 ---
437
436
  let charset = '';
438
437
  const mimeData = mime.getData(path);
439
- if (['htm', 'html', 'css', 'js', 'xml', 'jpg', 'jpeg', 'svg', 'gif', 'png'].includes(mimeData.extension)) {
438
+ if (['htm', 'html', 'css', 'js', 'xml', 'jpg', 'jpeg', 'svg', 'gif', 'png', 'json'].includes(mimeData.extension)) {
440
439
  charset = '; charset=utf-8';
441
440
  // --- 这些文件可能需要缓存 ---
442
441
  const hash = `W/"${stat.size.toString(16)}-${stat.mtime.getTime().toString(16)}"`;
@@ -447,7 +446,7 @@ export async function readToResponse(path, req, res, stat) {
447
446
  const noneMatch = req.headers['if-none-match'];
448
447
  const modifiedSince = req.headers['if-modified-since'];
449
448
  if ((hash === noneMatch) && (lastModified === modifiedSince)) {
450
- res.statusCode = 304;
449
+ lCore.writeHead(res, 304);
451
450
  res.end();
452
451
  return;
453
452
  }
@@ -472,5 +471,6 @@ export async function readToResponse(path, req, res, stat) {
472
471
  }
473
472
  // --- 不压缩 ---
474
473
  res.setHeader('content-length', stat.size);
474
+ lCore.writeHead(res, 200);
475
475
  await pipe(path, res instanceof http2.Http2ServerResponse ? (res.stream ?? res) : res);
476
476
  }
package/lib/net.d.ts CHANGED
@@ -114,6 +114,7 @@ export interface IRequestOptions {
114
114
  'type'?: 'form' | 'json';
115
115
  /** --- 秒数 --- */
116
116
  'timeout'?: number;
117
+ /** --- 追踪 location 次数,0 为不追踪,默认为 0 --- */
117
118
  'follow'?: number;
118
119
  /** --- 自定义 host 映射,如 {'www.maiyun.net': '127.0.0.1'},或全部映射到一个 host --- */
119
120
  'hosts'?: Record<string, string> | string;
package/lib/net.js CHANGED
@@ -12,6 +12,7 @@ import * as kebab from '#kebab/index.js';
12
12
  import * as lFs from '#kebab/lib/fs.js';
13
13
  import * as lText from '#kebab/lib/text.js';
14
14
  import * as lTime from '#kebab/lib/time.js';
15
+ import * as lCore from './core.js';
15
16
  // --- 自己 ---
16
17
  import * as fd from './net/formdata.js';
17
18
  import * as lRequest from './net/request.js';
@@ -98,6 +99,7 @@ export async function request(u, data, opt = {}) {
98
99
  const method = opt.method ?? 'GET';
99
100
  const type = opt.type ?? 'form';
100
101
  const timeout = opt.timeout ?? 10;
102
+ /** --- 追踪 location 次数,0 为不追踪,默认为 0 --- */
101
103
  const follow = opt.follow ?? 0;
102
104
  const hosts = opt.hosts ?? {};
103
105
  const save = opt.save;
@@ -525,7 +527,7 @@ export async function mproxy(ctr, auth, opt = {}) {
525
527
  if (rres.headers) {
526
528
  filterHeaders(rres.headers, res, opt.filter);
527
529
  }
528
- res.statusCode = rres.headers?.['http-code'] ?? 200;
530
+ lCore.writeHead(res, rres.headers?.['http-code'] ?? 200);
529
531
  await new Promise((resolve) => {
530
532
  stream.pipe(res).on('finish', () => {
531
533
  resolve();
@@ -589,7 +591,7 @@ export async function rproxy(ctr, route, opt = {}) {
589
591
  if (rres.headers) {
590
592
  filterHeaders(rres.headers, res, opt.filter);
591
593
  }
592
- res.statusCode = rres.headers?.['http-code'] ?? 200;
594
+ lCore.writeHead(res, rres.headers?.['http-code'] ?? 200);
593
595
  await new Promise((resolve) => {
594
596
  stream.pipe(res).on('finish', () => {
595
597
  resolve();
package/lib/text.d.ts CHANGED
@@ -51,6 +51,7 @@ export declare const REGEXP_DOMAIN: RegExp;
51
51
  * @return bool
52
52
  */
53
53
  export declare function isDomain(domain: string): boolean;
54
+ /** --- 可打印的 ascii 字符集 --- */
54
55
  export declare const REGEXP_ASCII: RegExp;
55
56
  /**
56
57
  * --- 判断是否在 ascii 字符集内,仅可输入部分 ---
package/lib/text.js CHANGED
@@ -210,6 +210,7 @@ export const REGEXP_DOMAIN = /^.+?\.((?![0-9]).)+$/i;
210
210
  export function isDomain(domain) {
211
211
  return REGEXP_DOMAIN.test(domain);
212
212
  }
213
+ /** --- 可打印的 ascii 字符集 --- */
213
214
  export const REGEXP_ASCII = /^[\x20-\x7E]*$/;
214
215
  /**
215
216
  * --- 判断是否在 ascii 字符集内,仅可输入部分 ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "3.2.18",
3
+ "version": "3.2.20",
4
4
  "description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
5
5
  "type": "module",
6
6
  "keywords": [
package/sys/child.js CHANGED
@@ -89,7 +89,7 @@ async function run() {
89
89
  }, function (req, res) {
90
90
  const host = (req.headers[':authority'] ?? req.headers['host'] ?? '');
91
91
  if (!host) {
92
- res.statusCode = 403;
92
+ lCore.writeHead(res, 403);
93
93
  res.end();
94
94
  return;
95
95
  }
@@ -141,7 +141,7 @@ async function run() {
141
141
  httpServer = http.createServer(function (req, res) {
142
142
  const host = (req.headers['host'] ?? '');
143
143
  if (!host) {
144
- res.statusCode = 403;
144
+ lCore.writeHead(res, 403);
145
145
  res.end();
146
146
  return;
147
147
  }
@@ -202,6 +202,7 @@ async function requestHandler(req, res, https) {
202
202
  'timeout': 30_000,
203
203
  'callback': () => {
204
204
  if (!req.socket.writable) {
205
+ // --- 用户连接已中断 ---
205
206
  return;
206
207
  }
207
208
  if (res.headersSent) {
@@ -209,8 +210,8 @@ async function requestHandler(req, res, https) {
209
210
  return;
210
211
  }
211
212
  const content = '<h1>504 Gateway Timeout</h1><hr>Kebab';
212
- res.statusCode = 504;
213
213
  res.setHeader('content-length', Buffer.byteLength(content));
214
+ lCore.writeHead(res, 504);
214
215
  res.end(content);
215
216
  }
216
217
  };
@@ -232,7 +233,7 @@ async function requestHandler(req, res, https) {
232
233
  /** --- 当前的 vhost 配置文件 --- */
233
234
  const vhost = await getVhostByHostname(uri.hostname ?? '');
234
235
  if (!vhost) {
235
- res.statusCode = 403;
236
+ lCore.writeHead(res, 403);
236
237
  res.end();
237
238
  return;
238
239
  /*
@@ -262,9 +263,9 @@ async function requestHandler(req, res, https) {
262
263
  let stat = await lFs.stats(vhost.real + now + item);
263
264
  if (!stat) {
264
265
  const content = '<h1>404 Not found</h1><hr>Kebab';
265
- res.statusCode = 404;
266
266
  res.setHeader('content-type', 'text/html; charset=utf-8');
267
267
  res.setHeader('content-length', Buffer.byteLength(content));
268
+ lCore.writeHead(res, 404);
268
269
  res.end(content);
269
270
  return;
270
271
  }
@@ -298,9 +299,11 @@ async function requestHandler(req, res, https) {
298
299
  'headers': {}
299
300
  }, '[CHILD][requestHandler][E0]' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
300
301
  const content = '<h1>500 Server Error</h1><hr>Kebab';
301
- res.statusCode = 500;
302
- res.setHeader('content-type', 'text/html; charset=utf-8');
303
- res.setHeader('content-length', Buffer.byteLength(content));
302
+ if (!res.headersSent) {
303
+ res.setHeader('content-type', 'text/html; charset=utf-8');
304
+ res.setHeader('content-length', Buffer.byteLength(content));
305
+ lCore.writeHead(res, 500);
306
+ }
304
307
  res.end(content);
305
308
  return;
306
309
  }
@@ -337,9 +340,11 @@ async function requestHandler(req, res, https) {
337
340
  catch (e) {
338
341
  lCore.log({}, '[CHILD][requestHandler][E1]' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
339
342
  const content = '<h1>500 Server Error</h1><hr>Kebab';
340
- res.statusCode = 500;
341
- res.setHeader('content-type', 'text/html; charset=utf-8');
342
- res.setHeader('content-length', Buffer.byteLength(content));
343
+ if (!res.headersSent) {
344
+ res.setHeader('content-type', 'text/html; charset=utf-8');
345
+ res.setHeader('content-length', Buffer.byteLength(content));
346
+ lCore.writeHead(res, 500);
347
+ }
343
348
  res.end(content);
344
349
  return;
345
350
  }
@@ -356,9 +361,9 @@ async function requestHandler(req, res, https) {
356
361
  }
357
362
  }
358
363
  const content = '<h1>403 Forbidden</h1><hr>Kebab';
359
- res.statusCode = 403;
360
364
  res.setHeader('content-type', 'text/html; charset=utf-8');
361
365
  res.setHeader('content-length', Buffer.byteLength(content));
366
+ lCore.writeHead(res, 403);
362
367
  res.end(content);
363
368
  }
364
369
  /**
package/sys/cmd.js CHANGED
@@ -126,16 +126,16 @@ async function run() {
126
126
  // --- config - s3 ---
127
127
  config.s3 ??= {};
128
128
  config.s3['CF'] ??= {};
129
- config.s3['CF'].account = '';
130
- config.s3['CF'].sid = '';
131
- config.s3['CF'].skey = '';
132
- config.s3['CF'].region = 'auto';
133
- config.s3['CF'].bucket = '';
129
+ config.s3['CF'].account ??= '';
130
+ config.s3['CF'].sid ??= '';
131
+ config.s3['CF'].skey ??= '';
132
+ config.s3['CF'].region ??= 'auto';
133
+ config.s3['CF'].bucket ??= '';
134
134
  config.s3['TENCENT'] ??= {};
135
- config.s3['TENCENT'].sid = '';
136
- config.s3['TENCENT'].skey = '';
137
- config.s3['TENCENT'].region = '';
138
- config.s3['TENCENT'].bucket = '';
135
+ config.s3['TENCENT'].sid ??= '';
136
+ config.s3['TENCENT'].skey ??= '';
137
+ config.s3['TENCENT'].region ??= '';
138
+ config.s3['TENCENT'].bucket ??= '';
139
139
  // --- config - turnstile ---
140
140
  config.turnstile ??= {};
141
141
  config.turnstile['CF'] ??= {};
package/sys/ctr.d.ts CHANGED
@@ -155,14 +155,14 @@ export declare class Ctr {
155
155
  /**
156
156
  * --- 检测提交的数据类型 ---
157
157
  * @param input 要校验的输入项
158
- * @param rule 规则
158
+ * @param rule 规则, int, double, num(可字符串), array, bool, string, ascii
159
159
  * @param rtn 返回值
160
160
  */
161
161
  protected _checkInput(input: Record<string, kebab.Json>, rule: Record<string, kebab.Json[]>, rtn: kebab.Json[]): boolean;
162
162
  /**
163
163
  * --- 检测提交的数据类型(会检测 XSRF) ---
164
164
  * @param input 要校验的输入项
165
- * @param rule 规则
165
+ * @param rule 规则, int, double, num(可字符串), array, bool, string, ascii
166
166
  * @param rtn 返回值
167
167
  */
168
168
  protected _checkXInput(input: Record<string, kebab.Json>, rule: Record<string, kebab.Json[]>, rtn: kebab.Json[]): boolean;
package/sys/ctr.js CHANGED
@@ -218,7 +218,7 @@ export class Ctr {
218
218
  /**
219
219
  * --- 检测提交的数据类型 ---
220
220
  * @param input 要校验的输入项
221
- * @param rule 规则
221
+ * @param rule 规则, int, double, num(可字符串), array, bool, string, ascii
222
222
  * @param rtn 返回值
223
223
  */
224
224
  _checkInput(input, rule, rtn) {
@@ -308,7 +308,8 @@ export class Ctr {
308
308
  }
309
309
  case 'int':
310
310
  case 'integer': {
311
- if (input[key] && (typeof input[key] !== 'number') && !Number.isSafeInteger(input[key])) {
311
+ // --- 必须是数字型且是整数 ---
312
+ if (input[key] && !Number.isSafeInteger(input[key])) {
312
313
  rtn[0] = val[lastK][0];
313
314
  rtn[1] = val[lastK][1];
314
315
  if (val[lastK][2]) {
@@ -320,6 +321,7 @@ export class Ctr {
320
321
  }
321
322
  case 'float':
322
323
  case 'double': {
324
+ // --- 必须是数字型 ---
323
325
  if (input[key] && (typeof input[key] !== 'number')) {
324
326
  rtn[0] = val[lastK][0];
325
327
  rtn[1] = val[lastK][1];
@@ -332,6 +334,7 @@ export class Ctr {
332
334
  }
333
335
  case 'num':
334
336
  case 'number': {
337
+ // --- 可字符串数字 ---
335
338
  if (input[key] && (typeof input[key] !== 'number') && !/^[0-9]+\.?[0-9]*$/.test(input[key])) {
336
339
  rtn[0] = val[lastK][0];
337
340
  rtn[1] = val[lastK][1];
@@ -378,6 +381,18 @@ export class Ctr {
378
381
  }
379
382
  break;
380
383
  }
384
+ case 'ascii': {
385
+ // --- 必须是 ASCII 字符 ---
386
+ if (input[key] !== null && !lText.isAscii(input[key])) {
387
+ rtn[0] = val[lastK][0];
388
+ rtn[1] = val[lastK][1];
389
+ if (val[lastK][2]) {
390
+ rtn[2] = val[lastK][2];
391
+ }
392
+ return false;
393
+ }
394
+ break;
395
+ }
381
396
  default: {
382
397
  let match;
383
398
  if (input[key] !== null) {
@@ -468,7 +483,7 @@ export class Ctr {
468
483
  /**
469
484
  * --- 检测提交的数据类型(会检测 XSRF) ---
470
485
  * @param input 要校验的输入项
471
- * @param rule 规则
486
+ * @param rule 规则, int, double, num(可字符串), array, bool, string, ascii
472
487
  * @param rtn 返回值
473
488
  */
474
489
  _checkXInput(input, rule, rtn) {
@@ -669,6 +684,7 @@ export class Ctr {
669
684
  this._res.setHeader('access-control-allow-methods', '*');
670
685
  if (this._req.method === 'OPTIONS') {
671
686
  this._res.setHeader('access-control-max-age', '3600');
687
+ this._httpCode = 204;
672
688
  return false;
673
689
  }
674
690
  return true;
package/sys/mod.d.ts CHANGED
@@ -69,6 +69,7 @@ export default class Mod {
69
69
  constructor(opt: {
70
70
  'db': lDb.Pool | lDb.Transaction;
71
71
  'ctr'?: sCtr.Ctr;
72
+ /** --- 框架会自动去重 --- */
72
73
  'index'?: string | string[];
73
74
  'alias'?: string;
74
75
  'row'?: Record<string, any>;
package/sys/route.js CHANGED
@@ -37,9 +37,9 @@ export async function run(data) {
37
37
  if (!configContent) {
38
38
  if (data.res) {
39
39
  const content = '<h1>500 File kebab.json can not be read</h1><hr>Kebab';
40
- data.res.statusCode = 500;
41
40
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
42
41
  data.res.setHeader('content-length', Buffer.byteLength(content));
42
+ lCore.writeHead(data.res, 500);
43
43
  data.res.end(content);
44
44
  }
45
45
  else {
@@ -133,13 +133,13 @@ export async function run(data) {
133
133
  /** --- 组合要跳转的路径 --- */
134
134
  const apath = config.const.path + (config.const.qs ? '?' + config.const.qs : '');
135
135
  if (config.lang.list.includes(lang)) {
136
- data.res.statusCode = 302;
137
136
  data.res.setHeader('location', config.const.urlBase + lang + '/' + apath);
137
+ lCore.writeHead(data.res, 302);
138
138
  data.res.end('');
139
139
  return true;
140
140
  }
141
- data.res.statusCode = 302;
142
141
  data.res.setHeader('location', config.const.urlBase + config.lang.list[0] + '/' + apath);
142
+ lCore.writeHead(data.res, 302);
143
143
  data.res.end('');
144
144
  return true;
145
145
  }
@@ -183,15 +183,15 @@ export async function run(data) {
183
183
  if (pathLeft.startsWith('middle')) {
184
184
  if (data.res) {
185
185
  if (config.route['#404']) {
186
- data.res.statusCode = 302;
187
186
  data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
187
+ lCore.writeHead(data.res, 302);
188
188
  data.res.end('');
189
189
  return true;
190
190
  }
191
191
  const content = '[Error] Controller not found, path: ' + path + '.';
192
- data.res.statusCode = 404;
193
192
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
194
193
  data.res.setHeader('content-length', Buffer.byteLength(content));
194
+ lCore.writeHead(data.res, 404);
195
195
  data.res.end(content);
196
196
  }
197
197
  else {
@@ -413,9 +413,9 @@ export async function run(data) {
413
413
  catch (e) {
414
414
  lCore.log(middle, '(E03)' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
415
415
  const content = '<h1>500 Server Error</h1><hr>Kebab';
416
- data.res.statusCode = 500;
417
416
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
418
417
  data.res.setHeader('content-length', Buffer.byteLength(content));
418
+ lCore.writeHead(data.res, 500);
419
419
  data.res.end(content);
420
420
  return true;
421
421
  }
@@ -429,9 +429,9 @@ export async function run(data) {
429
429
  catch (e) {
430
430
  lCore.log(middle, '(E05)' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
431
431
  const content = '<h1>500 Server Error</h1><hr>Kebab';
432
- data.res.statusCode = 500;
433
432
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
434
433
  data.res.setHeader('content-length', Buffer.byteLength(content));
434
+ lCore.writeHead(data.res, 500);
435
435
  data.res.end(content);
436
436
  return true;
437
437
  }
@@ -443,15 +443,15 @@ export async function run(data) {
443
443
  if (!await lFs.isFile(filePath)) {
444
444
  // --- 指定的控制器不存在 ---
445
445
  if (config.route['#404']) {
446
- data.res.statusCode = 302;
447
446
  data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
447
+ lCore.writeHead(data.res, 302);
448
448
  data.res.end('');
449
449
  return true;
450
450
  }
451
451
  const content = '[Error] Controller not found, path: ' + path + '.';
452
- data.res.statusCode = 404;
453
452
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
454
453
  data.res.setHeader('content-length', Buffer.byteLength(content));
454
+ lCore.writeHead(data.res, 404);
455
455
  data.res.end(content);
456
456
  return true;
457
457
  }
@@ -482,8 +482,8 @@ export async function run(data) {
482
482
  lCore.log(cctr, '', '-visit');
483
483
  // --- 强制 HTTPS ---
484
484
  if (config.set.mustHttps && !config.const.https) {
485
- data.res.statusCode = 302;
486
485
  data.res.setHeader('location', data.req.url ?? '');
486
+ lCore.writeHead(data.res, 302);
487
487
  data.res.end('');
488
488
  return true;
489
489
  }
@@ -491,15 +491,15 @@ export async function run(data) {
491
491
  if (pathRight.startsWith('_') || pathRight === 'onUpgrade' || pathRight === 'onLoad' || pathRight === 'onData' || pathRight === 'onDrain' || pathRight === 'onEnd' || pathRight === 'onClose' || pathRight === 'setPrototype' || pathRight === 'getPrototype' || pathRight === 'getAuthorization') {
492
492
  // --- _ 开头的 action 是内部方法,不允许访问 ---
493
493
  if (config.route['#404']) {
494
- data.res.statusCode = 302;
495
494
  data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
495
+ lCore.writeHead(data.res, 302);
496
496
  data.res.end('');
497
497
  return true;
498
498
  }
499
499
  const content = '[Error] Action not found, path: ' + path + '.';
500
- data.res.statusCode = 404;
501
500
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
502
501
  data.res.setHeader('content-length', Buffer.byteLength(content));
502
+ lCore.writeHead(data.res, 404);
503
503
  data.res.end(content);
504
504
  return true;
505
505
  }
@@ -508,15 +508,15 @@ export async function run(data) {
508
508
  });
509
509
  if (cctr[pathRight] === undefined) {
510
510
  if (config.route['#404']) {
511
- data.res.statusCode = 302;
512
511
  data.res.setHeader('location', lText.urlResolve(config.const.urlBase, config.route['#404']));
512
+ lCore.writeHead(data.res, 302);
513
513
  data.res.end('');
514
514
  return true;
515
515
  }
516
516
  const content = '[Error] Action not found, path: ' + path + '.';
517
- data.res.statusCode = 404;
518
517
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
519
518
  data.res.setHeader('content-length', Buffer.byteLength(content));
519
+ lCore.writeHead(data.res, 404);
520
520
  data.res.end(content);
521
521
  return true;
522
522
  }
@@ -541,9 +541,9 @@ export async function run(data) {
541
541
  catch (e) {
542
542
  lCore.log(cctr, '(E05)' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
543
543
  const content = '<h1>500 Server Error</h1><hr>Kebab';
544
- data.res.statusCode = 500;
545
544
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
546
545
  data.res.setHeader('content-length', Buffer.byteLength(content));
546
+ lCore.writeHead(data.res, 500);
547
547
  data.res.end(content);
548
548
  await waitCtr(cctr);
549
549
  return true;
@@ -556,9 +556,9 @@ export async function run(data) {
556
556
  catch (e) {
557
557
  lCore.log(cctr, '(E04)' + lText.stringifyJson(e.stack).slice(1, -1), '-error');
558
558
  const content = '<h1>500 Server Error</h1><hr>Kebab';
559
- data.res.statusCode = 500;
560
559
  data.res.setHeader('content-type', 'text/html; charset=utf-8');
561
560
  data.res.setHeader('content-length', Buffer.byteLength(content));
561
+ lCore.writeHead(data.res, 500);
562
562
  data.res.end(content);
563
563
  await waitCtr(cctr);
564
564
  return true;
@@ -580,7 +580,7 @@ export async function run(data) {
580
580
  // --- 已经自行输出过 writeHead,可能自行处理了内容,如 pipe,则不再 writeHead ---
581
581
  }
582
582
  else {
583
- data.res.statusCode = data.res.getHeader('location') ? 302 : httpCode;
583
+ lCore.writeHead(data.res, data.res.getHeader('location') ? 302 : httpCode);
584
584
  }
585
585
  if (!data.res.writableEnded) {
586
586
  // --- 如果当前还没结束,则强制关闭连接,一切 pipe 请自行在方法中 await,否则会被中断 ---
@@ -603,7 +603,7 @@ export async function run(data) {
603
603
  data.res.setHeader('content-encoding', compress.type);
604
604
  }
605
605
  }
606
- data.res.statusCode = httpCode;
606
+ lCore.writeHead(data.res, httpCode);
607
607
  }
608
608
  if (!data.res.writableEnded) {
609
609
  if (compress) {
@@ -634,7 +634,7 @@ export async function run(data) {
634
634
  if (compress) {
635
635
  data.res.setHeader('content-encoding', compress.type);
636
636
  }
637
- data.res.statusCode = httpCode;
637
+ lCore.writeHead(data.res, httpCode);
638
638
  }
639
639
  if (!data.res.writableEnded) {
640
640
  if (compress) {
@@ -652,7 +652,7 @@ export async function run(data) {
652
652
  if (rtn.length === 0) {
653
653
  // --- 异常 ---
654
654
  if (!data.res.headersSent) {
655
- data.res.statusCode = 500;
655
+ lCore.writeHead(data.res, 500);
656
656
  }
657
657
  if (!data.res.writableEnded) {
658
658
  data.res.end('<h1>500 Internal server error</h1><hr>Kebab');
@@ -688,7 +688,7 @@ export async function run(data) {
688
688
  data.res.setHeader('content-encoding', compress.type);
689
689
  }
690
690
  }
691
- data.res.statusCode = httpCode;
691
+ lCore.writeHead(data.res, httpCode);
692
692
  }
693
693
  if (!data.res.writableEnded) {
694
694
  if (compress) {
@@ -713,7 +713,7 @@ export async function run(data) {
713
713
  if (compress) {
714
714
  data.res.setHeader('content-encoding', compress.type);
715
715
  }
716
- data.res.statusCode = httpCode;
716
+ lCore.writeHead(data.res, httpCode);
717
717
  }
718
718
  if (!data.res.writableEnded) {
719
719
  const passThrough = new stream.PassThrough();
@@ -742,7 +742,7 @@ export async function run(data) {
742
742
  data.res.setHeader('content-encoding', compress.type);
743
743
  }
744
744
  }
745
- data.res.statusCode = httpCode;
745
+ lCore.writeHead(data.res, httpCode);
746
746
  }
747
747
  if (!data.res.writableEnded) {
748
748
  if (compress) {
@@ -759,7 +759,7 @@ export async function run(data) {
759
759
  else {
760
760
  // --- 异常 ---
761
761
  if (!data.res.headersSent) {
762
- data.res.statusCode = 500;
762
+ lCore.writeHead(data.res, 500);
763
763
  }
764
764
  if (!data.res.writableEnded) {
765
765
  data.res.end('<h1>500 Internal server error</h1><hr>Kebab');
@@ -24,7 +24,9 @@ export default class extends sCtr.Ctr {
24
24
  ctrCross2(): kebab.Json[];
25
25
  ctrReadable(): fs.ReadStream;
26
26
  ctrAsynctask(): any[];
27
- ctrTimeout(): Promise<any[]>;
27
+ ctrTimeoutLong(): Promise<any[]>;
28
+ ctrTimeoutShort(): Promise<any[]>;
29
+ ctr500(): Promise<void>;
28
30
  modTest(): Promise<kebab.Json[] | string | boolean>;
29
31
  modSplit(): Promise<string>;
30
32
  modSplit1(): Promise<void>;
@@ -76,6 +76,7 @@ export default class extends sCtr.Ctr {
76
76
  '<br>QS: ' + this._config.const.qs,
77
77
  '<br>HTTPS: ' + (this._config.const.https ? 'true' : 'false'),
78
78
  '<br>MOBILE: ' + (this._config.const.mobile ? 'true' : 'false'),
79
+ '<br>MINIPROGRAM: ' + (this._config.const.miniprogram) + ' (' + typeof this._config.const.miniprogram + ')',
79
80
  '<br>Real IP: ' + lCore.ip(this),
80
81
  '<br>Client IP: ' + lCore.realIP(this),
81
82
  '<br><br>URL_BASE: ' + this._config.const.urlBase,
@@ -112,7 +113,9 @@ export default class extends sCtr.Ctr {
112
113
  `<br><a href="${this._config.const.urlBase}test/ctr-cross">View "test/ctr-cross"</a>`,
113
114
  `<br><a href="${this._config.const.urlBase}test/ctr-readable">View "test/ctr-readable"</a>`,
114
115
  `<br><a href="${this._config.const.urlBase}test/ctr-asynctask">View "test/ctr-asynctask"</a>`,
115
- `<br><a href="${this._config.const.urlBase}test/ctr-timeout">View "test/ctr-timeout"</a>`,
116
+ `<br><a href="${this._config.const.urlBase}test/ctr-timeout-long">View "test/ctr-timeout-long"</a>`,
117
+ `<br><a href="${this._config.const.urlBase}test/ctr-timeout-short">View "test/ctr-timeout-short"</a>`,
118
+ `<br><a href="${this._config.const.urlBase}test/ctr-500">View "test/ctr-500"</a>`,
116
119
  '<br><br><b>Middle:</b>',
117
120
  `<br><br><a href="${this._config.const.urlBase}test/middle">View "test/middle"</a>`,
118
121
  '<br><br><b>Model test:</b>',
@@ -509,7 +512,7 @@ Result:<pre id="result">Nothing.</pre>` + this._getEnd();
509
512
  });
510
513
  return [1];
511
514
  }
512
- async ctrTimeout() {
515
+ async ctrTimeoutLong() {
513
516
  this.timeout = 70_000;
514
517
  const echo = ['1'];
515
518
  await lCore.sleep(15_000);
@@ -520,7 +523,25 @@ Result:<pre id="result">Nothing.</pre>` + this._getEnd();
520
523
  echo.push('4');
521
524
  await lCore.sleep(15_000);
522
525
  echo.push('5');
523
- return [1, echo];
526
+ return [1, { 'list': echo }];
527
+ }
528
+ async ctrTimeoutShort() {
529
+ this.timeout = 5_000;
530
+ const echo = ['1'];
531
+ await lCore.sleep(15_000);
532
+ echo.push('2');
533
+ await lCore.sleep(15_000);
534
+ echo.push('3');
535
+ await lCore.sleep(15_000);
536
+ echo.push('4');
537
+ await lCore.sleep(15_000);
538
+ echo.push('5');
539
+ return [1, { 'list': echo }];
540
+ }
541
+ async ctr500() {
542
+ await lCore.sleep(100);
543
+ lCore.writeHead(this._res, 200);
544
+ lCore.debug('DEBUG', this._res.abc.def);
524
545
  }
525
546
  async modTest() {
526
547
  const retur = [];