@maiyunnet/kebab 9.17.1 → 9.17.3
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 +182 -100
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/core.d.ts +10 -0
- package/lib/core.js +51 -0
- package/lib/socket.js +46 -19
- package/lib/undici/response.d.ts +1 -1
- package/lib/undici.d.ts +2 -0
- package/lib/undici.js +13 -2
- package/lib/ws.d.ts +15 -0
- package/lib/ws.js +103 -33
- package/package.json +2 -2
- package/sys/child.js +55 -41
- package/sys/master.js +90 -7
- package/www/example/ctr/test.d.ts +1 -0
- package/www/example/ctr/test.js +26 -2
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '9.17.
|
|
9
|
+
export const VER = '9.17.3';
|
|
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
|
@@ -206,6 +206,15 @@ export declare function sendRestart(hosts?: string[] | 'config'): Promise<Record
|
|
|
206
206
|
'result': boolean;
|
|
207
207
|
'return': string;
|
|
208
208
|
}>>;
|
|
209
|
+
/**
|
|
210
|
+
* --- 向主进程(或局域网同代码机子)发送 stop 操作,停止接收新连接并在现有连接全部结束后退出 ---
|
|
211
|
+
* @param hosts 局域网主机列表,config 表示使用全局配置;不传时通知当前 master
|
|
212
|
+
* @returns 各主机发送结果
|
|
213
|
+
*/
|
|
214
|
+
export declare function sendStop(hosts?: string[] | 'config'): Promise<Record<string, {
|
|
215
|
+
'result': boolean;
|
|
216
|
+
'return': string;
|
|
217
|
+
}>>;
|
|
209
218
|
/** --- PM2 操作类型 --- */
|
|
210
219
|
export type TPm2Action = 'start' | 'stop' | 'restart';
|
|
211
220
|
/**
|
|
@@ -213,6 +222,7 @@ export type TPm2Action = 'start' | 'stop' | 'restart';
|
|
|
213
222
|
* @param name PM2 进程名称
|
|
214
223
|
* @param action PM2 操作类型
|
|
215
224
|
* @param hosts 局域网列表
|
|
225
|
+
* @returns 各主机是否已接收并排队,不代表 PM2 操作最终执行成功
|
|
216
226
|
*/
|
|
217
227
|
export declare function sendPm2(name: string, action?: TPm2Action, hosts?: string[] | 'config'): Promise<Record<string, {
|
|
218
228
|
'result': boolean;
|
package/lib/core.js
CHANGED
|
@@ -713,11 +713,62 @@ export async function sendRestart(hosts) {
|
|
|
713
713
|
}
|
|
714
714
|
return rtn;
|
|
715
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* --- 向主进程(或局域网同代码机子)发送 stop 操作,停止接收新连接并在现有连接全部结束后退出 ---
|
|
718
|
+
* @param hosts 局域网主机列表,config 表示使用全局配置;不传时通知当前 master
|
|
719
|
+
* @returns 各主机发送结果
|
|
720
|
+
*/
|
|
721
|
+
export async function sendStop(hosts) {
|
|
722
|
+
if (!hosts) {
|
|
723
|
+
// --- 本地模式 ---
|
|
724
|
+
// eslint-disable-next-line no-console
|
|
725
|
+
console.log('[ Child] Sending stop request...');
|
|
726
|
+
process.send({
|
|
727
|
+
'action': 'stop'
|
|
728
|
+
});
|
|
729
|
+
return {
|
|
730
|
+
'127.0.0.1': { 'result': true, 'return': 'Done' }
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (hosts === 'config') {
|
|
734
|
+
hosts = globalConfig.hosts;
|
|
735
|
+
}
|
|
736
|
+
// --- 未传或 config 展开后为空数组,均回退到本机 ---
|
|
737
|
+
if (!hosts?.length) {
|
|
738
|
+
hosts = ['127.0.0.1'];
|
|
739
|
+
}
|
|
740
|
+
// --- 局域网模式 ---
|
|
741
|
+
const time = lTime.stamp();
|
|
742
|
+
/** --- 返回成功的 host --- */
|
|
743
|
+
const rtn = {};
|
|
744
|
+
for (const host of hosts) {
|
|
745
|
+
const res = await lUndici.get('http://' + host + ':' + globalConfig.rpcPort.toString() + '/' + lCrypto.aesEncrypt(lText.stringifyJson({
|
|
746
|
+
'action': 'stop',
|
|
747
|
+
'time': time
|
|
748
|
+
}), globalConfig.rpcSecret), {
|
|
749
|
+
'timeout': 5
|
|
750
|
+
});
|
|
751
|
+
const content = await res.getContent();
|
|
752
|
+
if (!content) {
|
|
753
|
+
rtn[host] = { 'result': false, 'return': 'Timeout' };
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
const str = content.toString();
|
|
757
|
+
if (str === 'Done') {
|
|
758
|
+
rtn[host] = { 'result': true, 'return': 'Done' };
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
rtn[host] = { 'result': false, 'return': str };
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return rtn;
|
|
765
|
+
}
|
|
716
766
|
/**
|
|
717
767
|
* --- 向本机或局域网 RPC 发送 PM2 操作 ---
|
|
718
768
|
* @param name PM2 进程名称
|
|
719
769
|
* @param action PM2 操作类型
|
|
720
770
|
* @param hosts 局域网列表
|
|
771
|
+
* @returns 各主机是否已接收并排队,不代表 PM2 操作最终执行成功
|
|
721
772
|
*/
|
|
722
773
|
export async function sendPm2(name, action = 'restart', hosts) {
|
|
723
774
|
if (hosts === 'config') {
|
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.d.ts
CHANGED
|
@@ -133,6 +133,8 @@ export interface IRequestOptions {
|
|
|
133
133
|
'retry'?: number;
|
|
134
134
|
/** --- JSON 解析失败后的重试次数,默认 0;仅适用于 ResponseJson 快捷方法,非幂等请求需由调用方保证安全 --- */
|
|
135
135
|
'retryJson'?: number;
|
|
136
|
+
/** --- JSON 重试验证方法;返回 false 时按 retryJson 的设置重试,仅适用于 ResponseJson 快捷方法 --- */
|
|
137
|
+
'retryJsonHandler'?: (json: kebab.Json) => boolean;
|
|
136
138
|
/** --- 追踪 location 次数,0 为不追踪,默认为 0 --- */
|
|
137
139
|
'follow'?: number;
|
|
138
140
|
/** --- 自定义 host 映射,如 {'www.maiyun.net': '127.0.0.1'},或全部映射到一个 host --- */
|
package/lib/undici.js
CHANGED
|
@@ -152,7 +152,7 @@ async function requestResponseJson(u, data, opt, action) {
|
|
|
152
152
|
}
|
|
153
153
|
const rtnStr = rtn.toString();
|
|
154
154
|
const json = lText.parseJson(rtnStr);
|
|
155
|
-
if (json) {
|
|
155
|
+
if (json && (opt.retryJsonHandler?.(json) ?? true)) {
|
|
156
156
|
return json;
|
|
157
157
|
}
|
|
158
158
|
if (i < retryJson) {
|
|
@@ -162,7 +162,8 @@ async function requestResponseJson(u, data, opt, action) {
|
|
|
162
162
|
}
|
|
163
163
|
if (opt.log === undefined || opt.log) {
|
|
164
164
|
const requestData = data === undefined ? '' : `, data: ${lText.stringifyJson(data)}`;
|
|
165
|
-
|
|
165
|
+
const reason = json ? 'retry json handler returned false' : 'parse json failed';
|
|
166
|
+
lCore.log({}, `[UNDICI][${action}] ${reason}, url: ${u}${requestData}, content: ${rtnStr}`, '-neterror');
|
|
166
167
|
}
|
|
167
168
|
return false;
|
|
168
169
|
}
|
|
@@ -635,10 +636,14 @@ export async function mproxy(ctr, auth, opt = {}) {
|
|
|
635
636
|
});
|
|
636
637
|
// --- 同时监听 close 确保 promise 一定会 resolve(客户端断连等场景) ---
|
|
637
638
|
stream.pipe(res).on('close', () => {
|
|
639
|
+
stream.destroy();
|
|
638
640
|
resolve();
|
|
639
641
|
});
|
|
640
642
|
});
|
|
641
643
|
}
|
|
644
|
+
else {
|
|
645
|
+
stream.destroy();
|
|
646
|
+
}
|
|
642
647
|
return 1;
|
|
643
648
|
}
|
|
644
649
|
/**
|
|
@@ -738,16 +743,22 @@ export async function rproxy(ctr, route, opt = {}) {
|
|
|
738
743
|
// --- 同时监听 close 确保 promise 一定会 resolve(客户端断连等场景) ---
|
|
739
744
|
if (compress) {
|
|
740
745
|
stream.pipe(compress.compress).pipe(res).on('close', () => {
|
|
746
|
+
stream.destroy();
|
|
747
|
+
compress.compress.destroy();
|
|
741
748
|
resolve();
|
|
742
749
|
});
|
|
743
750
|
}
|
|
744
751
|
else {
|
|
745
752
|
stream.pipe(res).on('close', () => {
|
|
753
|
+
stream.destroy();
|
|
746
754
|
resolve();
|
|
747
755
|
});
|
|
748
756
|
}
|
|
749
757
|
});
|
|
750
758
|
}
|
|
759
|
+
else {
|
|
760
|
+
stream.destroy();
|
|
761
|
+
}
|
|
751
762
|
return true;
|
|
752
763
|
}
|
|
753
764
|
return false;
|
package/lib/ws.d.ts
CHANGED
|
@@ -96,12 +96,23 @@ export declare class Socket {
|
|
|
96
96
|
private _bindEvent;
|
|
97
97
|
/** --- 还未开启监听时来的数据将存在这里 --- */
|
|
98
98
|
private readonly _waitMsg;
|
|
99
|
+
/** --- 尚未交给消息监听器的数据量 --- */
|
|
100
|
+
private _waitMsgBytes;
|
|
101
|
+
/** --- 是否暂停向上层派发消息 --- */
|
|
102
|
+
private _paused;
|
|
99
103
|
/** --- 还未开启 error 监听时产生的 error 错误对象 --- */
|
|
100
104
|
private _error;
|
|
101
105
|
/** --- 还未开启 close 监听时是不是就已经 close --- */
|
|
102
106
|
private _close;
|
|
103
107
|
/** --- 绑定的自定义监听事件(未绑定则默认在 _bindEvent 执行) --- */
|
|
104
108
|
private _on;
|
|
109
|
+
/**
|
|
110
|
+
* --- 暂存尚不能交给上层处理的消息 ---
|
|
111
|
+
* @param msg 消息
|
|
112
|
+
*/
|
|
113
|
+
private _queueMessage;
|
|
114
|
+
/** --- 依次派发缓存消息,若再次发生背压则停止 --- */
|
|
115
|
+
private _flushMessages;
|
|
105
116
|
/** --- 绑定监听 --- */
|
|
106
117
|
on(event: 'message', cb: (msg: {
|
|
107
118
|
'opcode': EOpcode;
|
|
@@ -113,6 +124,10 @@ export declare class Socket {
|
|
|
113
124
|
off(event: 'message' | 'drain' | 'error' | 'close' | 'end' | 'timeout'): this;
|
|
114
125
|
end(): void;
|
|
115
126
|
destroy(): void;
|
|
127
|
+
/** --- 暂停向消息监听器派发数据,底层仍只保留有界缓存 --- */
|
|
128
|
+
pause(): void;
|
|
129
|
+
/** --- 恢复向消息监听器派发数据 --- */
|
|
130
|
+
resume(): void;
|
|
116
131
|
/** --- 发送文本 --- */
|
|
117
132
|
writeText(data: Buffer | string | Array<Buffer | string>): boolean;
|
|
118
133
|
/** --- 发送结果对象字符串 --- */
|
package/lib/ws.js
CHANGED
|
@@ -24,6 +24,10 @@ export var EOpcode;
|
|
|
24
24
|
const liwsServer = liws.createServer({
|
|
25
25
|
'frameReceiveMode': EFrameReceiveMode.SIMPLE,
|
|
26
26
|
});
|
|
27
|
+
/** --- 未能及时转发的 WebSocket 消息最大缓存量,超过后关闭连接保护进程内存 --- */
|
|
28
|
+
const MAX_PENDING_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
29
|
+
/** --- 未能及时转发的 WebSocket 消息最大条数,防止大量小帧占满内存 --- */
|
|
30
|
+
const MAX_PENDING_MESSAGES = 4_096;
|
|
27
31
|
export class Socket {
|
|
28
32
|
/** --- 当前的 ws 对象 --- */
|
|
29
33
|
_ws;
|
|
@@ -132,19 +136,15 @@ export class Socket {
|
|
|
132
136
|
if (!('data' in msg)) {
|
|
133
137
|
return;
|
|
134
138
|
}
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
else {
|
|
143
|
-
this._waitMsg.push({
|
|
144
|
-
'opcode': msg.opcode,
|
|
145
|
-
'data': buf,
|
|
146
|
-
});
|
|
139
|
+
const item = {
|
|
140
|
+
'opcode': msg.opcode,
|
|
141
|
+
'data': Buffer.concat(msg.data),
|
|
142
|
+
};
|
|
143
|
+
if (this._paused || !this._on.message) {
|
|
144
|
+
this._queueMessage(item);
|
|
145
|
+
return;
|
|
147
146
|
}
|
|
147
|
+
this._on.message(item);
|
|
148
148
|
}).on('drain', () => {
|
|
149
149
|
this._on.drain?.();
|
|
150
150
|
}).on('error', (e) => {
|
|
@@ -157,6 +157,8 @@ export class Socket {
|
|
|
157
157
|
}).on('end', () => {
|
|
158
158
|
this._on.end?.();
|
|
159
159
|
}).on('close', () => {
|
|
160
|
+
this._waitMsg.length = 0;
|
|
161
|
+
this._waitMsgBytes = 0;
|
|
160
162
|
if (this._on.close) {
|
|
161
163
|
this._on.close();
|
|
162
164
|
}
|
|
@@ -169,6 +171,10 @@ export class Socket {
|
|
|
169
171
|
}
|
|
170
172
|
/** --- 还未开启监听时来的数据将存在这里 --- */
|
|
171
173
|
_waitMsg = [];
|
|
174
|
+
/** --- 尚未交给消息监听器的数据量 --- */
|
|
175
|
+
_waitMsgBytes = 0;
|
|
176
|
+
/** --- 是否暂停向上层派发消息 --- */
|
|
177
|
+
_paused = false;
|
|
172
178
|
/** --- 还未开启 error 监听时产生的 error 错误对象 --- */
|
|
173
179
|
_error = null;
|
|
174
180
|
/** --- 还未开启 close 监听时是不是就已经 close --- */
|
|
@@ -183,13 +189,44 @@ export class Socket {
|
|
|
183
189
|
end: undefined,
|
|
184
190
|
timeout: undefined,
|
|
185
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* --- 暂存尚不能交给上层处理的消息 ---
|
|
194
|
+
* @param msg 消息
|
|
195
|
+
*/
|
|
196
|
+
_queueMessage(msg) {
|
|
197
|
+
this._waitMsgBytes += msg.data.length;
|
|
198
|
+
if ((this._waitMsgBytes > MAX_PENDING_MESSAGE_BYTES) ||
|
|
199
|
+
(this._waitMsg.length >= MAX_PENDING_MESSAGES)) {
|
|
200
|
+
this._waitMsg.length = 0;
|
|
201
|
+
this._waitMsgBytes = 0;
|
|
202
|
+
this.destroy();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this._waitMsg.push(msg);
|
|
206
|
+
}
|
|
207
|
+
/** --- 依次派发缓存消息,若再次发生背压则停止 --- */
|
|
208
|
+
_flushMessages() {
|
|
209
|
+
if (this._paused || !this._on.message || !this._waitMsg.length) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const messages = this._waitMsg.splice(0);
|
|
213
|
+
this._waitMsgBytes = 0;
|
|
214
|
+
for (let i = 0; i < messages.length; ++i) {
|
|
215
|
+
if (this._paused) {
|
|
216
|
+
for (; i < messages.length; ++i) {
|
|
217
|
+
this._waitMsg.push(messages[i]);
|
|
218
|
+
this._waitMsgBytes += messages[i].data.length;
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this._on.message(messages[i]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
186
225
|
on(event, cb) {
|
|
187
226
|
this._on[event] = cb;
|
|
188
227
|
switch (event) {
|
|
189
228
|
case 'message': {
|
|
190
|
-
|
|
191
|
-
cb(item);
|
|
192
|
-
}
|
|
229
|
+
this._flushMessages();
|
|
193
230
|
break;
|
|
194
231
|
}
|
|
195
232
|
case 'error': {
|
|
@@ -227,6 +264,15 @@ export class Socket {
|
|
|
227
264
|
destroy() {
|
|
228
265
|
this._ws.destroy();
|
|
229
266
|
}
|
|
267
|
+
/** --- 暂停向消息监听器派发数据,底层仍只保留有界缓存 --- */
|
|
268
|
+
pause() {
|
|
269
|
+
this._paused = true;
|
|
270
|
+
}
|
|
271
|
+
/** --- 恢复向消息监听器派发数据 --- */
|
|
272
|
+
resume() {
|
|
273
|
+
this._paused = false;
|
|
274
|
+
this._flushMessages();
|
|
275
|
+
}
|
|
230
276
|
/** --- 发送文本 --- */
|
|
231
277
|
writeText(data) {
|
|
232
278
|
if (!this._ws.writable) {
|
|
@@ -311,69 +357,93 @@ export function createServer(request, socket, head, options = {}) {
|
|
|
311
357
|
*/
|
|
312
358
|
function bindPipe(s1, s2) {
|
|
313
359
|
return new Promise(resolve => {
|
|
360
|
+
/** --- 是否已经完成关闭,防止双向 close 重复处理 --- */
|
|
361
|
+
let closed = false;
|
|
362
|
+
/** --- 两端一起销毁,避免半开连接继续占用资源 --- */
|
|
363
|
+
const close = () => {
|
|
364
|
+
if (closed) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
closed = true;
|
|
368
|
+
s1.destroy();
|
|
369
|
+
s2.destroy();
|
|
370
|
+
resolve();
|
|
371
|
+
};
|
|
314
372
|
// --- 监听发送端的 ---
|
|
315
373
|
s1.on('message', (msg) => {
|
|
316
374
|
switch (msg.opcode) {
|
|
317
375
|
case EOpcode.TEXT: {
|
|
318
|
-
s2.writeText(msg.data.toString())
|
|
376
|
+
if (!s2.writeText(msg.data.toString())) {
|
|
377
|
+
s1.pause();
|
|
378
|
+
}
|
|
319
379
|
break;
|
|
320
380
|
}
|
|
321
381
|
case EOpcode.BINARY: {
|
|
322
|
-
s2.writeBinary(msg.data)
|
|
382
|
+
if (!s2.writeBinary(msg.data)) {
|
|
383
|
+
s1.pause();
|
|
384
|
+
}
|
|
323
385
|
break;
|
|
324
386
|
}
|
|
325
387
|
case EOpcode.CLOSE: {
|
|
326
|
-
|
|
327
|
-
resolve();
|
|
388
|
+
close();
|
|
328
389
|
break;
|
|
329
390
|
}
|
|
330
391
|
case EOpcode.PING: {
|
|
331
|
-
s2.ping(msg.data)
|
|
392
|
+
if (!s2.ping(msg.data)) {
|
|
393
|
+
s1.pause();
|
|
394
|
+
}
|
|
332
395
|
break;
|
|
333
396
|
}
|
|
334
397
|
case EOpcode.PONG: {
|
|
335
|
-
s2.pong(msg.data)
|
|
398
|
+
if (!s2.pong(msg.data)) {
|
|
399
|
+
s1.pause();
|
|
400
|
+
}
|
|
336
401
|
break;
|
|
337
402
|
}
|
|
338
403
|
default: {
|
|
339
404
|
// --- EOpcode.CONTINUATION ---
|
|
340
405
|
}
|
|
341
406
|
}
|
|
342
|
-
}).on('close', () => {
|
|
343
|
-
s2.
|
|
344
|
-
resolve();
|
|
407
|
+
}).on('close', close).on('drain', () => {
|
|
408
|
+
s2.resume();
|
|
345
409
|
});
|
|
346
410
|
// --- 监听远程端的 ---
|
|
347
411
|
s2.on('message', (msg) => {
|
|
348
412
|
switch (msg.opcode) {
|
|
349
413
|
case EOpcode.TEXT: {
|
|
350
|
-
s1.writeText(msg.data.toString())
|
|
414
|
+
if (!s1.writeText(msg.data.toString())) {
|
|
415
|
+
s2.pause();
|
|
416
|
+
}
|
|
351
417
|
break;
|
|
352
418
|
}
|
|
353
419
|
case EOpcode.BINARY: {
|
|
354
|
-
s1.writeBinary(msg.data)
|
|
420
|
+
if (!s1.writeBinary(msg.data)) {
|
|
421
|
+
s2.pause();
|
|
422
|
+
}
|
|
355
423
|
break;
|
|
356
424
|
}
|
|
357
425
|
case EOpcode.CLOSE: {
|
|
358
|
-
|
|
359
|
-
resolve();
|
|
426
|
+
close();
|
|
360
427
|
break;
|
|
361
428
|
}
|
|
362
429
|
case EOpcode.PING: {
|
|
363
|
-
s1.ping(msg.data)
|
|
430
|
+
if (!s1.ping(msg.data)) {
|
|
431
|
+
s2.pause();
|
|
432
|
+
}
|
|
364
433
|
break;
|
|
365
434
|
}
|
|
366
435
|
case EOpcode.PONG: {
|
|
367
|
-
s1.pong(msg.data)
|
|
436
|
+
if (!s1.pong(msg.data)) {
|
|
437
|
+
s2.pause();
|
|
438
|
+
}
|
|
368
439
|
break;
|
|
369
440
|
}
|
|
370
441
|
default: {
|
|
371
442
|
// --- EOpcode.CONTINUATION ---
|
|
372
443
|
}
|
|
373
444
|
}
|
|
374
|
-
}).on('close', () => {
|
|
375
|
-
s1.
|
|
376
|
-
resolve();
|
|
445
|
+
}).on('close', close).on('drain', () => {
|
|
446
|
+
s1.resume();
|
|
377
447
|
});
|
|
378
448
|
});
|
|
379
449
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maiyunnet/kebab",
|
|
3
|
-
"version": "9.17.
|
|
3
|
+
"version": "9.17.3",
|
|
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": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@tailwindcss/cli": "^4.3.3",
|
|
36
36
|
"@types/ssh2": "^1.15.5",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
|
-
"ajv-formats": "^3.0.1",
|
|
38
|
+
"ajv-formats": "^3.0.1",
|
|
39
39
|
"class-variance-authority": "^0.7.1",
|
|
40
40
|
"clsx": "^2.1.1",
|
|
41
41
|
"ejs": "^6.0.1",
|