@feng3d/cts 0.0.9 → 0.0.11
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/README.md +12 -3
- package/dist/data-channel.d.ts +93 -0
- package/dist/data-channel.d.ts.map +1 -0
- package/dist/data-channel.js +211 -0
- package/dist/data-channel.js.map +1 -0
- package/dist/handlers/control-handler.d.ts +10 -122
- package/dist/handlers/control-handler.d.ts.map +1 -1
- package/dist/handlers/control-handler.js +14 -164
- package/dist/handlers/control-handler.js.map +1 -1
- package/dist/handlers/unified-proxy.d.ts +40 -157
- package/dist/handlers/unified-proxy.d.ts.map +1 -1
- package/dist/handlers/unified-proxy.js +221 -355
- package/dist/handlers/unified-proxy.js.map +1 -1
- package/dist/server.d.ts +24 -68
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +144 -251
- package/dist/server.js.map +1 -1
- package/dist/session-manager.d.ts +2 -2
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +1 -1
- package/dist/session-manager.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,82 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module unified-proxy
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* 统一代理处理器模块。
|
|
5
|
+
*
|
|
6
|
+
* 在指定端口上同时支持 HTTP、WebSocket、TCP 和 UDP 代理。
|
|
7
|
+
* 所有 TCP 类协议(HTTP/WS/TCP)作为原始字节流转发,不做协议解析,
|
|
8
|
+
* HTTP 协议由外部客户端和本地服务端到端处理。
|
|
9
|
+
* UDP 使用基于会话的转发,30 秒超时自动清理。
|
|
5
10
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
11
|
+
import { createServer } from 'net';
|
|
12
|
+
import { createSocket as createUdpSocket } from 'dgram';
|
|
13
|
+
import { WebSocket } from 'ws';
|
|
8
14
|
import { v4 as uuidv4 } from 'uuid';
|
|
9
15
|
import { MessageType, createMessage, logger } from '@feng3d/chuantou-shared';
|
|
16
|
+
/** UDP 会话超时时间(毫秒) */
|
|
17
|
+
const UDP_SESSION_TIMEOUT = 30000;
|
|
10
18
|
/**
|
|
11
19
|
* 统一代理处理器
|
|
12
20
|
*
|
|
13
|
-
*
|
|
14
|
-
* 同时支持
|
|
15
|
-
*
|
|
21
|
+
* 管理多个代理服务器实例,每个实例监听一个独立端口,
|
|
22
|
+
* 同时支持 TCP 类协议和 UDP 协议。
|
|
23
|
+
* 数据通过独立的二进制数据通道传输,不经过 WebSocket 控制通道。
|
|
16
24
|
*/
|
|
17
25
|
export class UnifiedProxyHandler {
|
|
18
|
-
|
|
19
|
-
* 创建统一代理处理器实例
|
|
20
|
-
*
|
|
21
|
-
* @param sessionManager - 会话管理器,用于获取客户端连接和管理连接记录
|
|
22
|
-
*/
|
|
23
|
-
constructor(sessionManager) {
|
|
24
|
-
/**
|
|
25
|
-
* 待处理响应映射表
|
|
26
|
-
*
|
|
27
|
-
* 存储等待客户端响应的 Promise 回调和超时定时器,键为连接 ID。
|
|
28
|
-
*/
|
|
29
|
-
this.pendingResponses = new Map();
|
|
30
|
-
/**
|
|
31
|
-
* 用户 WebSocket 连接映射表
|
|
32
|
-
*
|
|
33
|
-
* 存储外部用户的 WebSocket 连接,键为连接 ID,值为用户的 WebSocket 实例。
|
|
34
|
-
*/
|
|
35
|
-
this.userConnections = new Map();
|
|
36
|
-
/**
|
|
37
|
-
* 流式 HTTP 响应映射表
|
|
38
|
-
*
|
|
39
|
-
* 存储正在进行的流式响应(如 SSE)的响应对象。
|
|
40
|
-
*/
|
|
41
|
-
this.streamingResponses = new Map();
|
|
42
|
-
this.sessionManager = sessionManager;
|
|
26
|
+
constructor(sessionManager, dataChannelManager) {
|
|
43
27
|
this.proxies = new Map();
|
|
44
|
-
|
|
28
|
+
/** 外部 TCP 连接映射表:connectionId → Socket */
|
|
29
|
+
this.userSockets = new Map();
|
|
30
|
+
/** UDP 会话映射表:sessionKey (addr:port) → UdpSession */
|
|
31
|
+
this.udpSessions = new Map();
|
|
32
|
+
/** connectionId → UDP 会话 key,用于反向查找 */
|
|
33
|
+
this.udpConnectionToSession = new Map();
|
|
34
|
+
this.sessionManager = sessionManager;
|
|
35
|
+
this.dataChannelManager = dataChannelManager;
|
|
36
|
+
// 监听来自客户端的 TCP 数据帧
|
|
37
|
+
this.dataChannelManager.on('data', (_clientId, connectionId, data) => {
|
|
38
|
+
this.handleDataFromClient(connectionId, data);
|
|
39
|
+
});
|
|
40
|
+
// 监听来自客户端的 UDP 数据帧
|
|
41
|
+
this.dataChannelManager.on('udpData', (_clientId, connectionId, data) => {
|
|
42
|
+
this.handleUdpDataFromClient(connectionId, data);
|
|
43
|
+
});
|
|
45
44
|
}
|
|
46
45
|
/**
|
|
47
46
|
* 启动代理服务器
|
|
48
47
|
*
|
|
49
|
-
*
|
|
50
|
-
* 若该端口已存在代理或端口被占用,将抛出异常。
|
|
51
|
-
*
|
|
52
|
-
* @param port - 代理监听的端口号
|
|
53
|
-
* @param clientId - 绑定的客户端唯一标识 ID
|
|
54
|
-
* @returns 代理服务器启动完成的 Promise
|
|
55
|
-
* @throws 端口已存在代理或端口被占用时抛出错误
|
|
48
|
+
* 在指定端口创建 TCP 服务器和 UDP socket,同时支持所有协议。
|
|
56
49
|
*/
|
|
57
50
|
async startProxy(port, clientId) {
|
|
58
51
|
if (this.proxies.has(port)) {
|
|
59
52
|
throw new Error(`端口 ${port} 的代理已存在`);
|
|
60
53
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
const tcpServer = createServer({ pauseOnConnect: true });
|
|
55
|
+
const udpSocket = createUdpSocket('udp4');
|
|
56
|
+
// TCP 连接处理
|
|
57
|
+
tcpServer.on('connection', (socket) => {
|
|
58
|
+
this.handleNewTcpConnection(clientId, port, socket);
|
|
59
|
+
});
|
|
60
|
+
tcpServer.on('error', (error) => {
|
|
61
|
+
logger.error(`代理在端口 ${port} 上 TCP 错误:`, error);
|
|
65
62
|
});
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// UDP 消息处理
|
|
64
|
+
udpSocket.on('message', (msg, rinfo) => {
|
|
65
|
+
this.handleUdpMessage(clientId, port, msg, rinfo);
|
|
69
66
|
});
|
|
70
|
-
|
|
71
|
-
logger.error(`代理在端口 ${port}
|
|
67
|
+
udpSocket.on('error', (error) => {
|
|
68
|
+
logger.error(`代理在端口 ${port} 上 UDP 错误:`, error);
|
|
72
69
|
});
|
|
73
70
|
return new Promise((resolve, reject) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
tcpServer.listen(port, () => {
|
|
72
|
+
const actualPort = tcpServer.address().port;
|
|
73
|
+
udpSocket.bind(actualPort, () => {
|
|
74
|
+
logger.log(`代理正在端口 ${actualPort} 上监听(TCP + UDP),绑定客户端 ${clientId}`);
|
|
75
|
+
this.proxies.set(actualPort, { tcpServer, udpSocket, port: actualPort });
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
udpSocket.on('error', (error) => {
|
|
79
|
+
tcpServer.close();
|
|
80
|
+
reject(error);
|
|
81
|
+
});
|
|
78
82
|
});
|
|
79
|
-
|
|
83
|
+
tcpServer.on('error', (error) => {
|
|
80
84
|
if (error.code === 'EADDRINUSE') {
|
|
81
85
|
reject(new Error(`端口 ${port} 已被占用`));
|
|
82
86
|
}
|
|
@@ -87,374 +91,238 @@ export class UnifiedProxyHandler {
|
|
|
87
91
|
});
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
|
-
*
|
|
94
|
+
* 处理新的 TCP 连接
|
|
91
95
|
*
|
|
92
|
-
*
|
|
93
|
-
* 支持流式响应(如 SSE)和普通 HTTP 响应。
|
|
94
|
-
*
|
|
95
|
-
* @param clientId - 处理该请求的客户端唯一标识 ID
|
|
96
|
-
* @param req - 收到的 HTTP 请求对象
|
|
97
|
-
* @param res - 用于发送响应的 HTTP 响应对象
|
|
96
|
+
* 读取首字节检测协议类型(仅用于日志),然后建立字节管道。
|
|
98
97
|
*/
|
|
99
|
-
|
|
98
|
+
handleNewTcpConnection(clientId, port, socket) {
|
|
100
99
|
const connectionId = uuidv4();
|
|
101
100
|
const clientSocket = this.sessionManager.getClientSocket(clientId);
|
|
101
|
+
const remoteAddress = socket.remoteAddress || '';
|
|
102
102
|
if (!clientSocket || clientSocket.readyState !== WebSocket.OPEN) {
|
|
103
|
-
|
|
104
|
-
res.end('网关错误:客户端未连接');
|
|
103
|
+
socket.destroy();
|
|
105
104
|
return;
|
|
106
105
|
}
|
|
107
|
-
//
|
|
108
|
-
this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let body;
|
|
120
|
-
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
121
|
-
body = await this.readRequestBody(req);
|
|
122
|
-
}
|
|
123
|
-
// 发送新连接消息给客户端
|
|
124
|
-
const newConnMsg = createMessage(MessageType.NEW_CONNECTION, {
|
|
125
|
-
connectionId,
|
|
126
|
-
protocol: 'http',
|
|
127
|
-
method: req.method,
|
|
128
|
-
url: req.url || '/',
|
|
129
|
-
headers,
|
|
130
|
-
body: body?.toString('base64'),
|
|
131
|
-
});
|
|
132
|
-
clientSocket.send(JSON.stringify(newConnMsg));
|
|
133
|
-
// 等待客户端响应头(第一个消息)
|
|
134
|
-
const firstResponse = await this.waitForResponse(connectionId, clientId);
|
|
135
|
-
// 检查是否为流式响应(仅根据 Content-Type 判断)
|
|
136
|
-
const contentType = String(firstResponse.headers['content-type'] || '').toLowerCase();
|
|
137
|
-
const isStreaming = contentType.includes('text/event-stream') ||
|
|
138
|
-
contentType.includes('stream');
|
|
139
|
-
if (isStreaming) {
|
|
140
|
-
// 流式响应模式(SSE)
|
|
141
|
-
logger.log(`HTTP 流式响应: ${req.url} (${connectionId})`);
|
|
142
|
-
this.streamingResponses.set(connectionId, res);
|
|
143
|
-
// 发送响应头
|
|
144
|
-
res.writeHead(firstResponse.statusCode, firstResponse.headers);
|
|
145
|
-
res.flushHeaders();
|
|
146
|
-
// 如果有初始 body,先发送
|
|
147
|
-
if (firstResponse.body) {
|
|
148
|
-
const bodyBuffer = Buffer.isBuffer(firstResponse.body)
|
|
149
|
-
? firstResponse.body
|
|
150
|
-
: Buffer.from(firstResponse.body, 'base64');
|
|
151
|
-
res.write(bodyBuffer);
|
|
152
|
-
}
|
|
153
|
-
// 等待流结束,不清理连接(由流结束事件处理)
|
|
154
|
-
this.pendingResponses.delete(connectionId);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
// 普通 HTTP 响应模式
|
|
158
|
-
res.writeHead(firstResponse.statusCode, firstResponse.headers);
|
|
159
|
-
if (firstResponse.body) {
|
|
160
|
-
const bodyBuffer = Buffer.isBuffer(firstResponse.body)
|
|
161
|
-
? firstResponse.body
|
|
162
|
-
: Buffer.from(firstResponse.body, 'base64');
|
|
163
|
-
res.end(bodyBuffer);
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
res.end();
|
|
167
|
-
}
|
|
168
|
-
logger.log(`HTTP 响应: ${firstResponse.statusCode},连接 ${connectionId}`);
|
|
169
|
-
// 清理连接
|
|
170
|
-
this.sessionManager.removeConnection(connectionId);
|
|
171
|
-
this.pendingResponses.delete(connectionId);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
logger.error(`处理 HTTP 请求 ${connectionId} 时出错:`, error);
|
|
176
|
-
if (!res.headersSent) {
|
|
177
|
-
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
178
|
-
res.end('服务器内部错误');
|
|
106
|
+
// 存储外部连接
|
|
107
|
+
this.userSockets.set(connectionId, socket);
|
|
108
|
+
// 恢复 socket,读取首字节检测协议
|
|
109
|
+
socket.once('readable', () => {
|
|
110
|
+
const data = socket.read(Math.min(socket.readableLength || 1024, 1024));
|
|
111
|
+
if (!data) {
|
|
112
|
+
socket.once('data', (firstData) => {
|
|
113
|
+
const protocol = this.detectProtocol(firstData);
|
|
114
|
+
this.setupConnection(clientId, port, connectionId, socket, remoteAddress, protocol, firstData);
|
|
115
|
+
});
|
|
116
|
+
socket.resume();
|
|
117
|
+
return;
|
|
179
118
|
}
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
-
this.streamingResponses.delete(connectionId);
|
|
184
|
-
}
|
|
119
|
+
const protocol = this.detectProtocol(data);
|
|
120
|
+
this.setupConnection(clientId, port, connectionId, socket, remoteAddress, protocol, data);
|
|
121
|
+
});
|
|
185
122
|
}
|
|
186
123
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* 将外部 WebSocket 连接桥接到穿透客户端,实现双向实时数据转发。
|
|
190
|
-
*
|
|
191
|
-
* @param clientId - 处理该连接的客户端唯一标识 ID
|
|
192
|
-
* @param req - HTTP 升级请求对象
|
|
193
|
-
* @param socket - 底层 socket 连接
|
|
194
|
-
* @param head - 升级请求的头部 buffer
|
|
124
|
+
* 建立连接:发送 NEW_CONNECTION 通知,并设置双向数据管道
|
|
195
125
|
*/
|
|
196
|
-
|
|
197
|
-
const connectionId = uuidv4();
|
|
126
|
+
setupConnection(clientId, port, connectionId, socket, remoteAddress, protocol, initialData) {
|
|
198
127
|
const clientSocket = this.sessionManager.getClientSocket(clientId);
|
|
199
128
|
if (!clientSocket || clientSocket.readyState !== WebSocket.OPEN) {
|
|
200
|
-
socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
|
|
201
129
|
socket.destroy();
|
|
130
|
+
this.userSockets.delete(connectionId);
|
|
202
131
|
return;
|
|
203
132
|
}
|
|
204
133
|
// 记录连接
|
|
205
|
-
this.sessionManager.addConnection(clientId, connectionId,
|
|
206
|
-
logger.log(
|
|
207
|
-
//
|
|
208
|
-
const wsServer = new WebSocketServer({ noServer: true });
|
|
209
|
-
wsServer.handleUpgrade(req, socket, head, (ws) => {
|
|
210
|
-
this.handleWebSocketConnection(clientId, connectionId, ws, req);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* 处理 WebSocket 连接
|
|
215
|
-
*
|
|
216
|
-
* 当外部用户连接到代理端口时,记录连接信息,通知穿透客户端有新连接到来,
|
|
217
|
-
* 并设置消息转发、关闭和错误的事件处理。
|
|
218
|
-
*
|
|
219
|
-
* @param clientId - 处理该连接的客户端唯一标识 ID
|
|
220
|
-
* @param connectionId - 连接唯一标识 ID
|
|
221
|
-
* @param userWs - 外部用户的 WebSocket 连接
|
|
222
|
-
* @param req - HTTP 升级请求对象
|
|
223
|
-
*/
|
|
224
|
-
handleWebSocketConnection(clientId, connectionId, userWs, req) {
|
|
225
|
-
const clientSocket = this.sessionManager.getClientSocket(clientId);
|
|
226
|
-
if (!clientSocket || clientSocket.readyState !== WebSocket.OPEN) {
|
|
227
|
-
userWs.close(1011, '客户端未连接');
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
logger.log(`WebSocket 连接已建立: ${req.url} (${connectionId})`);
|
|
231
|
-
// 存储用户 WebSocket 引用
|
|
232
|
-
this.userConnections.set(connectionId, userWs);
|
|
233
|
-
// 构建请求头
|
|
234
|
-
const headers = {};
|
|
235
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
236
|
-
if (value !== undefined) {
|
|
237
|
-
headers[key] = Array.isArray(value) ? value.join(', ') : value;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
// 发送新连接消息给客户端
|
|
134
|
+
this.sessionManager.addConnection(clientId, connectionId, remoteAddress, protocol);
|
|
135
|
+
logger.log(`${protocol.toUpperCase()} 连接: ${remoteAddress} -> :${port} (${connectionId})`);
|
|
136
|
+
// 通过 WebSocket 控制通道发送 NEW_CONNECTION
|
|
241
137
|
const newConnMsg = createMessage(MessageType.NEW_CONNECTION, {
|
|
242
138
|
connectionId,
|
|
243
|
-
protocol
|
|
244
|
-
|
|
245
|
-
|
|
139
|
+
protocol,
|
|
140
|
+
remotePort: port,
|
|
141
|
+
remoteAddress,
|
|
246
142
|
});
|
|
247
143
|
clientSocket.send(JSON.stringify(newConnMsg));
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
144
|
+
// 恢复 socket
|
|
145
|
+
socket.resume();
|
|
146
|
+
// 通过数据通道发送初始数据
|
|
147
|
+
this.dataChannelManager.sendToClient(clientId, connectionId, initialData);
|
|
148
|
+
// 后续数据通过数据通道转发
|
|
149
|
+
socket.on('data', (data) => {
|
|
150
|
+
this.dataChannelManager.sendToClient(clientId, connectionId, data);
|
|
251
151
|
});
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
this.notifyClientClose(clientId, connectionId, code);
|
|
152
|
+
socket.on('close', () => {
|
|
153
|
+
logger.log(`连接关闭: ${connectionId}`);
|
|
154
|
+
this.notifyClientClose(clientId, connectionId);
|
|
256
155
|
this.cleanupConnection(connectionId);
|
|
257
156
|
});
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
logger.error(`用户 WebSocket 错误 ${connectionId}:`, error);
|
|
157
|
+
socket.on('error', (error) => {
|
|
158
|
+
logger.error(`连接错误 ${connectionId}:`, error.message);
|
|
261
159
|
this.cleanupConnection(connectionId);
|
|
262
160
|
});
|
|
263
161
|
}
|
|
264
162
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* 将外部用户发送的 WebSocket 数据编码为 Base64 后,通过控制通道发送给穿透客户端。
|
|
268
|
-
*
|
|
269
|
-
* @param clientId - 目标客户端唯一标识 ID
|
|
270
|
-
* @param connectionId - 连接唯一标识 ID
|
|
271
|
-
* @param data - 用户发送的原始数据
|
|
163
|
+
* 检测连接协议类型(仅用于日志和统计)
|
|
272
164
|
*/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
165
|
+
detectProtocol(data) {
|
|
166
|
+
if (data.length < 4)
|
|
167
|
+
return 'tcp';
|
|
168
|
+
const header = data.toString('ascii', 0, Math.min(data.length, 8)).toUpperCase();
|
|
169
|
+
const httpMethods = ['GET', 'POST', 'PUT', 'DELET', 'HEAD', 'OPTIO', 'PATCH', 'TRACE', 'CONN'];
|
|
170
|
+
for (const method of httpMethods) {
|
|
171
|
+
if (header.startsWith(method)) {
|
|
172
|
+
// 进一步检测是否为 WebSocket 升级请求
|
|
173
|
+
const fullHeader = data.toString('ascii', 0, Math.min(data.length, 512)).toLowerCase();
|
|
174
|
+
if (fullHeader.includes('upgrade: websocket')) {
|
|
175
|
+
return 'websocket';
|
|
176
|
+
}
|
|
177
|
+
return 'http';
|
|
178
|
+
}
|
|
281
179
|
}
|
|
180
|
+
return 'tcp';
|
|
282
181
|
}
|
|
283
182
|
/**
|
|
284
|
-
*
|
|
183
|
+
* 处理来自客户端的 TCP 数据(通过数据通道)
|
|
285
184
|
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* @param clientId - 目标客户端唯一标识 ID
|
|
289
|
-
* @param connectionId - 已关闭的连接唯一标识 ID
|
|
290
|
-
* @param code - WebSocket 关闭状态码
|
|
185
|
+
* 写入到对应的外部 socket。
|
|
291
186
|
*/
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
connectionId,
|
|
297
|
-
});
|
|
298
|
-
clientSocket.send(JSON.stringify(closeMsg));
|
|
187
|
+
handleDataFromClient(connectionId, data) {
|
|
188
|
+
const socket = this.userSockets.get(connectionId);
|
|
189
|
+
if (socket && !socket.destroyed) {
|
|
190
|
+
socket.write(data);
|
|
299
191
|
}
|
|
300
192
|
}
|
|
301
193
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* 创建一个 Promise,等待穿透客户端处理完请求并返回响应数据。
|
|
305
|
-
* 若超过 30 秒未收到响应,将自动超时并拒绝 Promise。
|
|
306
|
-
*
|
|
307
|
-
* @param connectionId - 连接唯一标识 ID
|
|
308
|
-
* @param clientId - 客户端唯一标识 ID
|
|
309
|
-
* @returns 客户端返回的 HTTP 响应数据
|
|
310
|
-
*/
|
|
311
|
-
waitForResponse(connectionId, clientId) {
|
|
312
|
-
return new Promise((resolve, reject) => {
|
|
313
|
-
const timeout = setTimeout(() => {
|
|
314
|
-
this.pendingResponses.delete(connectionId);
|
|
315
|
-
reject(new Error('响应超时'));
|
|
316
|
-
}, 30000); // 30秒超时
|
|
317
|
-
this.pendingResponses.set(connectionId, { resolve, reject, timeout });
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* 读取 HTTP 请求体
|
|
322
|
-
*
|
|
323
|
-
* 从 IncomingMessage 流中读取完整的请求体数据。
|
|
324
|
-
*
|
|
325
|
-
* @param req - HTTP 请求对象
|
|
326
|
-
* @returns 包含完整请求体的 Buffer
|
|
194
|
+
* 处理外部 UDP 消息
|
|
327
195
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
196
|
+
handleUdpMessage(clientId, port, msg, rinfo) {
|
|
197
|
+
const sessionKey = `${rinfo.address}:${rinfo.port}`;
|
|
198
|
+
let session = this.udpSessions.get(sessionKey);
|
|
199
|
+
if (session) {
|
|
200
|
+
// 已有会话,刷新超时
|
|
201
|
+
clearTimeout(session.timer);
|
|
202
|
+
session.timer = setTimeout(() => this.cleanupUdpSession(sessionKey), UDP_SESSION_TIMEOUT);
|
|
203
|
+
// 转发数据到客户端
|
|
204
|
+
this.dataChannelManager.sendUdpToClient(clientId, session.connectionId, msg);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// 新会话
|
|
208
|
+
const clientSocket = this.sessionManager.getClientSocket(clientId);
|
|
209
|
+
if (!clientSocket || clientSocket.readyState !== WebSocket.OPEN) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const connectionId = uuidv4();
|
|
213
|
+
session = {
|
|
214
|
+
connectionId,
|
|
215
|
+
address: rinfo.address,
|
|
216
|
+
port: rinfo.port,
|
|
217
|
+
timer: setTimeout(() => this.cleanupUdpSession(sessionKey), UDP_SESSION_TIMEOUT),
|
|
218
|
+
};
|
|
219
|
+
this.udpSessions.set(sessionKey, session);
|
|
220
|
+
this.udpConnectionToSession.set(connectionId, { port, sessionKey });
|
|
221
|
+
// 记录连接
|
|
222
|
+
this.sessionManager.addConnection(clientId, connectionId, rinfo.address, 'udp');
|
|
223
|
+
logger.log(`UDP 会话: ${rinfo.address}:${rinfo.port} -> :${port} (${connectionId})`);
|
|
224
|
+
// 通过 WebSocket 发送 NEW_CONNECTION
|
|
225
|
+
const newConnMsg = createMessage(MessageType.NEW_CONNECTION, {
|
|
226
|
+
connectionId,
|
|
227
|
+
protocol: 'udp',
|
|
228
|
+
remotePort: port,
|
|
229
|
+
remoteAddress: rinfo.address,
|
|
336
230
|
});
|
|
337
|
-
|
|
338
|
-
|
|
231
|
+
clientSocket.send(JSON.stringify(newConnMsg));
|
|
232
|
+
// 通过 UDP 数据通道转发初始数据
|
|
233
|
+
this.dataChannelManager.sendUdpToClient(clientId, connectionId, msg);
|
|
234
|
+
}
|
|
339
235
|
}
|
|
340
236
|
/**
|
|
341
|
-
*
|
|
237
|
+
* 处理来自客户端的 UDP 数据(通过 UDP 数据通道)
|
|
342
238
|
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* @param connectionId - 连接唯一标识 ID
|
|
346
|
-
* @param data - 客户端返回的原始响应消息对象,包含 statusCode, headers, body 等字段
|
|
239
|
+
* 回发给外部 UDP 客户端。
|
|
347
240
|
*/
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
pending.resolve(responseData);
|
|
360
|
-
}
|
|
241
|
+
handleUdpDataFromClient(connectionId, data) {
|
|
242
|
+
const sessionInfo = this.udpConnectionToSession.get(connectionId);
|
|
243
|
+
if (!sessionInfo)
|
|
244
|
+
return;
|
|
245
|
+
const session = this.udpSessions.get(sessionInfo.sessionKey);
|
|
246
|
+
if (!session)
|
|
247
|
+
return;
|
|
248
|
+
const proxy = this.proxies.get(sessionInfo.port);
|
|
249
|
+
if (!proxy)
|
|
250
|
+
return;
|
|
251
|
+
proxy.udpSocket.send(data, session.port, session.address);
|
|
361
252
|
}
|
|
362
253
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
* 将穿透客户端发送的数据转发给对应的外部用户 WebSocket 连接。
|
|
366
|
-
*
|
|
367
|
-
* @param connectionId - 连接唯一标识 ID
|
|
368
|
-
* @param data - 穿透客户端发送的数据
|
|
254
|
+
* 通知客户端连接已关闭
|
|
369
255
|
*/
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
|
|
256
|
+
notifyClientClose(clientId, connectionId) {
|
|
257
|
+
const clientSocket = this.sessionManager.getClientSocket(clientId);
|
|
258
|
+
if (clientSocket && clientSocket.readyState === WebSocket.OPEN) {
|
|
259
|
+
const closeMsg = createMessage(MessageType.CONNECTION_CLOSE, {
|
|
260
|
+
connectionId,
|
|
261
|
+
});
|
|
262
|
+
clientSocket.send(JSON.stringify(closeMsg));
|
|
374
263
|
}
|
|
375
264
|
}
|
|
376
265
|
/**
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
* 当穿透客户端请求关闭某个连接时,关闭对应的外部用户 WebSocket 连接并清理资源。
|
|
380
|
-
*
|
|
381
|
-
* @param connectionId - 需要关闭的连接唯一标识 ID
|
|
382
|
-
* @param code - 可选的 WebSocket 关闭状态码,默认为 1000(正常关闭)
|
|
266
|
+
* 处理来自客户端的关闭请求
|
|
383
267
|
*/
|
|
384
|
-
handleClientClose(connectionId
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
268
|
+
handleClientClose(connectionId) {
|
|
269
|
+
// TCP 连接
|
|
270
|
+
const socket = this.userSockets.get(connectionId);
|
|
271
|
+
if (socket) {
|
|
272
|
+
socket.destroy();
|
|
273
|
+
this.cleanupConnection(connectionId);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// UDP 会话
|
|
277
|
+
const sessionInfo = this.udpConnectionToSession.get(connectionId);
|
|
278
|
+
if (sessionInfo) {
|
|
279
|
+
this.cleanupUdpSession(sessionInfo.sessionKey);
|
|
388
280
|
}
|
|
389
|
-
this.cleanupConnection(connectionId);
|
|
390
281
|
}
|
|
391
|
-
/**
|
|
392
|
-
* 清理连接资源
|
|
393
|
-
*
|
|
394
|
-
* 从用户连接映射表和会话管理器中移除指定连接的记录。
|
|
395
|
-
*
|
|
396
|
-
* @param connectionId - 需要清理的连接唯一标识 ID
|
|
397
|
-
*/
|
|
398
282
|
cleanupConnection(connectionId) {
|
|
399
|
-
this.
|
|
400
|
-
this.streamingResponses.delete(connectionId);
|
|
283
|
+
this.userSockets.delete(connectionId);
|
|
401
284
|
this.sessionManager.removeConnection(connectionId);
|
|
402
285
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
* @param data - 客户端发送的流式数据
|
|
410
|
-
*/
|
|
411
|
-
handleClientStreamData(connectionId, data) {
|
|
412
|
-
const res = this.streamingResponses.get(connectionId);
|
|
413
|
-
if (res && !res.writableEnded) {
|
|
414
|
-
res.write(data);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* 处理来自客户端的流式响应结束通知
|
|
419
|
-
*
|
|
420
|
-
* 当客户端通知流式响应结束时,关闭外部 HTTP 连接并清理资源。
|
|
421
|
-
*
|
|
422
|
-
* @param connectionId - 连接唯一标识 ID
|
|
423
|
-
*/
|
|
424
|
-
handleClientStreamEnd(connectionId) {
|
|
425
|
-
const res = this.streamingResponses.get(connectionId);
|
|
426
|
-
if (res && !res.writableEnded) {
|
|
427
|
-
res.end();
|
|
286
|
+
cleanupUdpSession(sessionKey) {
|
|
287
|
+
const session = this.udpSessions.get(sessionKey);
|
|
288
|
+
if (session) {
|
|
289
|
+
clearTimeout(session.timer);
|
|
290
|
+
this.udpConnectionToSession.delete(session.connectionId);
|
|
291
|
+
this.sessionManager.removeConnection(session.connectionId);
|
|
428
292
|
}
|
|
429
|
-
|
|
430
|
-
this.cleanupConnection(connectionId);
|
|
293
|
+
this.udpSessions.delete(sessionKey);
|
|
431
294
|
}
|
|
432
295
|
/**
|
|
433
296
|
* 停止指定端口的代理服务器
|
|
434
|
-
*
|
|
435
|
-
* 关闭指定端口上的代理服务器并从映射表中移除。
|
|
436
|
-
*
|
|
437
|
-
* @param port - 需要停止的代理端口号
|
|
438
|
-
* @returns 代理服务器关闭完成的 Promise
|
|
439
297
|
*/
|
|
440
298
|
async stopProxy(port) {
|
|
441
|
-
const
|
|
442
|
-
if (
|
|
443
|
-
return
|
|
444
|
-
|
|
299
|
+
const proxy = this.proxies.get(port);
|
|
300
|
+
if (!proxy)
|
|
301
|
+
return;
|
|
302
|
+
// 清理该端口的所有 TCP 连接
|
|
303
|
+
for (const [connId, socket] of this.userSockets) {
|
|
304
|
+
socket.destroy();
|
|
305
|
+
this.userSockets.delete(connId);
|
|
306
|
+
}
|
|
307
|
+
// 清理该端口的所有 UDP 会话
|
|
308
|
+
for (const [key, session] of this.udpSessions) {
|
|
309
|
+
const info = this.udpConnectionToSession.get(session.connectionId);
|
|
310
|
+
if (info && info.port === port) {
|
|
311
|
+
this.cleanupUdpSession(key);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
proxy.udpSocket.close(() => {
|
|
316
|
+
proxy.tcpServer.close(() => {
|
|
445
317
|
logger.log(`代理已在端口 ${port} 上停止`);
|
|
446
318
|
this.proxies.delete(port);
|
|
447
319
|
resolve();
|
|
448
320
|
});
|
|
449
321
|
});
|
|
450
|
-
}
|
|
322
|
+
});
|
|
451
323
|
}
|
|
452
324
|
/**
|
|
453
325
|
* 停止所有代理服务器
|
|
454
|
-
*
|
|
455
|
-
* 并行关闭所有已启动的代理服务器。
|
|
456
|
-
*
|
|
457
|
-
* @returns 所有代理服务器关闭完成的 Promise
|
|
458
326
|
*/
|
|
459
327
|
async stopAll() {
|
|
460
328
|
const stopPromises = [];
|
|
@@ -465,8 +333,6 @@ export class UnifiedProxyHandler {
|
|
|
465
333
|
}
|
|
466
334
|
/**
|
|
467
335
|
* 获取所有活跃代理的端口列表
|
|
468
|
-
*
|
|
469
|
-
* @returns 当前正在运行的所有代理端口号数组
|
|
470
336
|
*/
|
|
471
337
|
getActivePorts() {
|
|
472
338
|
return Array.from(this.proxies.keys());
|