11#!/usr/bin/env python3
22"""
3- Robust WebSocket Echo Server for Testing
3+ Ultra- Robust WebSocket Echo Server for Testing
44
5- This server echoes back any messages it receives.
6- It properly handles WebSocket control frames and connection lifecycle .
5+ This server echoes back any messages it receives using a manual receive loop
6+ instead of async for, which can exit unexpectedly in some scenarios .
77"""
88
99import asyncio
1414
1515logging .basicConfig (
1616 level = logging .INFO ,
17- format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17+ format = "%(asctime)s - %(levelname)s - %(message)s" ,
18+ stream = sys .stdout ,
1819)
1920logger = logging .getLogger (__name__ )
2021
2122
2223async def echo (websocket , path ):
2324 """
24- Echo handler that receives messages and sends them back.
25+ Echo handler that receives messages and sends them back using manual loop .
2526
2627 Args:
2728 websocket: The WebSocket connection
2829 path: The request path
2930 """
3031 client_addr = websocket .remote_address
31- logger .info (f"New WebSocket connection from { client_addr } (path: { path } ) " )
32+ logger .info (f"✅ New connection from { client_addr } to { path } " )
3233
34+ message_count = 0
3335 try :
34- async for message in websocket :
35- # Determine message type
36- if isinstance (message , bytes ):
37- msg_type = "binary"
38- msg_size = len (message )
39- else :
40- msg_type = "text"
36+ # Manual receive loop with explicit error checking
37+ while True :
38+ try :
39+ # Wait for message with timeout
40+ message = await asyncio .wait_for (
41+ websocket .recv (), timeout = 60.0 # 60 second timeout
42+ )
43+
44+ message_count += 1
45+ msg_type = "binary" if isinstance (message , bytes ) else "text"
4146 msg_size = len (message )
4247
43- # Echo back the message
44- await websocket .send (message )
45- logger .info (f"Echoed { msg_type } message: { msg_size } bytes from { client_addr } " )
48+ logger .info (
49+ f"📨 Received { msg_type } message #{ message_count } : { msg_size } bytes from { client_addr } "
50+ )
51+
52+ # Echo back immediately
53+ await websocket .send (message )
54+ logger .info (
55+ f"📤 Echoed { msg_type } message #{ message_count } : { msg_size } bytes to { client_addr } "
56+ )
57+
58+ except asyncio .TimeoutError :
59+ logger .warning (f"⏰ Timeout waiting for message from { client_addr } " )
60+ continue
61+ except websockets .exceptions .ConnectionClosedOK :
62+ logger .info (
63+ f"✅ Connection closed normally by { client_addr } after { message_count } messages"
64+ )
65+ break
66+ except websockets .exceptions .ConnectionClosedError as e :
67+ logger .warning (
68+ f"⚠️ Connection closed with error from { client_addr } : { e } "
69+ )
70+ break
4671
47- except websockets .exceptions .ConnectionClosedOK :
48- logger .info (f"Connection closed normally by { client_addr } " )
49- except websockets .exceptions .ConnectionClosedError as e :
50- logger .warning (f"Connection closed with error from { client_addr } : { e } " )
5172 except Exception as e :
52- logger .error (f"Unexpected error in echo handler from { client_addr } : { e } " , exc_info = True )
73+ logger .error (f"❌ Unexpected error handling { client_addr } : { e } " , exc_info = True )
5374 finally :
54- logger .info (f"Connection ended with { client_addr } " )
75+ logger .info (
76+ f"🔚 Handler ended for { client_addr } (processed { message_count } messages)"
77+ )
5578
5679
5780async def main ():
@@ -61,38 +84,36 @@ async def main():
6184 host = "0.0.0.0"
6285 port = 8890
6386
64- # Configure server with proper timeouts and settings
87+ logger .info (f"🚀 Starting WebSocket echo server on { host } :{ port } " )
88+
89+ # Start server with very permissive settings
6590 server = await websockets .serve (
6691 echo ,
6792 host ,
6893 port ,
69- # Ping settings to keep connection alive
70- ping_interval = 20 , # Send ping every 20 seconds
71- ping_timeout = 20 , # Wait 20 seconds for pong response
72- close_timeout = 10 , # Wait 10 seconds for close handshake
73- # Max message size (10MB)
74- max_size = 10 * 1024 * 1024 ,
75- # Max queue size for incoming messages
76- max_queue = 32 ,
94+ ping_interval = None , # Disable ping/pong (let client handle it)
95+ ping_timeout = None , # No ping timeout
96+ close_timeout = 10 , # 10 seconds for close handshake
97+ max_size = 10 * 1024 * 1024 , # 10MB max message
98+ max_queue = 32 , # Max 32 queued messages
99+ compression = None , # Disable compression for simplicity
77100 )
78101
79- logger .info (f"WebSocket echo server started on { host } :{ port } " )
80- logger .info ("Press Ctrl+C to stop" )
102+ logger .info (f"✅ WebSocket echo server is ready and listening" )
81103
82104 # Keep the server running
83105 try :
84106 await asyncio .Future ()
85107 except KeyboardInterrupt :
86- logger .info ("Shutting down server..." )
108+ logger .info ("🛑 Shutting down server..." )
87109 server .close ()
88110 await server .wait_closed ()
89- logger .info ("Server stopped" )
111+ logger .info ("✅ Server stopped" )
90112
91113
92114if __name__ == "__main__" :
93115 try :
94116 asyncio .run (main ())
95117 except KeyboardInterrupt :
96- logger .info ("Server interrupted by user" )
97- sys .exit (0 )
118+ logger .info ("🛑 Server interrupted by user" )
98119 sys .exit (0 )
0 commit comments