@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.
@@ -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.1",
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"