wsproto: Pure-Python WebSocket Protocol Implementation
raw JSON → 1.3.2 verified Tue May 12 auth: no python install: verified quickstart: stale
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.
pip install wsproto Common errors
error ModuleNotFoundError: No module named 'wsproto' ↓
cause The 'wsproto' library is not installed in your current Python environment.
fix
pip install wsproto
error AttributeError: 'Connection' object has no attribute 'send_message' ↓
cause Developers often expect a high-level 'send_message' method, but 'wsproto' provides a low-level 'send' method for sending individual WebSocket frames.
fix
Use the
connection.send() method with raw bytes and an appropriate WebSocket opcode (e.g., Opcode.TEXT, Opcode.BINARY). error wsproto.utilities.InvalidStateError ↓
cause An operation was attempted on the `wsproto.Connection` object when it was not in the correct state to perform that action (e.g., calling `accept()` on an already accepted connection).
fix
Implement proper state handling by checking
connection.state before attempting operations, ensuring they align with the connection's current lifecycle stage. error wsproto.utilities.ProtocolError ↓
cause The `wsproto` connection received bytes that do not conform to the WebSocket protocol specification (e.g., malformed frame header, invalid opcode, or reserved bits set incorrectly).
fix
Verify that the data being sent to
connection.receive_data() is valid WebSocket protocol data, debugging the sender or network for corruption or non-compliance. 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`. ↓
fix Be prepared to manage your own network sockets and I/O loops. `wsproto.WSConnection.send()` returns bytes to transmit, and `wsproto.WSConnection.receive_data(data)` takes bytes received from the network.
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. ↓
fix Upon detecting a connection drop (e.g., `recv()` returning `b''`), call `ws.receive_data(None)` immediately before tearing down the connection.
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()`). ↓
fix Always iterate `ws.events()` after receiving data and ensure you handle `Ping` and `CloseConnection` events by sending their respective responses using `ws.send(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. ↓
fix Maintain a buffer for incoming message data and append `event.data` until `event.message_finished` indicates the full message has arrived. Then, process the complete buffered 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. ↓
fix Update imports from `from wsproto.connection import WSConnection` to `from wsproto import WSConnection`. Replace calls to `ws.receive_bytes(data)` with `ws.receive_data(data)`.
gotcha When processing `wsproto` events, `event.data` for `TextMessage` events is a Unicode string (`str`), while for `BinaryMessage` events it is bytes (`bytes`). Attempting to call `.decode()` on `event.data` from a `TextMessage` will result in an `AttributeError` because strings do not have a `.decode()` method. ↓
fix Check the type of the event (e.g., `isinstance(event, wsproto.events.TextMessage)`) before processing its data. If `event` is a `TextMessage`, `event.data` is already a string and should be used directly. If `event` is a `BinaryMessage`, `event.data` is bytes and can be decoded if a string representation is needed.
gotcha When processing `Message` events (like `TextMessage` or `BinaryMessage`), be aware of the type of `event.data`. For `TextMessage` events, `event.data` is a `str`. For `BinaryMessage` events, `event.data` is `bytes`. Attempting to `decode()` a `str` or `encode()` bytes unnecessarily will result in an `AttributeError` or `TypeError`. ↓
fix For `TextMessage` events, `event.data` is already a string; use it directly or `encode()` it to bytes if the target API expects bytes. For `BinaryMessage` events, `event.data` is bytes; use it directly or `decode()` it to a string if the target API expects a string (specifying encoding).
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.10s 18.2M
3.10 alpine (musl) - - 0.12s 18.2M
3.10 slim (glibc) wheel 1.5s 0.07s 19M
3.10 slim (glibc) - - 0.07s 19M
3.11 alpine (musl) wheel - 0.13s 20.1M
3.11 alpine (musl) - - 0.15s 20.1M
3.11 slim (glibc) wheel 1.7s 0.13s 21M
3.11 slim (glibc) - - 0.12s 21M
3.12 alpine (musl) wheel - 0.12s 12.0M
3.12 alpine (musl) - - 0.14s 12.0M
3.12 slim (glibc) wheel 1.5s 0.12s 12M
3.12 slim (glibc) - - 0.13s 12M
3.13 alpine (musl) wheel - 0.10s 11.7M
3.13 alpine (musl) - - 0.12s 11.6M
3.13 slim (glibc) wheel 1.5s 0.11s 12M
3.13 slim (glibc) - - 0.11s 12M
3.9 alpine (musl) wheel - 0.10s 17.7M
3.9 alpine (musl) - - 0.12s 17.7M
3.9 slim (glibc) wheel 1.8s 0.08s 18M
3.9 slim (glibc) - - 0.10s 18M
Imports
- WSConnection wrong
from wsproto.connection import WSConnectioncorrectfrom wsproto import WSConnection - ConnectionType wrong
from wsproto.connection import ConnectionTypecorrectfrom 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 stale last tested: 2026-04-23
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()