wsproto: Pure-Python WebSocket Protocol Implementation
wsproto is a pure-Python, sans-I/O implementation of the WebSocket protocol stack (RFC 6455) and its per-message compression extension (RFC 7692). It provides a low-level, state-machine-driven API, allowing developers to embed WebSocket communication into various programming paradigms without dictating network or concurrency models. The current version is 1.3.2, with releases occurring on an irregular but active basis as needed by the `python-hyper` community.
Warnings
- gotcha wsproto is a 'sans-I/O' library. It manages the WebSocket protocol state but does not perform any network I/O itself. Users must implement their own 'network glue' to send and receive bytes over the actual network (e.g., using `socket` or an async I/O library) and feed them into `wsproto`.
- gotcha When the underlying network connection drops unexpectedly (e.g., `socket.recv()` returns zero bytes), you must call `ws.receive_data(None)` to inform `wsproto` of the connection closure and update its internal state. Failing to do so can leave the connection in an inconsistent state.
- gotcha For correct protocol behavior, both client and server connections *must* respond to certain control frames. Specifically, a received `Ping` event requires sending a `Pong` event (use `event.response()`), and a received `CloseConnection` event requires sending a `CloseConnection` event back (also `event.response()`).
- gotcha WebSocket data messages (TextMessage, BinaryMessage) can be fragmented across multiple `Message` events. The `data` field of these events represents only a chunk of the message. Applications need to buffer and reassemble these chunks until `event.message_finished` is `True` to get the complete logical message.
- breaking The primary `WSConnection` class was moved from `wsproto.connection.WSConnection` to `wsproto.WSConnection` in version 0.13.0. Additionally, the method to feed data into the connection was renamed from `receive_bytes` to `receive_data` in the same version.
Install
-
pip install wsproto
Imports
- WSConnection
from wsproto import WSConnection
- ConnectionType
from wsproto import ConnectionType
- Request
from wsproto.events import Request
- AcceptConnection
from wsproto.events import AcceptConnection
- Message
from wsproto.events import Message
- TextMessage
from wsproto.events import TextMessage
- CloseConnection
from wsproto.events import CloseConnection
Quickstart
from wsproto import WSConnection, ConnectionType
from wsproto.events import Request, AcceptConnection, TextMessage, CloseConnection
import socket
import os
# Example: Simple WebSocket client interaction (sans-I/O)
# This code demonstrates the wsproto logic; actual network I/O is omitted for brevity.
# In a real application, 'send_bytes_to_network' and 'receive_bytes_from_network'
# would interact with a socket or other I/O primitive.
def simulate_client_server_interaction():
client_ws = WSConnection(ConnectionType.CLIENT)
server_ws = WSConnection(ConnectionType.SERVER)
# Client initiates handshake
client_handshake_request = client_ws.send(Request(host="example.com", target="/"))
print(f"Client sends handshake request: {client_handshake_request!r}")
# Server receives handshake request
server_ws.receive_data(client_handshake_request)
for event in server_ws.events():
if isinstance(event, Request):
print(f"Server receives client Request: {event}")
server_handshake_response = server_ws.send(AcceptConnection())
print(f"Server sends handshake response: {server_handshake_response!r}")
break
# Client receives handshake response
client_ws.receive_data(server_handshake_response)
for event in client_ws.events():
if isinstance(event, AcceptConnection):
print(f"Client receives Server Acceptance: {event}")
break
# Client sends a message
client_message_bytes = client_ws.send(TextMessage(data="Hello from client!"))
print(f"Client sends message: {client_message_bytes!r}")
# Server receives the message
server_ws.receive_data(client_message_bytes)
for event in server_ws.events():
if isinstance(event, TextMessage):
print(f"Server receives message: {event.data!r}")
if event.message_finished:
# Server echoes back
server_response_bytes = server_ws.send(TextMessage(data=f"Echo: {event.data.decode()}"))
print(f"Server sends echo: {server_response_bytes!r}")
break
# Client receives server's echo
client_ws.receive_data(server_response_bytes)
for event in client_ws.events():
if isinstance(event, TextMessage):
print(f"Client receives echo: {event.data.decode()!r}")
break
# Client initiates close
client_close_bytes = client_ws.send(CloseConnection(code=1000, reason="Done"))
print(f"Client sends close frame: {client_close_bytes!r}")
# Server receives close
server_ws.receive_data(client_close_bytes)
for event in server_ws.events():
if isinstance(event, CloseConnection):
print(f"Server receives CloseConnection: {event}")
server_close_response = server_ws.send(event.response())
print(f"Server sends close response: {server_close_response!r}")
break
# Client receives server's close response
client_ws.receive_data(server_close_response)
for event in client_ws.events():
if isinstance(event, CloseConnection):
print(f"Client receives CloseConnection response: {event}")
break
simulate_client_server_interaction()