@maiyunnet/kebab 9.3.1 → 9.3.2

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/doc/kebab-rag.md CHANGED
@@ -1360,7 +1360,7 @@ index/variables/VER.md
1360
1360
 
1361
1361
  # Variable: VER
1362
1362
 
1363
- > `const` **VER**: `"9.3.1"` = `'9.3.1'`
1363
+ > `const` **VER**: `"9.3.2"` = `'9.3.2'`
1364
1364
 
1365
1365
  Defined in: [index.ts:10](https://github.com/maiyunnet/kebab/blob/master/index.ts#L10)
1366
1366
 
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * --- 本文件用来定义每个目录实体地址的常量 ---
6
6
  */
7
7
  /** --- 当前系统版本号 --- */
8
- export declare const VER = "9.3.1";
8
+ export declare const VER = "9.3.2";
9
9
  /** --- 框架根目录,以 / 结尾 --- */
10
10
  export declare const ROOT_PATH: string;
11
11
  /** --- 框架的 LIB,以 / 结尾 --- */
package/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * --- 本文件用来定义每个目录实体地址的常量 ---
7
7
  */
8
8
  /** --- 当前系统版本号 --- */
9
- export const VER = '9.3.1';
9
+ export const VER = '9.3.2';
10
10
  // --- 服务端用的路径 ---
11
11
  const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
12
12
  /** --- /xxx/xxx --- */
@@ -31,7 +31,7 @@ export declare class Response {
31
31
  /**
32
32
  * --- 获取响应读取流对象 ---
33
33
  */
34
- getStream(): (import("undici/types/readable").default & undici.Dispatcher.BodyMixin) | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress | null;
34
+ getStream(): zlib.BrotliDecompress | zlib.Gunzip | zlib.Inflate | (import("undici/types/readable").default & undici.Dispatcher.BodyMixin) | null;
35
35
  /**
36
36
  * --- 获取原生响应读取流对象 ---
37
37
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maiyunnet/kebab",
3
- "version": "9.3.1",
3
+ "version": "9.3.2",
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
@@ -26,9 +26,12 @@ const hbTimer = setInterval(function () {
26
26
  pid: process.pid
27
27
  });
28
28
  }, 10_000);
29
- /** --- 加载的证书列表(path: { sc, cert }) --- */
30
- let certList = [];
31
- /** --- server: index --- */
29
+ /**
30
+ * --- 证书持久化 Map,key 为 keyPath|certPath ---
31
+ * --- 防止 reload 时证书文件处于更新空隙期(旧文件已删、新文件未写入)导致证书丢失 ---
32
+ */
33
+ const certList = new Map();
34
+ /** --- servername → certList mapKey 索引缓存 --- */
32
35
  let certHostIndex = {};
33
36
  /** --- 默认证书 --- */
34
37
  let defaultSc;
@@ -83,25 +86,26 @@ async function run() {
83
86
  http2Server = http2.createSecureServer({
84
87
  // eslint-disable-next-line @typescript-eslint/naming-convention
85
88
  'SNICallback': (servername, cb) => {
89
+ // --- 每 24 小时自动重新加载一次证书,确保证书更新后能被及时加载到内存中 ---
86
90
  if (Date.now() - certLastLoad > 60_000 * 60 * 24) {
87
91
  // --- 不能用异步,不要干扰 SNI 进程 ---
88
92
  reloadCert().catch(e => {
89
93
  lCore.display('[CHILD][run] reloadCert error', e);
90
94
  });
91
95
  }
92
- const i = certHostIndex[servername];
93
- if (i !== undefined) {
94
- cb(null, certList[i].sc);
96
+ const cachedKey = certHostIndex[servername];
97
+ if (cachedKey !== undefined) {
98
+ cb(null, certList.get(cachedKey).sc);
95
99
  return;
96
100
  }
97
101
  // --- 查找 servername ---
98
- for (let i = 0; i < certList.length; ++i) {
99
- if (!certList[i].cert.checkHost(servername)) {
102
+ for (const [mapKey, item] of certList) {
103
+ if (!item.cert.checkHost(servername)) {
100
104
  continue;
101
105
  }
102
106
  // --- 找到 ---
103
- cb(null, certList[i].sc);
104
- certHostIndex[servername] = i;
107
+ cb(null, item.sc);
108
+ certHostIndex[servername] = mapKey;
105
109
  return;
106
110
  }
107
111
  if (defaultSc) {
@@ -488,18 +492,28 @@ async function reload() {
488
492
  }
489
493
  /**
490
494
  * --- 重新加载证书对 ---
495
+ * --- 采用增量更新策略:新证书加载成功则覆盖旧值,加载失败(文件更新空隙期)则保留旧证书 ---
491
496
  */
492
497
  async function reloadCert() {
493
498
  certLastLoad = Date.now();
494
- const cl = [];
499
+ /** --- 本次配置中出现的 mapKey 集合,用于事后清理已下线的证书 --- */
500
+ const newKeys = new Set();
501
+ /** --- 配置文件是否成功加载,未成功则不清理 Map,防止误删全部证书 --- */
502
+ let configLoaded = false;
495
503
  try {
496
504
  const certConfig = await lFs.getContent(kebab.CONF_CWD + 'cert.json', 'utf8');
497
505
  if (certConfig) {
498
506
  const certs = lText.parseJson(certConfig);
507
+ configLoaded = true;
499
508
  for (const item of certs) {
500
- const key = await lFs.getContent(lText.isRealPath(item.key) ? item.key : kebab.CERT_CWD + item.key, 'utf8');
501
- const cert = await lFs.getContent(lText.isRealPath(item.cert) ? item.cert : kebab.CERT_CWD + item.cert, 'utf8');
509
+ const keyPath = lText.isRealPath(item.key) ? item.key : kebab.CERT_CWD + item.key;
510
+ const certPath = lText.isRealPath(item.cert) ? item.cert : kebab.CERT_CWD + item.cert;
511
+ const mapKey = keyPath + '|' + certPath;
512
+ newKeys.add(mapKey);
513
+ const key = await lFs.getContent(keyPath, 'utf8');
514
+ const cert = await lFs.getContent(certPath, 'utf8');
502
515
  if (!cert || !key) {
516
+ // --- 文件不存在(可能正在更新),保留旧证书(如果有) ---
503
517
  continue;
504
518
  }
505
519
  const certo = new crypto.X509Certificate(cert);
@@ -507,18 +521,22 @@ async function reloadCert() {
507
521
  'key': key,
508
522
  'cert': cert
509
523
  });
510
- cl.push({
511
- 'cert': certo,
512
- 'sc': sc
513
- });
524
+ certList.set(mapKey, { 'cert': certo, 'sc': sc });
514
525
  }
515
526
  }
516
527
  }
517
528
  catch {
518
529
  // --- NOTHING ---
519
530
  }
520
- certList = cl;
521
- defaultSc = cl.length > 0 ? cl[0].sc : undefined;
531
+ // --- 仅在配置文件加载成功时,移除已从配置中下线的证书 ---
532
+ if (configLoaded) {
533
+ for (const key of certList.keys()) {
534
+ if (!newKeys.has(key)) {
535
+ certList.delete(key);
536
+ }
537
+ }
538
+ }
539
+ defaultSc = certList.size > 0 ? certList.values().next().value.sc : undefined;
522
540
  certHostIndex = {};
523
541
  }
524
542
  // --- 接收主进程回传信号,主要用来 reload,restart ---