@maiyunnet/kebab 9.17.2 → 9.18.0
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 +5415 -2317
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/socket.js +46 -19
- package/lib/undici/response.d.ts +1 -1
- package/lib/undici.js +10 -0
- package/lib/ws.d.ts +105 -3
- package/lib/ws.js +543 -145
- package/package.json +17 -16
- package/sys/ctr.d.ts +3 -1
- package/sys/ctr.js +4 -2
- package/sys/route.js +5 -7
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '9.
|
|
9
|
+
export const VER = '9.18.0';
|
|
10
10
|
// --- 服务端用的路径 ---
|
|
11
11
|
const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
12
12
|
/** --- /xxx/xxx --- */
|
package/lib/socket.js
CHANGED
|
@@ -17,31 +17,60 @@ export function rwebsocket(port, url, opt = {}) {
|
|
|
17
17
|
/** --- 请求端产生的双向 socket --- */
|
|
18
18
|
const server = net.createServer(socket => {
|
|
19
19
|
socket.setKeepAlive(true);
|
|
20
|
+
/** --- 远程端的双向 WebSocket --- */
|
|
21
|
+
let rws = null;
|
|
22
|
+
/** --- 心跳定时器 --- */
|
|
23
|
+
let timer = null;
|
|
24
|
+
/** --- 是否已开始清理 --- */
|
|
25
|
+
let closed = false;
|
|
26
|
+
/** --- 同时清理 TCP、WebSocket 与心跳 --- */
|
|
27
|
+
const close = () => {
|
|
28
|
+
if (closed) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
closed = true;
|
|
32
|
+
if (timer) {
|
|
33
|
+
clearInterval(timer);
|
|
34
|
+
timer = null;
|
|
35
|
+
}
|
|
36
|
+
socket.destroy();
|
|
37
|
+
rws?.destroy();
|
|
38
|
+
};
|
|
39
|
+
// --- 必须在异步连接前监听,避免本地先断开后留下远端连接 ---
|
|
40
|
+
socket.on('close', close).on('error', err => {
|
|
41
|
+
lCore.display('[' + lTime.format(null, 'Y-m-d H:i:s') + '] Client error: ' + socket.remoteAddress + ':' + socket.remotePort + ', ' + err.message);
|
|
42
|
+
close();
|
|
43
|
+
});
|
|
20
44
|
(async () => {
|
|
21
45
|
// --- 每次进一个新连接都反代到一个新 WebSocket ---
|
|
22
46
|
lCore.display('[' + lTime.format(null, 'Y-m-d H:i:s') + '] New client: ' + socket.remoteAddress + ':' + socket.remotePort);
|
|
23
|
-
|
|
24
|
-
const rws = await lWs.connect(url, opt);
|
|
47
|
+
rws = await lWs.connect(url, opt);
|
|
25
48
|
if (!rws) {
|
|
26
|
-
|
|
49
|
+
close();
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (closed) {
|
|
53
|
+
rws.destroy();
|
|
27
54
|
return false;
|
|
28
55
|
}
|
|
29
|
-
|
|
30
|
-
rws
|
|
56
|
+
timer = setInterval(() => {
|
|
57
|
+
rws?.ping();
|
|
31
58
|
}, 10_000);
|
|
32
59
|
rws.on('message', msg => {
|
|
33
60
|
switch (msg.opcode) {
|
|
34
61
|
case lWs.EOpcode.TEXT:
|
|
35
62
|
case lWs.EOpcode.BINARY: {
|
|
36
|
-
socket.write(msg.data)
|
|
63
|
+
if (!socket.write(msg.data)) {
|
|
64
|
+
rws?.pause();
|
|
65
|
+
}
|
|
37
66
|
break;
|
|
38
67
|
}
|
|
39
68
|
case lWs.EOpcode.CLOSE: {
|
|
40
|
-
|
|
69
|
+
close();
|
|
41
70
|
break;
|
|
42
71
|
}
|
|
43
72
|
case lWs.EOpcode.PING: {
|
|
44
|
-
rws
|
|
73
|
+
rws?.pong();
|
|
45
74
|
break;
|
|
46
75
|
}
|
|
47
76
|
case lWs.EOpcode.PONG: {
|
|
@@ -51,20 +80,18 @@ export function rwebsocket(port, url, opt = {}) {
|
|
|
51
80
|
// --- EOpcode.CONTINUATION ---
|
|
52
81
|
}
|
|
53
82
|
}
|
|
54
|
-
}).on('
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
83
|
+
}).on('drain', () => {
|
|
84
|
+
socket.resume();
|
|
85
|
+
}).on('close', close);
|
|
58
86
|
socket.on('data', data => {
|
|
59
|
-
rws.writeBinary(data)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
if (rws && !rws.writeBinary(data)) {
|
|
88
|
+
socket.pause();
|
|
89
|
+
}
|
|
90
|
+
}).on('drain', () => {
|
|
91
|
+
rws?.resume();
|
|
63
92
|
}).on('end', () => {
|
|
64
|
-
rws.end();
|
|
65
93
|
lCore.display('[' + lTime.format(null, 'Y-m-d H:i:s') + '] Client disconnected: ' + socket.remoteAddress + ':' + socket.remotePort);
|
|
66
|
-
|
|
67
|
-
lCore.display('[' + lTime.format(null, 'Y-m-d H:i:s') + '] Client error: ' + socket.remoteAddress + ':' + socket.remotePort + ', ' + err.message);
|
|
94
|
+
close();
|
|
68
95
|
});
|
|
69
96
|
})().catch(() => { });
|
|
70
97
|
}).listen(port, () => {
|
package/lib/undici/response.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare class Response {
|
|
|
31
31
|
/**
|
|
32
32
|
* --- 获取响应读取流对象 ---
|
|
33
33
|
*/
|
|
34
|
-
getStream():
|
|
34
|
+
getStream(): (import("undici/types/readable").default & undici.Dispatcher.BodyMixin) | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress | null;
|
|
35
35
|
/**
|
|
36
36
|
* --- 获取原生响应读取流对象 ---
|
|
37
37
|
*/
|
package/lib/undici.js
CHANGED
|
@@ -636,10 +636,14 @@ export async function mproxy(ctr, auth, opt = {}) {
|
|
|
636
636
|
});
|
|
637
637
|
// --- 同时监听 close 确保 promise 一定会 resolve(客户端断连等场景) ---
|
|
638
638
|
stream.pipe(res).on('close', () => {
|
|
639
|
+
stream.destroy();
|
|
639
640
|
resolve();
|
|
640
641
|
});
|
|
641
642
|
});
|
|
642
643
|
}
|
|
644
|
+
else {
|
|
645
|
+
stream.destroy();
|
|
646
|
+
}
|
|
643
647
|
return 1;
|
|
644
648
|
}
|
|
645
649
|
/**
|
|
@@ -739,16 +743,22 @@ export async function rproxy(ctr, route, opt = {}) {
|
|
|
739
743
|
// --- 同时监听 close 确保 promise 一定会 resolve(客户端断连等场景) ---
|
|
740
744
|
if (compress) {
|
|
741
745
|
stream.pipe(compress.compress).pipe(res).on('close', () => {
|
|
746
|
+
stream.destroy();
|
|
747
|
+
compress.compress.destroy();
|
|
742
748
|
resolve();
|
|
743
749
|
});
|
|
744
750
|
}
|
|
745
751
|
else {
|
|
746
752
|
stream.pipe(res).on('close', () => {
|
|
753
|
+
stream.destroy();
|
|
747
754
|
resolve();
|
|
748
755
|
});
|
|
749
756
|
}
|
|
750
757
|
});
|
|
751
758
|
}
|
|
759
|
+
else {
|
|
760
|
+
stream.destroy();
|
|
761
|
+
}
|
|
752
762
|
return true;
|
|
753
763
|
}
|
|
754
764
|
return false;
|
package/lib/ws.d.ts
CHANGED
|
@@ -43,6 +43,25 @@ export interface IConnectOptions {
|
|
|
43
43
|
'url': string;
|
|
44
44
|
'auth': string;
|
|
45
45
|
};
|
|
46
|
+
/** --- WebSocket 握手失败回调 --- */
|
|
47
|
+
onConnectError?: (error: unknown) => void;
|
|
48
|
+
}
|
|
49
|
+
/** --- WebSocket 管道首先结束的一侧 --- */
|
|
50
|
+
export type TPipeSide = 'source' | 'target';
|
|
51
|
+
/** --- WebSocket 管道结束前最后收到的底层事件 --- */
|
|
52
|
+
export type TPipeCloseEvent = 'close' | 'end' | 'timeout' | 'error';
|
|
53
|
+
/** --- WebSocket 管道关闭信息 --- */
|
|
54
|
+
export interface IPipeCloseInfo {
|
|
55
|
+
'side': TPipeSide;
|
|
56
|
+
'event': TPipeCloseEvent;
|
|
57
|
+
'error'?: unknown;
|
|
58
|
+
'code'?: number;
|
|
59
|
+
'reason'?: string;
|
|
60
|
+
}
|
|
61
|
+
/** --- WebSocket 关闭信息 --- */
|
|
62
|
+
export interface ISocketCloseInfo {
|
|
63
|
+
'code': number;
|
|
64
|
+
'reason': string;
|
|
46
65
|
}
|
|
47
66
|
/** --- 正向代理请求的传入参数选项 --- */
|
|
48
67
|
export interface IMproxyOptions {
|
|
@@ -78,10 +97,32 @@ export interface IRproxyOptions {
|
|
|
78
97
|
'url': string;
|
|
79
98
|
'auth': string;
|
|
80
99
|
};
|
|
100
|
+
/** --- 连接目标 WebSocket 失败回调 --- */
|
|
101
|
+
onConnectError?: (error: unknown) => void;
|
|
102
|
+
/** --- 管道关闭回调,返回首先关闭的一侧及其底层事件 --- */
|
|
103
|
+
onClose?: (info: IPipeCloseInfo) => void;
|
|
104
|
+
/** --- 生成回传给另一侧的 WebSocket 关闭原因,仅在管道断开时调用 --- */
|
|
105
|
+
closeReason?: (info: IPipeCloseInfo) => string;
|
|
81
106
|
}
|
|
82
107
|
export declare class Socket {
|
|
83
108
|
/** --- 当前的 ws 对象 --- */
|
|
84
109
|
private _ws;
|
|
110
|
+
/** --- 当前是否为服务端接入的连接 --- */
|
|
111
|
+
private readonly _isServer;
|
|
112
|
+
/** --- 客户端发出的帧是否掩码 --- */
|
|
113
|
+
private _masking;
|
|
114
|
+
/** --- 等待底层写入回调的消息数量 --- */
|
|
115
|
+
private _pendingWrites;
|
|
116
|
+
/** --- 是否已经因发送缓存触发背压 --- */
|
|
117
|
+
private _writeBlocked;
|
|
118
|
+
/** --- 是否已由本端主动结束写入 --- */
|
|
119
|
+
private _finished;
|
|
120
|
+
/** --- 是否已由对端结束读取 --- */
|
|
121
|
+
private _ended;
|
|
122
|
+
/** --- 底层 TCP Socket,用于设置和清理空闲超时 --- */
|
|
123
|
+
private _transport?;
|
|
124
|
+
/** --- 底层 TCP Socket 的超时监听器 --- */
|
|
125
|
+
private _transportTimeout?;
|
|
85
126
|
constructor(request?: http.IncomingMessage, socket?: net.Socket, head?: Buffer, options?: {
|
|
86
127
|
'headers'?: http.OutgoingHttpHeaders;
|
|
87
128
|
'timeout'?: number;
|
|
@@ -94,31 +135,85 @@ export declare class Socket {
|
|
|
94
135
|
connect(u: string, opt?: IConnectOptions): Promise<this | null>;
|
|
95
136
|
/** --- 创建成功后第一时间绑定事件 --- */
|
|
96
137
|
private _bindEvent;
|
|
138
|
+
/**
|
|
139
|
+
* --- 绑定底层 TCP Socket 的空闲超时 ---
|
|
140
|
+
* @param socket 底层 TCP Socket
|
|
141
|
+
* @param timeout 超时毫秒数,0 为不超时
|
|
142
|
+
*/
|
|
143
|
+
private _bindTransport;
|
|
144
|
+
/** --- 清理底层 TCP Socket 监听器 --- */
|
|
145
|
+
private _unbindTransport;
|
|
146
|
+
/**
|
|
147
|
+
* --- 将 ws 的消息数据统一转换为 Buffer ---
|
|
148
|
+
* @param data ws 原始消息数据
|
|
149
|
+
* @returns Buffer
|
|
150
|
+
*/
|
|
151
|
+
private _toBuffer;
|
|
152
|
+
/**
|
|
153
|
+
* --- 接收并派发消息;上层未就绪或主动暂停时同步暂停底层 TCP 读取 ---
|
|
154
|
+
* @param msg 消息
|
|
155
|
+
*/
|
|
156
|
+
private _receive;
|
|
157
|
+
/**
|
|
158
|
+
* --- 派发错误,监听器尚未绑定时暂存 ---
|
|
159
|
+
* @param error 错误对象
|
|
160
|
+
*/
|
|
161
|
+
private _emitError;
|
|
97
162
|
/** --- 还未开启监听时来的数据将存在这里 --- */
|
|
98
163
|
private readonly _waitMsg;
|
|
164
|
+
/** --- 尚未交给消息监听器的数据量 --- */
|
|
165
|
+
private _waitMsgBytes;
|
|
166
|
+
/** --- 是否暂停向上层派发消息 --- */
|
|
167
|
+
private _paused;
|
|
99
168
|
/** --- 还未开启 error 监听时产生的 error 错误对象 --- */
|
|
100
169
|
private _error;
|
|
101
170
|
/** --- 还未开启 close 监听时是不是就已经 close --- */
|
|
102
|
-
private _close
|
|
171
|
+
private _close?;
|
|
103
172
|
/** --- 绑定的自定义监听事件(未绑定则默认在 _bindEvent 执行) --- */
|
|
104
173
|
private _on;
|
|
174
|
+
/**
|
|
175
|
+
* --- 暂存尚不能交给上层处理的消息 ---
|
|
176
|
+
* @param msg 消息
|
|
177
|
+
*/
|
|
178
|
+
private _queueMessage;
|
|
179
|
+
/** --- 依次派发缓存消息,若再次发生背压则停止 --- */
|
|
180
|
+
private _flushMessages;
|
|
105
181
|
/** --- 绑定监听 --- */
|
|
106
182
|
on(event: 'message', cb: (msg: {
|
|
107
183
|
'opcode': EOpcode;
|
|
108
184
|
'data': Buffer;
|
|
109
185
|
}) => void | Promise<void>): this;
|
|
110
186
|
on(event: 'error', cb: (error: any) => void | Promise<void>): this;
|
|
111
|
-
on(event: '
|
|
187
|
+
on(event: 'close', cb: (info: ISocketCloseInfo) => void | Promise<void>): this;
|
|
188
|
+
on(event: 'drain' | 'end' | 'timeout', cb: () => void | Promise<void>): this;
|
|
112
189
|
/** --- 取消监听 --- */
|
|
113
190
|
off(event: 'message' | 'drain' | 'error' | 'close' | 'end' | 'timeout'): this;
|
|
114
|
-
|
|
191
|
+
/**
|
|
192
|
+
* --- 正常结束 WebSocket 连接 ---
|
|
193
|
+
* @param code WebSocket 关闭码
|
|
194
|
+
* @param reason 关闭原因,超过协议上限时自动安全截断
|
|
195
|
+
*/
|
|
196
|
+
end(code?: number, reason?: string): void;
|
|
115
197
|
destroy(): void;
|
|
198
|
+
/** --- 暂停向消息监听器派发数据,并暂停底层 TCP 读取 --- */
|
|
199
|
+
pause(): void;
|
|
200
|
+
/** --- 恢复派发缓存消息,并恢复底层 TCP 读取 --- */
|
|
201
|
+
resume(): void;
|
|
116
202
|
/** --- 发送文本 --- */
|
|
117
203
|
writeText(data: Buffer | string | Array<Buffer | string>): boolean;
|
|
118
204
|
/** --- 发送结果对象字符串 --- */
|
|
119
205
|
writeResult(data: kebab.Json): boolean;
|
|
120
206
|
/** --- 发送二进制 --- */
|
|
121
207
|
writeBinary(data: string | Buffer | Array<string | Buffer>): boolean;
|
|
208
|
+
/**
|
|
209
|
+
* --- 发送消息并将 ws 的发送缓存转换为 Kebab 的背压布尔值 ---
|
|
210
|
+
* @param data 消息数据
|
|
211
|
+
* @param binary 是否为二进制消息
|
|
212
|
+
* @returns 是否已直接写入底层 Socket 缓存
|
|
213
|
+
*/
|
|
214
|
+
private _send;
|
|
215
|
+
/** --- 发送缓存完全排空后派发 drain --- */
|
|
216
|
+
private _emitDrain;
|
|
122
217
|
/** --- 当前是否是可写状态 --- */
|
|
123
218
|
get writable(): boolean;
|
|
124
219
|
/** --- 当前是否已经结束读取,并且无法继续读取 --- */
|
|
@@ -133,6 +228,13 @@ export declare class Socket {
|
|
|
133
228
|
ping(data?: Buffer | string): boolean;
|
|
134
229
|
/** --- 发送 ping --- */
|
|
135
230
|
pong(data?: Buffer | string): boolean;
|
|
231
|
+
/**
|
|
232
|
+
* --- 发送控制帧 ---
|
|
233
|
+
* @param method 控制帧类型
|
|
234
|
+
* @param data 控制帧数据
|
|
235
|
+
* @returns 是否已直接写入底层 Socket 缓存
|
|
236
|
+
*/
|
|
237
|
+
private _sendControl;
|
|
136
238
|
}
|
|
137
239
|
/**
|
|
138
240
|
* --- 创建一个 ws 客户端发起 ws 请求 ---
|