@maiyunnet/kebab 9.18.0 → 9.18.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 +317 -163
- package/index.d.ts +3 -1
- package/index.js +1 -1
- package/lib/undici/request.d.ts +10 -0
- package/lib/undici/request.js +16 -0
- package/lib/undici.d.ts +16 -0
- package/lib/undici.js +33 -1
- package/package.json +1 -1
- package/sys/child.js +1 -1
- package/sys/ctr.d.ts +7 -0
- package/sys/ctr.js +28 -1
- package/sys/route.d.ts +2 -1
- package/sys/route.js +49 -9
package/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
6
6
|
*/
|
|
7
7
|
/** --- 当前系统版本号 --- */
|
|
8
|
-
export declare const VER = "9.18.
|
|
8
|
+
export declare const VER = "9.18.2";
|
|
9
9
|
/** --- 框架根目录,以 / 结尾 --- */
|
|
10
10
|
export declare const ROOT_PATH: string;
|
|
11
11
|
/** --- 框架的 LIB,以 / 结尾 --- */
|
|
@@ -159,6 +159,8 @@ export interface IConfigConst {
|
|
|
159
159
|
'host': string;
|
|
160
160
|
'hostname': string;
|
|
161
161
|
'hostport': number;
|
|
162
|
+
/** --- 协议与 host,如 https://example.com:8443 --- */
|
|
163
|
+
'urlOrigin': string;
|
|
162
164
|
'uri': IUrlParse;
|
|
163
165
|
'rootPath': string;
|
|
164
166
|
'modPath': string;
|
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '9.18.
|
|
9
|
+
export const VER = '9.18.2';
|
|
10
10
|
// --- 服务端用的路径 ---
|
|
11
11
|
const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
12
12
|
/** --- /xxx/xxx --- */
|
package/lib/undici/request.d.ts
CHANGED
|
@@ -47,6 +47,16 @@ export declare class Request {
|
|
|
47
47
|
* @param timeout 秒
|
|
48
48
|
*/
|
|
49
49
|
timeout(timeout: number): this;
|
|
50
|
+
/**
|
|
51
|
+
* --- 设置空闲连接允许复用的最长时间 ---
|
|
52
|
+
* @param timeout 秒数;非正数或无效值按未设置处理;默认无服务端提示时为 4 秒,有提示时采用提示值减 2 秒且最多 600 秒
|
|
53
|
+
*/
|
|
54
|
+
keepAliveTimeout(timeout: number): this;
|
|
55
|
+
/**
|
|
56
|
+
* --- 设置连接建立后允许承接新请求的最长时间 ---
|
|
57
|
+
* @param timeout 秒数;非正数或无效值按未设置处理;达到后不再复用但不会中断在途请求,默认不限制
|
|
58
|
+
*/
|
|
59
|
+
reuseTimeout(timeout: number): this;
|
|
50
60
|
/**
|
|
51
61
|
* --- 设置网络异常后的重试次数 ---
|
|
52
62
|
* @param retry 重试次数,默认为 1;非幂等请求需由调用方保证安全
|
package/lib/undici/request.js
CHANGED
|
@@ -59,6 +59,22 @@ export class Request {
|
|
|
59
59
|
this._opt['timeout'] = timeout;
|
|
60
60
|
return this;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* --- 设置空闲连接允许复用的最长时间 ---
|
|
64
|
+
* @param timeout 秒数;非正数或无效值按未设置处理;默认无服务端提示时为 4 秒,有提示时采用提示值减 2 秒且最多 600 秒
|
|
65
|
+
*/
|
|
66
|
+
keepAliveTimeout(timeout) {
|
|
67
|
+
this._opt['keepAliveTimeout'] = timeout;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* --- 设置连接建立后允许承接新请求的最长时间 ---
|
|
72
|
+
* @param timeout 秒数;非正数或无效值按未设置处理;达到后不再复用但不会中断在途请求,默认不限制
|
|
73
|
+
*/
|
|
74
|
+
reuseTimeout(timeout) {
|
|
75
|
+
this._opt['reuseTimeout'] = timeout;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
62
78
|
/**
|
|
63
79
|
* --- 设置网络异常后的重试次数 ---
|
|
64
80
|
* @param retry 重试次数,默认为 1;非幂等请求需由调用方保证安全
|
package/lib/undici.d.ts
CHANGED
|
@@ -74,6 +74,10 @@ export declare function fetch(input: string | URL | Request, init?: RequestInit
|
|
|
74
74
|
'hosts'?: Record<string, string> | string;
|
|
75
75
|
/** --- 网络异常后的重试次数,默认 0;流式请求体不可重试,非幂等请求需由调用方保证安全 --- */
|
|
76
76
|
'retry'?: number;
|
|
77
|
+
/** --- 空闲连接允许复用的最长秒数;非正数或无效值按未设置处理;默认无服务端提示时为 4 秒,有提示时采用提示值减 2 秒且最多 600 秒 --- */
|
|
78
|
+
'keepAliveTimeout'?: number;
|
|
79
|
+
/** --- 连接建立后允许承接新请求的最长秒数;非正数或无效值按未设置处理;达到后不再复用但不会中断在途请求,默认不限制 --- */
|
|
80
|
+
'reuseTimeout'?: number;
|
|
77
81
|
}): Promise<Response>;
|
|
78
82
|
/**
|
|
79
83
|
* --- 发起一个请求 ---
|
|
@@ -152,6 +156,10 @@ export interface IRequestOptions {
|
|
|
152
156
|
};
|
|
153
157
|
/** --- 连接是否保持长连接(即是否允许复用),默认为 true --- */
|
|
154
158
|
'keep'?: boolean;
|
|
159
|
+
/** --- 空闲连接允许复用的最长秒数;非正数或无效值按未设置处理;默认无服务端提示时为 4 秒,有提示时采用提示值减 2 秒且最多 600 秒;自定义 Agent 由其自身配置 --- */
|
|
160
|
+
'keepAliveTimeout'?: number;
|
|
161
|
+
/** --- 连接建立后允许承接新请求的最长秒数;非正数或无效值按未设置处理;达到后不再复用但不会中断在途请求,默认不限制;自定义 Agent 由其自身配置 --- */
|
|
162
|
+
'reuseTimeout'?: number;
|
|
155
163
|
/** --- 复用池名/Agent,默认为 default --- */
|
|
156
164
|
'reuse'?: string | undici.ProxyAgent | undici.Agent;
|
|
157
165
|
/** --- cookie 托管对象 --- */
|
|
@@ -170,6 +178,10 @@ export interface IMproxyOptions {
|
|
|
170
178
|
'hosts'?: Record<string, string> | string;
|
|
171
179
|
'local'?: string;
|
|
172
180
|
'headers'?: THttpHeaders;
|
|
181
|
+
/** --- 空闲连接允许复用的最长秒数;非正数或无效值按未设置处理;默认无服务端提示时为 4 秒,有提示时采用提示值减 2 秒且最多 600 秒 --- */
|
|
182
|
+
'keepAliveTimeout'?: number;
|
|
183
|
+
/** --- 连接建立后允许承接新请求的最长秒数;非正数或无效值按未设置处理;达到后不再复用但不会中断在途请求,默认不限制 --- */
|
|
184
|
+
'reuseTimeout'?: number;
|
|
173
185
|
/** --- 过滤 header,返回 true 则留下 --- */
|
|
174
186
|
filter?: (h: string) => boolean;
|
|
175
187
|
/** --- 默认为 default --- */
|
|
@@ -184,6 +196,10 @@ export interface IRproxyOptions {
|
|
|
184
196
|
'hosts'?: Record<string, string> | string;
|
|
185
197
|
'local'?: string;
|
|
186
198
|
'headers'?: THttpHeaders;
|
|
199
|
+
/** --- 空闲连接允许复用的最长秒数;非正数或无效值按未设置处理;默认无服务端提示时为 4 秒,有提示时采用提示值减 2 秒且最多 600 秒 --- */
|
|
200
|
+
'keepAliveTimeout'?: number;
|
|
201
|
+
/** --- 连接建立后允许承接新请求的最长秒数;非正数或无效值按未设置处理;达到后不再复用但不会中断在途请求,默认不限制 --- */
|
|
202
|
+
'reuseTimeout'?: number;
|
|
187
203
|
/** --- 过滤 header,返回 true 则留下 --- */
|
|
188
204
|
filter?: (h: string) => boolean;
|
|
189
205
|
/** --- 正向 mproxy 代理,url 如 https://xxx/abc --- */
|
package/lib/undici.js
CHANGED
|
@@ -45,14 +45,31 @@ function normalizeRetry(retry) {
|
|
|
45
45
|
return (typeof retry === 'number') && Number.isFinite(retry) ?
|
|
46
46
|
Math.max(0, Math.floor(retry)) : 0;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* --- 将 Agent 的秒数配置转换为毫秒 ---
|
|
50
|
+
* @param timeout 秒数
|
|
51
|
+
* @returns 未设置或数值无效时返回 undefined,否则返回毫秒数
|
|
52
|
+
*/
|
|
53
|
+
function normalizeAgentTimeout(timeout) {
|
|
54
|
+
if (timeout === undefined) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const milliseconds = timeout * 1_000;
|
|
58
|
+
if (!Number.isFinite(milliseconds) || (milliseconds <= 0)) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return milliseconds;
|
|
62
|
+
}
|
|
48
63
|
/** --- 获取或创建 undici.agent 对象 --- */
|
|
49
64
|
function getAgent(opt = {}) {
|
|
50
65
|
let k = opt.reuse ?? 'default';
|
|
51
66
|
if (typeof k !== 'string') {
|
|
52
67
|
return k;
|
|
53
68
|
}
|
|
69
|
+
const keepAliveTimeout = normalizeAgentTimeout(opt.keepAliveTimeout);
|
|
70
|
+
const reuseTimeout = normalizeAgentTimeout(opt.reuseTimeout);
|
|
54
71
|
if (k === 'default') {
|
|
55
|
-
// --- hosts/local/keep
|
|
72
|
+
// --- hosts/local/keep/连接时长均会影响 agent 行为,需生成独立的 key ---
|
|
56
73
|
const features = [];
|
|
57
74
|
if (opt.hosts) {
|
|
58
75
|
features.push(`h:${lCrypto.hashHmac('md5', lText.stringifyJson(opt.hosts))}`);
|
|
@@ -63,6 +80,12 @@ function getAgent(opt = {}) {
|
|
|
63
80
|
if (opt.keep === false) {
|
|
64
81
|
features.push('k:0');
|
|
65
82
|
}
|
|
83
|
+
if (keepAliveTimeout !== undefined) {
|
|
84
|
+
features.push(`ka:${keepAliveTimeout}`);
|
|
85
|
+
}
|
|
86
|
+
if (reuseTimeout !== undefined) {
|
|
87
|
+
features.push(`rt:${reuseTimeout}`);
|
|
88
|
+
}
|
|
66
89
|
if (features.length > 0) {
|
|
67
90
|
k = features.join('|');
|
|
68
91
|
}
|
|
@@ -74,6 +97,13 @@ function getAgent(opt = {}) {
|
|
|
74
97
|
lookup: buildDnsLookup(opt.hosts),
|
|
75
98
|
},
|
|
76
99
|
'pipelining': opt.keep === false ? 0 : 1,
|
|
100
|
+
...(keepAliveTimeout === undefined ? {} : {
|
|
101
|
+
'keepAliveTimeout': keepAliveTimeout,
|
|
102
|
+
'keepAliveMaxTimeout': keepAliveTimeout,
|
|
103
|
+
}),
|
|
104
|
+
...(reuseTimeout === undefined ? {} : {
|
|
105
|
+
'clientTtl': reuseTimeout,
|
|
106
|
+
}),
|
|
77
107
|
}));
|
|
78
108
|
}
|
|
79
109
|
return agents.get(k);
|
|
@@ -293,6 +323,8 @@ export async function fetch(input, init = {}) {
|
|
|
293
323
|
'hosts': init.hosts,
|
|
294
324
|
'mproxy': init.mproxy,
|
|
295
325
|
'retry': init.retry,
|
|
326
|
+
'keepAliveTimeout': init.keepAliveTimeout,
|
|
327
|
+
'reuseTimeout': init.reuseTimeout,
|
|
296
328
|
'signal': init.signal ?? undefined,
|
|
297
329
|
'follow': init.redirect === 'follow' ? 10 : 0,
|
|
298
330
|
};
|
package/package.json
CHANGED
package/sys/child.js
CHANGED
package/sys/ctr.d.ts
CHANGED
|
@@ -58,6 +58,13 @@ export type TValibotResult<TSchema extends v.GenericSchema, TIssue extends v.Bas
|
|
|
58
58
|
readonly issues: [TIssue, ...TIssue[]];
|
|
59
59
|
readonly response: kebab.Json[];
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* --- 判断当前 HTTP 请求是否仍可响应 ---
|
|
63
|
+
* @param req 请求对象
|
|
64
|
+
* @param res 响应对象
|
|
65
|
+
* @returns 客户端对应的请求流仍可响应时返回 true
|
|
66
|
+
*/
|
|
67
|
+
export declare function isHttpRequestAvailable(req: http2.Http2ServerRequest | http.IncomingMessage, res: http2.Http2ServerResponse | http.ServerResponse): boolean;
|
|
61
68
|
export declare class Ctr {
|
|
62
69
|
/** --- 路由参数序列数组 --- */
|
|
63
70
|
protected _param: string[];
|
package/sys/ctr.js
CHANGED
|
@@ -19,6 +19,27 @@ export function clearLocaleData() {
|
|
|
19
19
|
localeFiles = [];
|
|
20
20
|
localeData = {};
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* --- 判断当前 HTTP 请求是否仍可响应 ---
|
|
24
|
+
* @param req 请求对象
|
|
25
|
+
* @param res 响应对象
|
|
26
|
+
* @returns 客户端对应的请求流仍可响应时返回 true
|
|
27
|
+
*/
|
|
28
|
+
export function isHttpRequestAvailable(req, res) {
|
|
29
|
+
if (req.destroyed && !req.complete) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (('stream' in req) && (req.stream.closed || req.stream.destroyed)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (req.socket.destroyed || !req.socket.writable) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (res.destroyed || res.closed || res.writableEnded || !res.writable) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
22
43
|
export class Ctr {
|
|
23
44
|
/** --- 路由参数序列数组 --- */
|
|
24
45
|
_param = [];
|
|
@@ -69,7 +90,13 @@ export class Ctr {
|
|
|
69
90
|
}
|
|
70
91
|
/** --- 当前用户连接是否还在连接中 --- */
|
|
71
92
|
get _isAvail() {
|
|
72
|
-
|
|
93
|
+
if (this._socket) {
|
|
94
|
+
return this._socket.writable;
|
|
95
|
+
}
|
|
96
|
+
if (!this._res) {
|
|
97
|
+
return !this._req.socket.destroyed && this._req.socket.writable;
|
|
98
|
+
}
|
|
99
|
+
return isHttpRequestAvailable(this._req, this._res);
|
|
73
100
|
}
|
|
74
101
|
/** --- timeout 的 timer --- */
|
|
75
102
|
_timer;
|
package/sys/route.d.ts
CHANGED
|
@@ -51,12 +51,13 @@ export declare function waitCtr(cctr: sCtr.Ctr): Promise<void>;
|
|
|
51
51
|
/**
|
|
52
52
|
* --- 获取 post 对象(通常已自动获取),如果是文件上传(formdata)的情况则不获取 ---
|
|
53
53
|
* @param req 请求对象
|
|
54
|
+
* @returns 解析后的 POST 数据,请求传输中断时返回 false
|
|
54
55
|
*/
|
|
55
56
|
export declare function getPost(req: http2.Http2ServerRequest | http.IncomingMessage): Promise<{
|
|
56
57
|
'input': string;
|
|
57
58
|
'raw': Record<string, any>;
|
|
58
59
|
'post': Record<string, any>;
|
|
59
|
-
}>;
|
|
60
|
+
} | false>;
|
|
60
61
|
/**
|
|
61
62
|
* --- 获取 formdata 的 post ---
|
|
62
63
|
* @param req 请求头
|
package/sys/route.js
CHANGED
|
@@ -145,6 +145,7 @@ export async function run(data) {
|
|
|
145
145
|
'host': data.uri.host ?? '',
|
|
146
146
|
'hostname': data.uri.hostname ?? '',
|
|
147
147
|
'hostport': data.uri.port ? parseInt(data.uri.port) : (data.uri.protocol === 'https:' ? 443 : 80),
|
|
148
|
+
'urlOrigin': (data.uri.protocol ?? '') + '//' + (data.uri.host ?? ''),
|
|
148
149
|
'uri': data.uri,
|
|
149
150
|
// --- 服务端用的路径 ---
|
|
150
151
|
'rootPath': data.rootPath,
|
|
@@ -452,6 +453,9 @@ export async function run(data) {
|
|
|
452
453
|
}
|
|
453
454
|
if (reqStartRtn === 1) {
|
|
454
455
|
const rawPost = await getPost(data.req);
|
|
456
|
+
if (!rawPost) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
455
459
|
// --- 原始 POST ---
|
|
456
460
|
middle.setPrototype('_rawPost', rawPost.raw);
|
|
457
461
|
// --- 原始 input ---
|
|
@@ -843,6 +847,7 @@ export async function waitCtr(cctr) {
|
|
|
843
847
|
/**
|
|
844
848
|
* --- 获取 post 对象(通常已自动获取),如果是文件上传(formdata)的情况则不获取 ---
|
|
845
849
|
* @param req 请求对象
|
|
850
|
+
* @returns 解析后的 POST 数据,请求传输中断时返回 false
|
|
846
851
|
*/
|
|
847
852
|
export function getPost(req) {
|
|
848
853
|
return new Promise(function (resolve) {
|
|
@@ -860,7 +865,28 @@ export function getPost(req) {
|
|
|
860
865
|
const maxPostSize = 50 * 1024 * 1024;
|
|
861
866
|
let buffer = Buffer.from('');
|
|
862
867
|
let overflow = false;
|
|
863
|
-
|
|
868
|
+
let finished = false;
|
|
869
|
+
let onData;
|
|
870
|
+
let onEnd;
|
|
871
|
+
let onError;
|
|
872
|
+
let onAborted;
|
|
873
|
+
let onClose;
|
|
874
|
+
const cleanup = () => {
|
|
875
|
+
req.off('data', onData);
|
|
876
|
+
req.off('end', onEnd);
|
|
877
|
+
req.off('error', onError);
|
|
878
|
+
req.off('aborted', onAborted);
|
|
879
|
+
req.off('close', onClose);
|
|
880
|
+
};
|
|
881
|
+
const finish = (result) => {
|
|
882
|
+
if (finished) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
finished = true;
|
|
886
|
+
cleanup();
|
|
887
|
+
resolve(result);
|
|
888
|
+
};
|
|
889
|
+
onData = (chunk) => {
|
|
864
890
|
if (overflow) {
|
|
865
891
|
return;
|
|
866
892
|
}
|
|
@@ -870,10 +896,10 @@ export function getPost(req) {
|
|
|
870
896
|
return;
|
|
871
897
|
}
|
|
872
898
|
buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
|
|
873
|
-
}
|
|
874
|
-
|
|
899
|
+
};
|
|
900
|
+
onEnd = () => {
|
|
875
901
|
if (overflow) {
|
|
876
|
-
|
|
902
|
+
finish({
|
|
877
903
|
'input': '',
|
|
878
904
|
'raw': {},
|
|
879
905
|
'post': {},
|
|
@@ -882,7 +908,7 @@ export function getPost(req) {
|
|
|
882
908
|
}
|
|
883
909
|
const s = buffer.toString();
|
|
884
910
|
if (!s) {
|
|
885
|
-
|
|
911
|
+
finish({
|
|
886
912
|
'input': '',
|
|
887
913
|
'raw': {},
|
|
888
914
|
'post': {},
|
|
@@ -893,14 +919,14 @@ export function getPost(req) {
|
|
|
893
919
|
if (ct.includes('json')) {
|
|
894
920
|
try {
|
|
895
921
|
const raw = lText.parseJson(s);
|
|
896
|
-
|
|
922
|
+
finish({
|
|
897
923
|
'input': s,
|
|
898
924
|
'raw': raw,
|
|
899
925
|
'post': lText.trimJson(raw),
|
|
900
926
|
});
|
|
901
927
|
}
|
|
902
928
|
catch {
|
|
903
|
-
|
|
929
|
+
finish({
|
|
904
930
|
'input': '',
|
|
905
931
|
'raw': {},
|
|
906
932
|
'post': {},
|
|
@@ -909,12 +935,26 @@ export function getPost(req) {
|
|
|
909
935
|
return;
|
|
910
936
|
}
|
|
911
937
|
const raw = lText.queryParse(s);
|
|
912
|
-
|
|
938
|
+
finish({
|
|
913
939
|
'input': s,
|
|
914
940
|
'raw': raw,
|
|
915
941
|
'post': lText.trimJson(raw),
|
|
916
942
|
});
|
|
917
|
-
}
|
|
943
|
+
};
|
|
944
|
+
onError = () => {
|
|
945
|
+
finish(false);
|
|
946
|
+
};
|
|
947
|
+
onAborted = () => {
|
|
948
|
+
finish(false);
|
|
949
|
+
};
|
|
950
|
+
onClose = () => {
|
|
951
|
+
finish(false);
|
|
952
|
+
};
|
|
953
|
+
req.on('data', onData);
|
|
954
|
+
req.on('end', onEnd);
|
|
955
|
+
req.on('error', onError);
|
|
956
|
+
req.on('aborted', onAborted);
|
|
957
|
+
req.on('close', onClose);
|
|
918
958
|
});
|
|
919
959
|
}
|
|
920
960
|
/**
|