@aetherframework/websocket 1.0.0 โ 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/LICENSE +21 -0
- 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 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present, AetherFramework Contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Basic WebSocket Server Example - Final Version
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WebSocketFactory } from '../index.js';
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
try {
|
|
9
|
+
// Create WebSocket factory instance
|
|
10
|
+
const factory = new WebSocketFactory({
|
|
11
|
+
driver: 'http', // Use HTTP upgrade driver
|
|
12
|
+
port: process.env.WS_PORT || 8080,
|
|
13
|
+
host: process.env.WS_HOST || '0.0.0.0',
|
|
14
|
+
maxPayload: 1024 * 1024, // 1MB
|
|
15
|
+
pingInterval: 30000, // 30 seconds heartbeat
|
|
16
|
+
pingTimeout: 10000 // 10 seconds timeout
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Create server
|
|
20
|
+
const server = await factory.createServer();
|
|
21
|
+
|
|
22
|
+
console.log('โ
WebSocket server starting...');
|
|
23
|
+
|
|
24
|
+
// Listen for connection events
|
|
25
|
+
factory.on('connection', (connection) => {
|
|
26
|
+
console.log(`๐ Client connected: ${connection.id} from ${connection.remoteAddress || 'unknown'}`);
|
|
27
|
+
|
|
28
|
+
// Send welcome message
|
|
29
|
+
connection.send(JSON.stringify({
|
|
30
|
+
type: 'welcome',
|
|
31
|
+
message: 'Connected to WebSocket server',
|
|
32
|
+
connectionId: connection.id,
|
|
33
|
+
timestamp: Date.now()
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Listen for message events
|
|
37
|
+
connection.on('message', (data, isBinary) => {
|
|
38
|
+
const message = isBinary ? '[Binary Data]' : data.toString();
|
|
39
|
+
console.log(`๐จ Received from ${connection.id}: ${message}`);
|
|
40
|
+
|
|
41
|
+
// Echo back the message
|
|
42
|
+
connection.send(`Echo: ${message}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Listen for close events
|
|
46
|
+
connection.on('close', (code, reason) => {
|
|
47
|
+
console.log(`โ Client disconnected: ${connection.id}, code: ${code}, reason: ${reason}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Listen for error events
|
|
51
|
+
connection.on('error', (error) => {
|
|
52
|
+
console.error(`โ ๏ธ Connection error for ${connection.id}:`, error);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Listen for server listening event
|
|
57
|
+
factory.on('server:listening', (address) => {
|
|
58
|
+
console.log(`๐ Server listening on ws://${address.host}:${address.port}`);
|
|
59
|
+
console.log('๐ก Waiting for connections...');
|
|
60
|
+
console.log('๐ Press Ctrl+C to stop the server');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Listen for server error events
|
|
64
|
+
factory.on('error', (error) => {
|
|
65
|
+
console.error('๐ฅ Server error:', error);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Graceful shutdown handling
|
|
69
|
+
process.on('SIGINT', async () => {
|
|
70
|
+
console.log('\n๐ Shutting down WebSocket server...');
|
|
71
|
+
try {
|
|
72
|
+
await factory.close();
|
|
73
|
+
console.log('โ
Server closed successfully');
|
|
74
|
+
process.exit(0);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('โ Error closing server:', error);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Keep process running
|
|
82
|
+
process.on('SIGTERM', async () => {
|
|
83
|
+
console.log('\n๐ Received SIGTERM, shutting down...');
|
|
84
|
+
await factory.close();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('โ Failed to start server:', error);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Start the server
|
|
95
|
+
main();
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Chat Server Example using Rooms
|
|
3
|
+
* @description Complete chat server with room-based broadcasting with event fixes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { WebSocketFactory } from '../index.js';
|
|
7
|
+
import BroadcastManager from '../src/middleware/broadcast-manager.js';
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
try {
|
|
11
|
+
console.log('๐ Starting chat server...');
|
|
12
|
+
console.log(`๐ก Port: ${process.env.WS_PORT || 3000}`);
|
|
13
|
+
console.log(`๐ Host: ${process.env.WS_HOST || '0.0.0.0'}`);
|
|
14
|
+
|
|
15
|
+
// Create WebSocket factory instance
|
|
16
|
+
const factory = new WebSocketFactory({
|
|
17
|
+
driver: 'http',
|
|
18
|
+
port: process.env.WS_PORT || 3000,
|
|
19
|
+
host: process.env.WS_HOST || '0.0.0.0',
|
|
20
|
+
maxPayload: 1024 * 1024,
|
|
21
|
+
pingInterval: 30000,
|
|
22
|
+
pingTimeout: 10000
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Create broadcast manager
|
|
26
|
+
const broadcastMgr = new BroadcastManager();
|
|
27
|
+
|
|
28
|
+
// Track if server:listening event has been fired
|
|
29
|
+
let serverListeningEventFired = false;
|
|
30
|
+
|
|
31
|
+
// Listen for server listening event
|
|
32
|
+
factory.on('server:listening', (address) => {
|
|
33
|
+
serverListeningEventFired = true;
|
|
34
|
+
console.log(`\n๐ ============================================`);
|
|
35
|
+
console.log(`๐ Chat server listening on ws://${address.host}:${address.port}`);
|
|
36
|
+
console.log(`๐ฌ Room-based chat server ready for connections`);
|
|
37
|
+
console.log(`๐ Press Ctrl+C to stop the server`);
|
|
38
|
+
console.log(`============================================\n`);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Listen for driver initialization (with duplicate prevention)
|
|
42
|
+
let driverInitialized = false;
|
|
43
|
+
factory.on('driver:initialized', (info) => {
|
|
44
|
+
if (!driverInitialized) {
|
|
45
|
+
driverInitialized = true;
|
|
46
|
+
console.log(`๐ง Driver initialized: ${info.driver}`);
|
|
47
|
+
if (factory.connectionManager) {
|
|
48
|
+
console.log('๐ Connection manager initialized');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Listen for connection events
|
|
54
|
+
factory.on('connection', (connection) => {
|
|
55
|
+
console.log(`๐ค User connected: ${connection.id}`);
|
|
56
|
+
|
|
57
|
+
// Apply broadcast manager middleware
|
|
58
|
+
broadcastMgr.middleware()(connection, () => {
|
|
59
|
+
console.log(`โ
Broadcast middleware applied for ${connection.id}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Send welcome message
|
|
63
|
+
connection.send(JSON.stringify({
|
|
64
|
+
type: 'system',
|
|
65
|
+
message: 'Welcome to Chat Server!',
|
|
66
|
+
commands: {
|
|
67
|
+
join: 'Send {"type":"join","room":"room-name"} to join a room',
|
|
68
|
+
chat: 'Send {"type":"chat","room":"room-name","message":"your message"} to send message',
|
|
69
|
+
leave: 'Send {"type":"leave","room":"room-name"} to leave a room',
|
|
70
|
+
list: 'Send {"type":"list"} to list all rooms'
|
|
71
|
+
},
|
|
72
|
+
connectionId: connection.id,
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// Listen for message events
|
|
77
|
+
connection.on('message', (data, isBinary) => {
|
|
78
|
+
if (isBinary) {
|
|
79
|
+
connection.send(JSON.stringify({
|
|
80
|
+
type: 'error',
|
|
81
|
+
message: 'Binary messages not supported in chat'
|
|
82
|
+
}));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const message = JSON.parse(data.toString());
|
|
88
|
+
console.log(`๐จ Message from ${connection.id}:`, message);
|
|
89
|
+
|
|
90
|
+
switch (message.type) {
|
|
91
|
+
case 'join':
|
|
92
|
+
handleJoinRoom(connection, message);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case 'leave':
|
|
96
|
+
handleLeaveRoom(connection, message);
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case 'chat':
|
|
100
|
+
handleChatMessage(connection, message);
|
|
101
|
+
break;
|
|
102
|
+
|
|
103
|
+
case 'list':
|
|
104
|
+
handleListRooms(connection);
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case 'users':
|
|
108
|
+
handleListUsers(connection, message);
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
default:
|
|
112
|
+
connection.send(JSON.stringify({
|
|
113
|
+
type: 'error',
|
|
114
|
+
message: 'Unknown message type',
|
|
115
|
+
validTypes: ['join', 'leave', 'chat', 'list', 'users']
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`โ JSON parse error from ${connection.id}:`, error.message);
|
|
120
|
+
connection.send(JSON.stringify({
|
|
121
|
+
type: 'error',
|
|
122
|
+
message: 'Invalid JSON format',
|
|
123
|
+
example: '{"type":"join","room":"general"}'
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Listen for close events
|
|
129
|
+
connection.on('close', (code, reason) => {
|
|
130
|
+
console.log(`๐ User disconnected: ${connection.id}, code: ${code}, reason: ${reason}`);
|
|
131
|
+
|
|
132
|
+
// Leave all rooms when user disconnects
|
|
133
|
+
const userRooms = connection.rooms ? connection.rooms() : [];
|
|
134
|
+
userRooms.forEach(room => {
|
|
135
|
+
connection.leave(room);
|
|
136
|
+
broadcastToRoom(room, {
|
|
137
|
+
type: 'system',
|
|
138
|
+
message: `User ${connection.id.substring(0, 8)} left the room`,
|
|
139
|
+
timestamp: Date.now()
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Listen for error events
|
|
145
|
+
connection.on('error', (error) => {
|
|
146
|
+
console.error(`โ ๏ธ Connection error for ${connection.id}:`, error);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Listen for server error events
|
|
151
|
+
factory.on('error', (error) => {
|
|
152
|
+
console.error('๐ฅ Server error:', error);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Create server
|
|
156
|
+
console.log('๐ Creating server...');
|
|
157
|
+
const server = await factory.createServer();
|
|
158
|
+
console.log('โ
Server instance created:', server);
|
|
159
|
+
|
|
160
|
+
// Fallback: If server:listening event doesn't fire within 500ms, show server info manually
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
if (!serverListeningEventFired) {
|
|
163
|
+
console.log('\nโ ๏ธ Server:listening event was not fired, checking server status manually...');
|
|
164
|
+
|
|
165
|
+
// Try to get server address directly
|
|
166
|
+
if (server && server.address) {
|
|
167
|
+
const address = typeof server.address === 'function' ? server.address() : server.address;
|
|
168
|
+
if (address && address.port) {
|
|
169
|
+
console.log(`๐ ============================================`);
|
|
170
|
+
console.log(`๐ Chat server listening on ws://${address.address || '0.0.0.0'}:${address.port}`);
|
|
171
|
+
console.log(`๐ฌ Room-based chat server ready for connections`);
|
|
172
|
+
console.log(`๐ Press Ctrl+C to stop the server`);
|
|
173
|
+
console.log(`============================================\n`);
|
|
174
|
+
} else {
|
|
175
|
+
console.log(`๐ Server should be available at: ws://${process.env.WS_HOST || '0.0.0.0'}:${process.env.WS_PORT || 3000}`);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
console.log(`๐ Server should be available at: ws://${process.env.WS_HOST || '0.0.0.0'}:${process.env.WS_PORT || 3000}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}, 500);
|
|
182
|
+
|
|
183
|
+
// Display connection instructions
|
|
184
|
+
console.log('\n๐ How to connect:');
|
|
185
|
+
console.log('1. Open browser developer tools (F12)');
|
|
186
|
+
console.log('2. In Console tab, run:');
|
|
187
|
+
console.log(`
|
|
188
|
+
const ws = new WebSocket('ws://localhost:${process.env.WS_PORT || 3000}');
|
|
189
|
+
ws.onopen = () => {
|
|
190
|
+
console.log('Connected!');
|
|
191
|
+
ws.send(JSON.stringify({type:"join",room:"general"}));
|
|
192
|
+
ws.send(JSON.stringify({type:"chat",room:"general",message:"Hello!"}));
|
|
193
|
+
};
|
|
194
|
+
ws.onmessage = (e) => console.log('Server:', JSON.parse(e.data));
|
|
195
|
+
`);
|
|
196
|
+
|
|
197
|
+
// Room handling functions
|
|
198
|
+
function handleJoinRoom(connection, message) {
|
|
199
|
+
if (!message.room) {
|
|
200
|
+
connection.send(JSON.stringify({
|
|
201
|
+
type: 'error',
|
|
202
|
+
message: 'Room name is required',
|
|
203
|
+
example: '{"type":"join","room":"general"}'
|
|
204
|
+
}));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const roomName = message.room.trim();
|
|
209
|
+
if (roomName.length === 0) {
|
|
210
|
+
connection.send(JSON.stringify({
|
|
211
|
+
type: 'error',
|
|
212
|
+
message: 'Room name cannot be empty'
|
|
213
|
+
}));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Join room
|
|
218
|
+
connection.join(roomName);
|
|
219
|
+
|
|
220
|
+
console.log(`๐ฅ ${connection.id} joined room: ${roomName}`);
|
|
221
|
+
|
|
222
|
+
// Send confirmation message
|
|
223
|
+
connection.send(JSON.stringify({
|
|
224
|
+
type: 'system',
|
|
225
|
+
message: `Joined room: ${roomName}`,
|
|
226
|
+
room: roomName,
|
|
227
|
+
timestamp: Date.now()
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
// Broadcast to other users in the room
|
|
231
|
+
broadcastToRoom(roomName, {
|
|
232
|
+
type: 'system',
|
|
233
|
+
message: `New user ${connection.id.substring(0, 8)} joined the room`,
|
|
234
|
+
userCount: getRoomUserCount(roomName),
|
|
235
|
+
timestamp: Date.now()
|
|
236
|
+
}, connection.id); // Exclude self
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function handleLeaveRoom(connection, message) {
|
|
240
|
+
if (!message.room) {
|
|
241
|
+
connection.send(JSON.stringify({
|
|
242
|
+
type: 'error',
|
|
243
|
+
message: 'Room name is required',
|
|
244
|
+
example: '{"type":"leave","room":"general"}'
|
|
245
|
+
}));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const roomName = message.room.trim();
|
|
250
|
+
connection.leave(roomName);
|
|
251
|
+
|
|
252
|
+
console.log(`๐ค ${connection.id} left room: ${roomName}`);
|
|
253
|
+
|
|
254
|
+
connection.send(JSON.stringify({
|
|
255
|
+
type: 'system',
|
|
256
|
+
message: `Left room: ${roomName}`,
|
|
257
|
+
room: roomName,
|
|
258
|
+
timestamp: Date.now()
|
|
259
|
+
}));
|
|
260
|
+
|
|
261
|
+
// Notify other users in the room
|
|
262
|
+
broadcastToRoom(roomName, {
|
|
263
|
+
type: 'system',
|
|
264
|
+
message: `User ${connection.id.substring(0, 8)} left the room`,
|
|
265
|
+
userCount: getRoomUserCount(roomName),
|
|
266
|
+
timestamp: Date.now()
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function handleChatMessage(connection, message) {
|
|
271
|
+
if (!message.room || !message.message) {
|
|
272
|
+
connection.send(JSON.stringify({
|
|
273
|
+
type: 'error',
|
|
274
|
+
message: 'Room and message are required',
|
|
275
|
+
example: '{"type":"chat","room":"general","message":"Hello!"}'
|
|
276
|
+
}));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const roomName = message.room.trim();
|
|
281
|
+
const chatMessage = message.message.trim();
|
|
282
|
+
|
|
283
|
+
// Check if user is in the room
|
|
284
|
+
const userRooms = connection.rooms ? connection.rooms() : [];
|
|
285
|
+
if (!userRooms.includes(roomName)) {
|
|
286
|
+
connection.send(JSON.stringify({
|
|
287
|
+
type: 'error',
|
|
288
|
+
message: `You are not in room: ${roomName}`,
|
|
289
|
+
suggestion: 'Send {"type":"join","room":"room-name"} first'
|
|
290
|
+
}));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log(`๐ฌ ${connection.id} -> ${roomName}: ${chatMessage}`);
|
|
295
|
+
|
|
296
|
+
// Broadcast message to room
|
|
297
|
+
broadcastToRoom(roomName, {
|
|
298
|
+
type: 'chat',
|
|
299
|
+
from: connection.id.substring(0, 8),
|
|
300
|
+
room: roomName,
|
|
301
|
+
message: chatMessage,
|
|
302
|
+
timestamp: Date.now()
|
|
303
|
+
}, connection.id); // Exclude sender
|
|
304
|
+
|
|
305
|
+
// Send confirmation to sender
|
|
306
|
+
connection.send(JSON.stringify({
|
|
307
|
+
type: 'sent',
|
|
308
|
+
room: roomName,
|
|
309
|
+
message: chatMessage,
|
|
310
|
+
timestamp: Date.now()
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function handleListRooms(connection) {
|
|
315
|
+
// Get all rooms (simplified implementation)
|
|
316
|
+
const roomInfo = {
|
|
317
|
+
type: 'rooms',
|
|
318
|
+
count: 0,
|
|
319
|
+
rooms: [],
|
|
320
|
+
timestamp: Date.now()
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
connection.send(JSON.stringify(roomInfo));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function handleListUsers(connection, message) {
|
|
327
|
+
if (!message.room) {
|
|
328
|
+
connection.send(JSON.stringify({
|
|
329
|
+
type: 'error',
|
|
330
|
+
message: 'Room name is required',
|
|
331
|
+
example: '{"type":"users","room":"general"}'
|
|
332
|
+
}));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const roomName = message.room.trim();
|
|
337
|
+
const userInfo = {
|
|
338
|
+
type: 'users',
|
|
339
|
+
room: roomName,
|
|
340
|
+
count: 0,
|
|
341
|
+
users: [],
|
|
342
|
+
timestamp: Date.now()
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
connection.send(JSON.stringify(userInfo));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function broadcastToRoom(roomName, message, excludeConnectionId = null) {
|
|
349
|
+
// Simplified broadcast implementation
|
|
350
|
+
console.log(`๐ข Broadcasting to room ${roomName}:`, message);
|
|
351
|
+
|
|
352
|
+
// In a real implementation, use broadcast manager's room functionality
|
|
353
|
+
// For now, we'll use factory.broadcast with filtering
|
|
354
|
+
if (factory.broadcast) {
|
|
355
|
+
factory.broadcast(JSON.stringify(message), (conn) => {
|
|
356
|
+
// Exclude specific connection
|
|
357
|
+
if (excludeConnectionId && conn.id === excludeConnectionId) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Check if connection is in the room
|
|
362
|
+
const userRooms = conn.rooms ? conn.rooms() : [];
|
|
363
|
+
return userRooms.includes(roomName);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function getRoomUserCount(roomName) {
|
|
369
|
+
// Simplified implementation
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Graceful shutdown handling
|
|
374
|
+
process.on('SIGINT', async () => {
|
|
375
|
+
console.log('\n๐ Shutting down chat server...');
|
|
376
|
+
try {
|
|
377
|
+
// Notify all users about server shutdown
|
|
378
|
+
if (factory.broadcast) {
|
|
379
|
+
factory.broadcast(JSON.stringify({
|
|
380
|
+
type: 'system',
|
|
381
|
+
message: 'Server is shutting down. Goodbye!',
|
|
382
|
+
timestamp: Date.now()
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
await factory.close();
|
|
387
|
+
console.log('โ
Chat server closed successfully');
|
|
388
|
+
process.exit(0);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error('โ Error closing server:', error);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
process.on('SIGTERM', async () => {
|
|
396
|
+
console.log('\n๐ Received SIGTERM, shutting down...');
|
|
397
|
+
await factory.close();
|
|
398
|
+
process.exit(0);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Keep the process running
|
|
402
|
+
console.log('โณ Server is running and waiting for connections...');
|
|
403
|
+
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error('โ Failed to start chat server:', error);
|
|
406
|
+
console.error('๐ก Possible solutions:');
|
|
407
|
+
console.error('1. Check if port 3000 is already in use');
|
|
408
|
+
console.error('2. Try running as administrator (if using port < 1024)');
|
|
409
|
+
console.error('3. Check firewall settings');
|
|
410
|
+
console.error('4. Verify WebSocketFactory implementation');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Start the chat server
|
|
416
|
+
main();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file client-test.js - Native WebSocket Stress Test Client
|
|
3
|
+
* @description Uses Node.js built-in WebSocket (no 'ws' package required)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// No import needed! Node.js 18+ has global WebSocket
|
|
7
|
+
const URL = 'ws://localhost:8081';
|
|
8
|
+
|
|
9
|
+
console.log('๐ Starting stress test client using native WebSocket...');
|
|
10
|
+
|
|
11
|
+
const ws = new WebSocket(URL);
|
|
12
|
+
|
|
13
|
+
ws.onopen = () => {
|
|
14
|
+
console.log('โ
Connected to server!');
|
|
15
|
+
|
|
16
|
+
// 1. Request initial stats
|
|
17
|
+
ws.send(JSON.stringify({ type: 'stats_request' }));
|
|
18
|
+
|
|
19
|
+
// 2. Start stress test sequence
|
|
20
|
+
// Sending 1000 messages with 512 bytes payload, no delay
|
|
21
|
+
console.log('๐ค Sending stress test command (1000 iterations)...');
|
|
22
|
+
ws.send(JSON.stringify({
|
|
23
|
+
type: 'stress_test',
|
|
24
|
+
iterations: 1000,
|
|
25
|
+
delay: 0,
|
|
26
|
+
payloadSize: 512
|
|
27
|
+
}));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
ws.onmessage = (event) => {
|
|
31
|
+
const data = JSON.parse(event.data);
|
|
32
|
+
|
|
33
|
+
if (data.type === 'stats_response') {
|
|
34
|
+
console.log('๐ Server Stats Received:', {
|
|
35
|
+
activeConnections: data.stats.connections.active,
|
|
36
|
+
totalMessages: data.stats.messages.received
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else if (data.type === 'stress_test_complete') {
|
|
40
|
+
console.log('\n๐ ===== STRESS TEST COMPLETE =====');
|
|
41
|
+
console.log(`โก Speed: ${data.messagesPerSecond} msgs/sec`);
|
|
42
|
+
console.log(`โฑ๏ธ Duration: ${data.duration} ms`);
|
|
43
|
+
console.log(`๐ฆ Total Iterations: ${data.iterations}`);
|
|
44
|
+
console.log('==================================\n');
|
|
45
|
+
|
|
46
|
+
// Close connection after test
|
|
47
|
+
ws.close();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
ws.onerror = (error) => {
|
|
52
|
+
console.error('โ WebSocket Error:', error.message);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
ws.onclose = () => {
|
|
56
|
+
console.log('๐ Connection closed.');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Realtime API Example with Auth and Rate Limiting
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { WebSocketFactory } from '../index.js';
|
|
6
|
+
import AuthMiddleware from '../src/middleware/auth-middleware.js';
|
|
7
|
+
import RateLimiter from '../src/middleware/rate-limiter.js';
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
// Create WebSocket factory instance with HTTP driver
|
|
11
|
+
const factory = new WebSocketFactory({
|
|
12
|
+
driver: 'http',
|
|
13
|
+
port: 4000
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Create authentication middleware instance
|
|
17
|
+
const auth = new AuthMiddleware({
|
|
18
|
+
verify: async (token) => {
|
|
19
|
+
// Mock token verification logic
|
|
20
|
+
// In production, replace with actual JWT or OAuth verification
|
|
21
|
+
return token === 'secret-token' ? { userId: 'user_123' } : null;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Create rate limiter middleware instance
|
|
26
|
+
const limiter = new RateLimiter({
|
|
27
|
+
windowMs: 10000, // Time window in milliseconds (10 seconds)
|
|
28
|
+
max: 50 // Maximum requests per window
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Create WebSocket server
|
|
32
|
+
const server = await factory.createServer();
|
|
33
|
+
|
|
34
|
+
// Listen for connection events
|
|
35
|
+
factory.on('connection', (ws) => {
|
|
36
|
+
// Chain middlewares: auth -> rate limiter -> connection handler
|
|
37
|
+
auth.middleware()(ws, () => {
|
|
38
|
+
limiter.middleware()(ws, () => {
|
|
39
|
+
console.log(`Authenticated user connected: ${ws.user?.userId}`);
|
|
40
|
+
|
|
41
|
+
// Listen for message events
|
|
42
|
+
ws.on('message', (data) => {
|
|
43
|
+
// Echo back with status confirmation
|
|
44
|
+
ws.send(JSON.stringify({
|
|
45
|
+
status: 'ok',
|
|
46
|
+
received: data,
|
|
47
|
+
timestamp: Date.now()
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Listen for close events
|
|
52
|
+
ws.on('close', (code, reason) => {
|
|
53
|
+
console.log(`User ${ws.user?.userId} disconnected: ${code} - ${reason}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Listen for error events
|
|
57
|
+
ws.on('error', (error) => {
|
|
58
|
+
console.error(`Connection error for user ${ws.user?.userId}:`, error);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Listen for server listening event
|
|
65
|
+
factory.on('server:listening', (address) => {
|
|
66
|
+
console.log(`Secure Realtime API listening on ws://${address.host}:${address.port}`);
|
|
67
|
+
console.log('๐ Authentication required: Send token in connection header');
|
|
68
|
+
console.log('โฑ๏ธ Rate limiting: 50 requests per 10 seconds');
|
|
69
|
+
console.log('๐ Press Ctrl+C to stop the server');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Listen for server error events
|
|
73
|
+
factory.on('error', (error) => {
|
|
74
|
+
console.error('Server error:', error);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Graceful shutdown handling
|
|
78
|
+
process.on('SIGINT', async () => {
|
|
79
|
+
console.log('\nShutting down Realtime API server...');
|
|
80
|
+
try {
|
|
81
|
+
await factory.close();
|
|
82
|
+
console.log('Server closed successfully');
|
|
83
|
+
process.exit(0);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Error closing server:', error);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
process.on('SIGTERM', async () => {
|
|
91
|
+
console.log('\nReceived SIGTERM, shutting down...');
|
|
92
|
+
await factory.close();
|
|
93
|
+
process.exit(0);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Start the server
|
|
98
|
+
main().catch(console.error);
|
|
@@ -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,7 +43,8 @@
|
|
|
43
43
|
"files": [
|
|
44
44
|
"index.js",
|
|
45
45
|
"server.js",
|
|
46
|
-
"src
|
|
46
|
+
"src",
|
|
47
|
+
"examples",
|
|
47
48
|
"connection.js",
|
|
48
49
|
"README.md",
|
|
49
50
|
"LICENSE"
|