Simple WebSocket server and client for Python
—
Simple WebSocket provides specific exception classes for handling WebSocket-related errors. These exceptions provide detailed information about connection failures and closures, enabling proper error handling and recovery strategies.
Base exception class for all simple-websocket related errors, serving as the parent class for WebSocket-specific exceptions.
class SimpleWebsocketError(RuntimeError):
"""
Base exception class for all simple-websocket related errors.
Inherits from RuntimeError and serves as the parent class for all
WebSocket-specific exceptions in the simple-websocket library.
"""Exception raised when WebSocket connection cannot be established or encounters an error during operation.
class ConnectionError(SimpleWebsocketError):
"""
Connection error exception class.
Raised when:
- WebSocket handshake fails
- Invalid server response during connection
- Network connectivity issues
- SSL/TLS certificate errors
- Server rejection of connection
"""
def __init__(self, status_code=None):
"""
Initialize connection error.
Parameters:
- status_code: HTTP status code associated with the error (optional)
"""Exception raised when WebSocket connection is closed, either by the remote peer or due to protocol violations.
class ConnectionClosed(SimpleWebsocketError):
"""
Connection closed exception class.
Raised when:
- Remote peer closes the connection
- Connection is closed due to protocol violation
- Network connection is lost
- Maximum message size exceeded
- Ping/pong timeout occurs
"""
def __init__(self, reason=None, message=None):
"""
Initialize connection closed exception.
Parameters:
- reason: CloseReason enum value indicating why connection was closed
- message: Optional text message describing the closure
"""class ConnectionError:
status_code: int | None # HTTP status code if available
class ConnectionClosed:
reason: CloseReason # Close reason code from WebSocket protocol
message: str | None # Optional close message textWebSocket connections can be closed for various reasons. The ConnectionClosed exception includes a reason code that indicates why the connection was terminated:
import simple_websocket
def handle_websocket_connection():
try:
# Attempt to connect
ws = simple_websocket.Client.connect("ws://localhost:8000/ws")
# Send and receive messages
ws.send("Hello, Server!")
response = ws.receive(timeout=10)
print(f"Response: {response}")
except simple_websocket.ConnectionError as e:
print(f"Failed to connect: {e}")
if e.status_code:
print(f"HTTP Status: {e.status_code}")
except simple_websocket.ConnectionClosed as e:
print(f"Connection was closed: {e}")
print(f"Close reason: {e.reason}")
if e.message:
print(f"Close message: {e.message}")
except Exception as e:
print(f"Unexpected error: {e}")
finally:
# Always clean up
try:
ws.close()
except:
pass # Connection may already be closedimport asyncio
import simple_websocket
async def async_websocket_handler():
ws = None
try:
# Attempt async connection
ws = await simple_websocket.AioClient.connect("wss://secure-server.com/ws")
# Send message and wait for response
await ws.send("Hello, Async Server!")
response = await ws.receive(timeout=15)
print(f"Server response: {response}")
except simple_websocket.ConnectionError as e:
print(f"Connection failed: {e}")
if e.status_code == 403:
print("Access forbidden - check authentication")
elif e.status_code == 404:
print("WebSocket endpoint not found")
elif e.status_code >= 500:
print("Server error - try again later")
except simple_websocket.ConnectionClosed as e:
print(f"Connection closed: {e}")
# Handle different close reasons
if e.reason == 1000: # Normal closure
print("Connection closed normally")
elif e.reason == 1001: # Going away
print("Server is shutting down")
elif e.reason == 1008: # Policy violation
print("Message violated server policy")
elif e.reason == 1009: # Message too big
print("Message was too large")
else:
print(f"Connection closed with code: {e.reason}")
except asyncio.TimeoutError:
print("Timeout waiting for server response")
finally:
if ws:
await ws.close()
# Run the async handler
asyncio.run(async_websocket_handler())import simple_websocket
def websocket_server_handler(environ, start_response):
ws = None
try:
# Accept WebSocket connection
ws = simple_websocket.Server.accept(
environ,
subprotocols=['chat', 'echo'],
max_message_size=1024 * 1024 # 1MB limit
)
print(f"Client connected with subprotocol: {ws.subprotocol}")
while True:
try:
# Receive message with timeout
message = ws.receive(timeout=30)
if message is None:
# Timeout occurred
print("Client timeout - closing connection")
ws.close(reason=1000, message="Timeout")
break
# Echo the message
ws.send(f"Echo: {message}")
except simple_websocket.ConnectionClosed as e:
print(f"Client disconnected: {e}")
if e.reason == 1001:
print("Client navigated away")
elif e.reason == 1006:
print("Connection lost unexpectedly")
break
except simple_websocket.ConnectionError as e:
print(f"Failed to establish WebSocket connection: {e}")
if e.status_code == 400:
print("Bad WebSocket request")
elif e.status_code == 426:
print("Upgrade required")
except Exception as e:
print(f"Unexpected server error: {e}")
finally:
if ws:
try:
ws.close(reason=1011, message="Server error")
except:
pass # Connection may already be closedimport simple_websocket
from wsproto.frame_protocol import CloseReason
class WebSocketManager:
def __init__(self):
self.ws = None
self.reconnect_attempts = 0
self.max_reconnect_attempts = 3
async def connect_with_retry(self, url):
"""Connect with automatic retry logic."""
while self.reconnect_attempts < self.max_reconnect_attempts:
try:
self.ws = await simple_websocket.AioClient.connect(url)
self.reconnect_attempts = 0 # Reset on successful connection
return True
except simple_websocket.ConnectionError as e:
self.reconnect_attempts += 1
print(f"Connection attempt {self.reconnect_attempts} failed: {e}")
if e.status_code == 401:
print("Authentication required - cannot retry")
return False
elif e.status_code == 404:
print("Endpoint not found - cannot retry")
return False
elif self.reconnect_attempts < self.max_reconnect_attempts:
await asyncio.sleep(2 ** self.reconnect_attempts) # Exponential backoff
print("Max reconnection attempts reached")
return False
async def handle_message_loop(self):
"""Handle messages with comprehensive error handling."""
try:
while True:
message = await self.ws.receive(timeout=60)
if message is None:
# Send ping to keep connection alive
await self.ws.send("ping")
continue
# Process message
await self.process_message(message)
except simple_websocket.ConnectionClosed as e:
print(f"Connection closed: {e}")
# Decide whether to reconnect based on close reason
should_reconnect = e.reason in [
CloseReason.GOING_AWAY, # 1001
CloseReason.ABNORMAL_CLOSURE, # 1006
CloseReason.INTERNAL_ERROR # 1011
]
if should_reconnect:
print("Attempting to reconnect...")
return await self.connect_with_retry(self.url)
else:
print("Connection closed permanently")
return False
async def process_message(self, message):
"""Process received message."""
try:
# Your message processing logic here
print(f"Processing: {message}")
await self.ws.send(f"Processed: {message}")
except Exception as e:
print(f"Error processing message: {e}")
# Send error response
await self.ws.send(f"Error: {str(e)}")try:
# WebSocket operations
pass
except simple_websocket.ConnectionError as e:
# Handle connection establishment errors
pass
except simple_websocket.ConnectionClosed as e:
# Handle connection closure
passexcept simple_websocket.ConnectionClosed as e:
if e.reason in [1000, 1001]: # Normal closures
# Clean shutdown
pass
elif e.reason in [1002, 1003, 1007]: # Protocol errors
# Log error, don't retry
pass
elif e.reason in [1006, 1011]: # Abnormal/server errors
# Consider reconnection
passtry:
# WebSocket operations
pass
except simple_websocket.ConnectionError:
# Handle errors
pass
finally:
# Always cleanup resources
if ws:
try:
ws.close()
except:
pass # Ignore cleanup errorsInstall with Tessl CLI
npx tessl i tessl/pypi-simple-websocket