@aetherframework/websocket 1.0.1 → 1.0.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/examples/basic-server.js +95 -0
- package/examples/chat-server.js +416 -0
- package/examples/client-test.js +58 -0
- package/examples/realtime-api.js +98 -0
- package/examples//342/200/214stress-test-server.js +495 -0
- package/package.json +3 -1
- package/src/core/ConnectionManager.js +213 -0
- package/src/core/FrameParser.js +115 -0
- package/src/core/HandshakeHandler.js +93 -0
- package/src/core/ProtocolHandler.js +186 -0
- package/src/core/WebSocketFactory.js +293 -0
- package/src/drivers/http-driver.js +576 -0
- package/src/drivers/index.js +29 -0
- package/src/drivers/memory-driver.js +422 -0
- package/src/drivers/tcp-driver.js +471 -0
- package/src/drivers/tls-driver.js +502 -0
- package/src/middleware/auth-middleware.js +37 -0
- package/src/middleware/broadcast-manager.js +173 -0
- package/src/middleware/compression.js +194 -0
- package/src/middleware/message-logger.js +322 -0
- package/src/middleware/rate-limiter.js +142 -0
- package/src/utils/config-loader.js +183 -0
- package/src/utils/connection-pool.js +110 -0
- package/src/utils/error-handler.js +59 -0
- package/src/utils/frame-encoder.js +211 -0
- package/src/utils/heartbeat-manager.js +91 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file WebSocket Stress Test Server
|
|
3
|
+
* @description A WebSocket server designed for load testing and performance benchmarking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { WebSocketFactory } from '../index.js';
|
|
7
|
+
import FrameEncoder from '../src/utils/frame-encoder.js'; // 添加这行导入
|
|
8
|
+
|
|
9
|
+
// Configuration for stress testing
|
|
10
|
+
const STRESS_TEST_CONFIG = {
|
|
11
|
+
driver: 'http',
|
|
12
|
+
port: process.env.STRESS_TEST_PORT || 8081,
|
|
13
|
+
host: process.env.STRESS_TEST_HOST || '0.0.0.0',
|
|
14
|
+
maxPayload: 10 * 1024 * 1024, // 10MB for large message testing
|
|
15
|
+
pingInterval: 60000, // 60 seconds (reduced for stress testing)
|
|
16
|
+
pingTimeout: 30000, // 30 seconds timeout
|
|
17
|
+
maxConnections: 100000, // High connection limit for stress testing
|
|
18
|
+
socketTimeout: 0 // No timeout for long-running connections
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Statistics tracking
|
|
22
|
+
const stats = {
|
|
23
|
+
connections: {
|
|
24
|
+
total: 0,
|
|
25
|
+
active: 0,
|
|
26
|
+
peak: 0
|
|
27
|
+
},
|
|
28
|
+
messages: {
|
|
29
|
+
received: 0,
|
|
30
|
+
sent: 0,
|
|
31
|
+
bytesReceived: 0,
|
|
32
|
+
bytesSent: 0
|
|
33
|
+
},
|
|
34
|
+
errors: {
|
|
35
|
+
connection: 0,
|
|
36
|
+
message: 0,
|
|
37
|
+
other: 0
|
|
38
|
+
},
|
|
39
|
+
startTime: Date.now()
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Store active connections for message handling
|
|
43
|
+
const activeConnections = new Map();
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
try {
|
|
47
|
+
console.log('⚡ Starting WebSocket Stress Test Server...');
|
|
48
|
+
console.log(`📊 Port: ${STRESS_TEST_CONFIG.port}`);
|
|
49
|
+
console.log(`🏠 Host: ${STRESS_TEST_CONFIG.host}`);
|
|
50
|
+
console.log(`🔧 Max Connections: ${STRESS_TEST_CONFIG.maxConnections}`);
|
|
51
|
+
console.log(`📦 Max Payload: ${STRESS_TEST_CONFIG.maxPayload / 1024 / 1024}MB`);
|
|
52
|
+
|
|
53
|
+
// Create WebSocket factory instance with stress test configuration
|
|
54
|
+
const factory = new WebSocketFactory(STRESS_TEST_CONFIG);
|
|
55
|
+
|
|
56
|
+
// Create server
|
|
57
|
+
const server = await factory.createServer();
|
|
58
|
+
|
|
59
|
+
// Listen for server listening event
|
|
60
|
+
factory.on('server:listening', (address) => {
|
|
61
|
+
console.log(`\n🎯 ============================================`);
|
|
62
|
+
console.log(`⚡ Stress Test Server listening on ws://${address.host}:${address.port}`);
|
|
63
|
+
console.log(`📈 Ready for load testing and performance benchmarking`);
|
|
64
|
+
console.log(`🛑 Press Ctrl+C to stop the server`);
|
|
65
|
+
console.log(`============================================\n`);
|
|
66
|
+
|
|
67
|
+
// Start statistics logging
|
|
68
|
+
startStatisticsLogging();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Listen for connection events
|
|
72
|
+
factory.on('connection', (connection) => {
|
|
73
|
+
// Update connection statistics
|
|
74
|
+
stats.connections.total++;
|
|
75
|
+
stats.connections.active++;
|
|
76
|
+
stats.connections.peak = Math.max(stats.connections.peak, stats.connections.active);
|
|
77
|
+
|
|
78
|
+
const connectionId = connection.id || `conn_${stats.connections.total}`;
|
|
79
|
+
const remoteAddress = connection.remoteAddress || 'unknown';
|
|
80
|
+
|
|
81
|
+
// Store connection for later use
|
|
82
|
+
activeConnections.set(connectionId, connection);
|
|
83
|
+
|
|
84
|
+
console.log(`🔗 Client connected: ${connectionId} from ${remoteAddress}`);
|
|
85
|
+
console.log(`📊 Active connections: ${stats.connections.active}/${stats.connections.total}`);
|
|
86
|
+
|
|
87
|
+
// Log connection object structure for debugging
|
|
88
|
+
console.log(`🔍 Connection object type: ${typeof connection}`);
|
|
89
|
+
console.log(`🔍 Connection object keys:`, Object.keys(connection));
|
|
90
|
+
|
|
91
|
+
// Send connection acknowledgment with server info
|
|
92
|
+
const ackMessage = JSON.stringify({
|
|
93
|
+
type: 'connection_ack',
|
|
94
|
+
connectionId: connectionId,
|
|
95
|
+
serverTime: Date.now(),
|
|
96
|
+
stats: {
|
|
97
|
+
activeConnections: stats.connections.active,
|
|
98
|
+
uptime: Date.now() - stats.startTime
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Send message through driver's socket
|
|
103
|
+
if (connection.socket && typeof connection.socket.write === 'function') {
|
|
104
|
+
try {
|
|
105
|
+
// Create WebSocket frame for the message
|
|
106
|
+
const frame = createWebSocketFrame(ackMessage);
|
|
107
|
+
connection.socket.write(frame);
|
|
108
|
+
stats.messages.sent++;
|
|
109
|
+
stats.messages.bytesSent += Buffer.byteLength(ackMessage);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`❌ Error sending acknowledgment to ${connectionId}:`, error.message);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
console.error(`❌ No socket available for connection ${connectionId}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Listen for message events from factory (not from connection object)
|
|
119
|
+
factory.on('message', (connection, data, isBinary) => {
|
|
120
|
+
const connectionId = connection.id || 'unknown';
|
|
121
|
+
const messageSize = Buffer.byteLength(data);
|
|
122
|
+
stats.messages.received++;
|
|
123
|
+
stats.messages.bytesReceived += messageSize;
|
|
124
|
+
|
|
125
|
+
// Log message statistics periodically
|
|
126
|
+
if (stats.messages.received % 1000 === 0) {
|
|
127
|
+
console.log(`📨 Received ${stats.messages.received} messages (${formatBytes(stats.messages.bytesReceived)})`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Process different types of stress test messages
|
|
131
|
+
try {
|
|
132
|
+
if (isBinary) {
|
|
133
|
+
// Handle binary data (for binary stress testing)
|
|
134
|
+
handleBinaryMessage(connection, data, messageSize);
|
|
135
|
+
} else {
|
|
136
|
+
// Handle text data
|
|
137
|
+
const message = data.toString();
|
|
138
|
+
handleTextMessage(connection, message, messageSize);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
stats.errors.message++;
|
|
142
|
+
console.error(`❌ Message processing error for ${connectionId}:`, error.message);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Listen for close events from factory
|
|
147
|
+
factory.on('close', (connection, code, reason) => {
|
|
148
|
+
const connectionId = connection.id || 'unknown';
|
|
149
|
+
stats.connections.active--;
|
|
150
|
+
console.log(`👋 Client disconnected: ${connectionId}, code: ${code}, reason: ${reason}`);
|
|
151
|
+
console.log(`📊 Active connections: ${stats.connections.active}/${stats.connections.total}`);
|
|
152
|
+
|
|
153
|
+
// Remove from active connections
|
|
154
|
+
activeConnections.delete(connectionId);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Listen for error events from factory
|
|
158
|
+
factory.on('error', (error) => {
|
|
159
|
+
stats.errors.other++;
|
|
160
|
+
console.error('💥 Server error:', error);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Handle different types of stress test messages
|
|
164
|
+
function handleTextMessage(connection, message, messageSize) {
|
|
165
|
+
const connectionId = connection.id || 'unknown';
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(message);
|
|
169
|
+
|
|
170
|
+
switch (parsed.type) {
|
|
171
|
+
case 'echo':
|
|
172
|
+
// Echo back the message (basic echo test)
|
|
173
|
+
const echoResponse = {
|
|
174
|
+
type: 'echo_response',
|
|
175
|
+
original: parsed.data,
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
messageSize: messageSize
|
|
178
|
+
};
|
|
179
|
+
sendToConnection(connection, JSON.stringify(echoResponse));
|
|
180
|
+
stats.messages.sent++;
|
|
181
|
+
stats.messages.bytesSent += Buffer.byteLength(JSON.stringify(echoResponse));
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case 'broadcast':
|
|
185
|
+
// Broadcast message to all connections (broadcast stress test)
|
|
186
|
+
const broadcastMessage = JSON.stringify({
|
|
187
|
+
type: 'broadcast',
|
|
188
|
+
from: connectionId,
|
|
189
|
+
data: parsed.data,
|
|
190
|
+
timestamp: Date.now()
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Broadcast to all active connections
|
|
194
|
+
broadcastToAll(broadcastMessage, connectionId);
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case 'stats_request':
|
|
198
|
+
// Return server statistics
|
|
199
|
+
const statsResponse = {
|
|
200
|
+
type: 'stats_response',
|
|
201
|
+
stats: getCurrentStats(),
|
|
202
|
+
timestamp: Date.now()
|
|
203
|
+
};
|
|
204
|
+
sendToConnection(connection, JSON.stringify(statsResponse));
|
|
205
|
+
stats.messages.sent++;
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case 'large_payload':
|
|
209
|
+
// Test large payload handling
|
|
210
|
+
const largeResponse = {
|
|
211
|
+
type: 'large_payload_response',
|
|
212
|
+
size: parsed.size || 1024,
|
|
213
|
+
data: 'x'.repeat(parsed.size || 1024),
|
|
214
|
+
timestamp: Date.now()
|
|
215
|
+
};
|
|
216
|
+
sendToConnection(connection, JSON.stringify(largeResponse));
|
|
217
|
+
stats.messages.sent++;
|
|
218
|
+
stats.messages.bytesSent += Buffer.byteLength(JSON.stringify(largeResponse));
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case 'stress_test':
|
|
222
|
+
// Run a stress test sequence
|
|
223
|
+
runStressTestSequence(connection, parsed);
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
default:
|
|
227
|
+
// Default echo response
|
|
228
|
+
const defaultResponse = {
|
|
229
|
+
type: 'default_response',
|
|
230
|
+
received: parsed,
|
|
231
|
+
timestamp: Date.now()
|
|
232
|
+
};
|
|
233
|
+
sendToConnection(connection, JSON.stringify(defaultResponse));
|
|
234
|
+
stats.messages.sent++;
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
// If JSON parsing fails, echo back as text
|
|
238
|
+
const echoText = `Echo: ${message}`;
|
|
239
|
+
sendToConnection(connection, echoText);
|
|
240
|
+
stats.messages.sent++;
|
|
241
|
+
stats.messages.bytesSent += Buffer.byteLength(echoText);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function handleBinaryMessage(connection, data, messageSize) {
|
|
246
|
+
// For binary stress testing, echo back the binary data
|
|
247
|
+
sendToConnection(connection, data, true);
|
|
248
|
+
stats.messages.sent++;
|
|
249
|
+
stats.messages.bytesSent += messageSize;
|
|
250
|
+
|
|
251
|
+
// Log binary message statistics
|
|
252
|
+
if (stats.messages.received % 100 === 0) {
|
|
253
|
+
console.log(`🔢 Binary message: ${formatBytes(messageSize)} from ${connection.id || 'unknown'}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function sendToConnection(connection, data, isBinary = false) {
|
|
258
|
+
try {
|
|
259
|
+
if (connection.socket && typeof connection.socket.write === 'function') {
|
|
260
|
+
// Create WebSocket frame
|
|
261
|
+
const frame = createWebSocketFrame(data, isBinary);
|
|
262
|
+
return connection.socket.write(frame);
|
|
263
|
+
} else {
|
|
264
|
+
console.error(`❌ No socket available for connection ${connection.id || 'unknown'}`);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error(`❌ Error sending to connection ${connection.id || 'unknown'}:`, error.message);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createWebSocketFrame(data, isBinary = false) {
|
|
274
|
+
try {
|
|
275
|
+
// 使用 FrameEncoder 创建正确的 WebSocket 帧
|
|
276
|
+
const opcode = isBinary ? 0x2 : 0x1; // 0x1 = TEXT, 0x2 = BINARY
|
|
277
|
+
return FrameEncoder.encode(opcode, data, true, false); // fin=true, mask=false (服务器到客户端不需要掩码)
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('❌ Error creating WebSocket frame:', error.message);
|
|
280
|
+
|
|
281
|
+
// 回退到简单实现
|
|
282
|
+
const payload = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
283
|
+
const payloadLength = payload.length;
|
|
284
|
+
|
|
285
|
+
let frame;
|
|
286
|
+
if (payloadLength <= 125) {
|
|
287
|
+
frame = Buffer.alloc(2 + payloadLength);
|
|
288
|
+
frame = isBinary ? 0x82 : 0x81; // FIN + opcode
|
|
289
|
+
frame = payloadLength; // No mask
|
|
290
|
+
payload.copy(frame, 2);
|
|
291
|
+
} else if (payloadLength <= 65535) {
|
|
292
|
+
frame = Buffer.alloc(4 + payloadLength);
|
|
293
|
+
frame = isBinary ? 0x82 : 0x81;
|
|
294
|
+
frame = 126; // Extended payload length
|
|
295
|
+
frame.writeUInt16BE(payloadLength, 2);
|
|
296
|
+
payload.copy(frame, 4);
|
|
297
|
+
} else {
|
|
298
|
+
frame = Buffer.alloc(10 + payloadLength);
|
|
299
|
+
frame = isBinary ? 0x82 : 0x81;
|
|
300
|
+
frame = 127; // 64-bit payload length
|
|
301
|
+
frame.writeBigUInt64BE(BigInt(payloadLength), 2);
|
|
302
|
+
payload.copy(frame, 10);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return frame;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function broadcastToAll(message, excludeConnectionId = null) {
|
|
310
|
+
let sentCount = 0;
|
|
311
|
+
|
|
312
|
+
activeConnections.forEach((connection, id) => {
|
|
313
|
+
if (excludeConnectionId && id === excludeConnectionId) {
|
|
314
|
+
return; // Skip excluded connection
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (sendToConnection(connection, message)) {
|
|
318
|
+
sentCount++;
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
stats.messages.sent += sentCount;
|
|
323
|
+
return sentCount;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function runStressTestSequence(connection, testConfig) {
|
|
327
|
+
const { iterations = 100, delay = 10, payloadSize = 1024 } = testConfig;
|
|
328
|
+
const connectionId = connection.id || 'unknown';
|
|
329
|
+
|
|
330
|
+
console.log(`🧪 Starting stress test for ${connectionId}: ${iterations} iterations`);
|
|
331
|
+
|
|
332
|
+
let completed = 0;
|
|
333
|
+
const testStartTime = Date.now();
|
|
334
|
+
|
|
335
|
+
function sendTestMessage(index) {
|
|
336
|
+
if (index >= iterations) {
|
|
337
|
+
// Test complete
|
|
338
|
+
const testDuration = Date.now() - testStartTime;
|
|
339
|
+
const messagesPerSecond = iterations / (testDuration / 1000);
|
|
340
|
+
|
|
341
|
+
const completionMessage = JSON.stringify({
|
|
342
|
+
type: 'stress_test_complete',
|
|
343
|
+
iterations: iterations,
|
|
344
|
+
duration: testDuration,
|
|
345
|
+
messagesPerSecond: messagesPerSecond.toFixed(2),
|
|
346
|
+
timestamp: Date.now()
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
sendToConnection(connection, completionMessage);
|
|
350
|
+
|
|
351
|
+
console.log(`✅ Stress test completed for ${connectionId}: ${messagesPerSecond.toFixed(2)} msg/sec`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const testMessage = {
|
|
356
|
+
type: 'stress_test_message',
|
|
357
|
+
index: index,
|
|
358
|
+
payload: 'x'.repeat(payloadSize),
|
|
359
|
+
timestamp: Date.now()
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
if (sendToConnection(connection, JSON.stringify(testMessage))) {
|
|
363
|
+
stats.messages.sent++;
|
|
364
|
+
stats.messages.bytesSent += Buffer.byteLength(JSON.stringify(testMessage));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
setTimeout(() => sendTestMessage(index + 1), delay);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
sendTestMessage(0);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getCurrentStats() {
|
|
374
|
+
const uptime = Date.now() - stats.startTime;
|
|
375
|
+
const hours = Math.floor(uptime / 3600000);
|
|
376
|
+
const minutes = Math.floor((uptime % 3600000) / 60000);
|
|
377
|
+
const seconds = Math.floor((uptime % 60000) / 1000);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
connections: {
|
|
381
|
+
total: stats.connections.total,
|
|
382
|
+
active: stats.connections.active,
|
|
383
|
+
peak: stats.connections.peak
|
|
384
|
+
},
|
|
385
|
+
messages: {
|
|
386
|
+
received: stats.messages.received,
|
|
387
|
+
sent: stats.messages.sent,
|
|
388
|
+
bytesReceived: formatBytes(stats.messages.bytesReceived),
|
|
389
|
+
bytesSent: formatBytes(stats.messages.bytesSent),
|
|
390
|
+
receiveRate: (stats.messages.received / (uptime / 1000)).toFixed(2),
|
|
391
|
+
sendRate: (stats.messages.sent / (uptime / 1000)).toFixed(2)
|
|
392
|
+
},
|
|
393
|
+
errors: stats.errors,
|
|
394
|
+
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
|
395
|
+
memory: process.memoryUsage()
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function formatBytes(bytes) {
|
|
400
|
+
if (bytes === 0) return '0 Bytes';
|
|
401
|
+
const k = 1024;
|
|
402
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
403
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
404
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function startStatisticsLogging() {
|
|
408
|
+
// Log statistics every 30 seconds
|
|
409
|
+
setInterval(() => {
|
|
410
|
+
const currentStats = getCurrentStats();
|
|
411
|
+
console.log('\n📈 ========== SERVER STATISTICS ==========');
|
|
412
|
+
console.log(`⏱️ Uptime: ${currentStats.uptime}`);
|
|
413
|
+
console.log(`👥 Connections: ${currentStats.connections.active} active / ${currentStats.connections.total} total (Peak: ${currentStats.connections.peak})`);
|
|
414
|
+
console.log(`📨 Messages: ${currentStats.messages.received} received, ${currentStats.messages.sent} sent`);
|
|
415
|
+
console.log(`📊 Data: ${currentStats.messages.bytesReceived} received, ${currentStats.messages.bytesSent} sent`);
|
|
416
|
+
console.log(`⚡ Rates: ${currentStats.messages.receiveRate} msg/sec received, ${currentStats.messages.sendRate} msg/sec sent`);
|
|
417
|
+
console.log(`❌ Errors: ${currentStats.errors.connection} connection, ${currentStats.errors.message} message, ${currentStats.errors.other} other`);
|
|
418
|
+
console.log(`💾 Memory: ${formatBytes(currentStats.memory.rss)} RSS, ${formatBytes(currentStats.memory.heapUsed)} heap used`);
|
|
419
|
+
console.log(`========================================\n`);
|
|
420
|
+
}, 30000);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Graceful shutdown handling
|
|
424
|
+
process.on('SIGINT', async () => {
|
|
425
|
+
console.log('\n🔄 Shutting down stress test server...');
|
|
426
|
+
console.log('📊 Final statistics:');
|
|
427
|
+
console.log(JSON.stringify(getCurrentStats(), null, 2));
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
await factory.close();
|
|
431
|
+
console.log('✅ Stress test server closed successfully');
|
|
432
|
+
process.exit(0);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error('❌ Error closing server:', error);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
process.on('SIGTERM', async () => {
|
|
440
|
+
console.log('\n🔄 Received SIGTERM, shutting down...');
|
|
441
|
+
await factory.close();
|
|
442
|
+
process.exit(0);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Display test client instructions
|
|
446
|
+
console.log('\n🧪 ========== STRESS TEST CLIENT EXAMPLES ==========');
|
|
447
|
+
console.log('1. Basic Echo Test:');
|
|
448
|
+
console.log(`
|
|
449
|
+
const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
|
|
450
|
+
ws.onopen = () => {
|
|
451
|
+
for(let i = 0; i < 1000; i++) {
|
|
452
|
+
ws.send(JSON.stringify({type: 'echo', data: 'Test message ' + i}));
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
`);
|
|
456
|
+
|
|
457
|
+
console.log('\n2. Large Payload Test:');
|
|
458
|
+
console.log(`
|
|
459
|
+
const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
|
|
460
|
+
ws.onopen = () => {
|
|
461
|
+
ws.send(JSON.stringify({type: 'large_payload', size: 1000000}));
|
|
462
|
+
};
|
|
463
|
+
`);
|
|
464
|
+
|
|
465
|
+
console.log('\n3. Stress Test Sequence:');
|
|
466
|
+
console.log(`
|
|
467
|
+
const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
|
|
468
|
+
ws.onopen = () => {
|
|
469
|
+
ws.send(JSON.stringify({
|
|
470
|
+
type: 'stress_test',
|
|
471
|
+
iterations: 1000,
|
|
472
|
+
delay: 0,
|
|
473
|
+
payloadSize: 1024
|
|
474
|
+
}));
|
|
475
|
+
};
|
|
476
|
+
`);
|
|
477
|
+
|
|
478
|
+
console.log('\n4. Request Statistics:');
|
|
479
|
+
console.log(`
|
|
480
|
+
const ws = new WebSocket('ws://localhost:${STRESS_TEST_CONFIG.port}');
|
|
481
|
+
ws.onopen = () => {
|
|
482
|
+
ws.send(JSON.stringify({type: 'stats_request'}));
|
|
483
|
+
ws.onmessage = (e) => console.log('Stats:', JSON.parse(e.data));
|
|
484
|
+
};
|
|
485
|
+
`);
|
|
486
|
+
console.log('==================================================\n');
|
|
487
|
+
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error('❌ Failed to start stress test server:', error);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Start the stress test server
|
|
495
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aetherframework/websocket",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Zero-dependency WebSocket server implementation for AetherJS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"files": [
|
|
44
44
|
"index.js",
|
|
45
45
|
"server.js",
|
|
46
|
+
"src",
|
|
47
|
+
"examples",
|
|
46
48
|
"connection.js",
|
|
47
49
|
"README.md",
|
|
48
50
|
"LICENSE"
|