Skip to content

Commit 4636723

Browse files
committed
fix(test): Implement robust WebSocket echo server with proper error handling and logging
Signed-off-by: Xin Liu <[email protected]>
1 parent b8a697d commit 4636723

File tree

3 files changed

+134
-11
lines changed

3 files changed

+134
-11
lines changed

.github/workflows/test.yml

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,40 @@ jobs:
8787
8888
- name: Start WebSocket echo server
8989
run: |
90-
# Use simple Python WebSocket echo server
90+
# Use robust Python WebSocket echo server
9191
cat > ws-echo.py << 'EOF'
9292
import asyncio
9393
import websockets
94+
import logging
95+
96+
logging.basicConfig(level=logging.INFO)
97+
logger = logging.getLogger(__name__)
9498
9599
async def echo(websocket, path):
96-
async for message in websocket:
97-
await websocket.send(message)
100+
logger.info(f"New WebSocket connection from {websocket.remote_address}")
101+
try:
102+
async for message in websocket:
103+
# Echo back the message
104+
await websocket.send(message)
105+
logger.info(f"Echoed message: {len(message)} bytes")
106+
except websockets.exceptions.ConnectionClosed:
107+
logger.info("Connection closed normally")
108+
except Exception as e:
109+
logger.error(f"Error in echo handler: {e}")
110+
finally:
111+
logger.info("Connection ended")
98112
99113
async def main():
100-
async with websockets.serve(echo, "0.0.0.0", 8890):
114+
# Configure server with proper settings
115+
async with websockets.serve(
116+
echo,
117+
"0.0.0.0",
118+
8890,
119+
ping_interval=20,
120+
ping_timeout=20,
121+
close_timeout=10
122+
):
123+
logger.info("WebSocket echo server started on port 8890")
101124
await asyncio.Future()
102125
103126
if __name__ == "__main__":

docker-compose.test.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,22 @@ services:
3636
- test-network
3737

3838
# WebSocket Echo 服务(替代 echo.websocket.org)
39-
# 使用简单的 WebSocket echo 服务器
39+
# 使用 Python websockets 库实现的健壮 echo 服务器
4040
ws-echo:
41-
image: jmalloc/echo-server:latest
41+
image: python:3.11-alpine
4242
container_name: ss-proxy-test-ws
4343
ports:
44-
- "8890:8080"
45-
environment:
46-
- PORT=8080
44+
- "8890:8890"
45+
volumes:
46+
- ./tests/mock-data/ws-echo.py:/app/ws-echo.py:ro
47+
working_dir: /app
48+
command: sh -c "pip install --no-cache-dir websockets && python ws-echo.py"
4749
healthcheck:
48-
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080"]
50+
test: ["CMD", "sh", "-c", "timeout 5 sh -c 'cat < /dev/null > /dev/tcp/localhost/8890' || exit 1"]
4951
interval: 10s
5052
timeout: 5s
5153
retries: 3
52-
start_period: 5s
54+
start_period: 10s
5355
networks:
5456
- test-network
5557

tests/mock-data/ws-echo.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Robust WebSocket Echo Server for Testing
4+
5+
This server echoes back any messages it receives.
6+
It properly handles WebSocket control frames and connection lifecycle.
7+
"""
8+
9+
import asyncio
10+
import logging
11+
import sys
12+
13+
import websockets
14+
15+
logging.basicConfig(
16+
level=logging.INFO,
17+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
18+
)
19+
logger = logging.getLogger(__name__)
20+
21+
22+
async def echo(websocket, path):
23+
"""
24+
Echo handler that receives messages and sends them back.
25+
26+
Args:
27+
websocket: The WebSocket connection
28+
path: The request path
29+
"""
30+
client_addr = websocket.remote_address
31+
logger.info(f"New WebSocket connection from {client_addr} (path: {path})")
32+
33+
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"
41+
msg_size = len(message)
42+
43+
# Echo back the message
44+
await websocket.send(message)
45+
logger.info(f"Echoed {msg_type} message: {msg_size} bytes from {client_addr}")
46+
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}")
51+
except Exception as e:
52+
logger.error(f"Unexpected error in echo handler from {client_addr}: {e}", exc_info=True)
53+
finally:
54+
logger.info(f"Connection ended with {client_addr}")
55+
56+
57+
async def main():
58+
"""
59+
Start the WebSocket echo server.
60+
"""
61+
host = "0.0.0.0"
62+
port = 8890
63+
64+
# Configure server with proper timeouts and settings
65+
server = await websockets.serve(
66+
echo,
67+
host,
68+
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,
77+
)
78+
79+
logger.info(f"WebSocket echo server started on {host}:{port}")
80+
logger.info("Press Ctrl+C to stop")
81+
82+
# Keep the server running
83+
try:
84+
await asyncio.Future()
85+
except KeyboardInterrupt:
86+
logger.info("Shutting down server...")
87+
server.close()
88+
await server.wait_closed()
89+
logger.info("Server stopped")
90+
91+
92+
if __name__ == "__main__":
93+
try:
94+
asyncio.run(main())
95+
except KeyboardInterrupt:
96+
logger.info("Server interrupted by user")
97+
sys.exit(0)
98+
sys.exit(0)

0 commit comments

Comments
 (0)