WebSockets state-machine based protocol implementation
npx @tessl/cli install tessl/pypi-wsproto@1.2.0A pure-Python implementation of a WebSocket protocol stack. wsproto is written from the ground up to be embeddable in any program, ensuring WebSocket communication as defined in RFC 6455 and RFC 7692 (compression extensions) regardless of programming paradigm. The library provides a purely in-memory solution defined in terms of data actions and WebSocket frames, without providing parsing, network, or concurrency layers.
pip install wsprotofrom wsproto import WSConnection, ConnectionTypeCommon event imports:
from wsproto.events import (
Request, AcceptConnection, RejectConnection, RejectData,
TextMessage, BytesMessage, CloseConnection,
Ping, Pong
)Low-level protocol imports:
from wsproto.connection import Connection, ConnectionState, CLIENT, SERVER
from wsproto.frame_protocol import (
FrameProtocol, FrameDecoder, MessageDecoder,
Opcode, CloseReason, Frame, Header, RsvBits, ParseFailed
)
from wsproto.extensions import Extension, PerMessageDeflate, SUPPORTED_EXTENSIONS
from wsproto.handshake import H11Handshake
from wsproto.utilities import (
ProtocolError, LocalProtocolError, RemoteProtocolError,
generate_nonce, generate_accept_token,
normed_header_dict, split_comma_header
)
from wsproto.typing import Headersfrom wsproto import WSConnection, ConnectionType
from wsproto.events import Request, AcceptConnection, TextMessage
# Create a client connection
ws = WSConnection(ConnectionType.CLIENT)
# Generate handshake request
data_to_send = ws.send(Request(host='echo.websocket.org', target='/'))
# Send data_to_send over your network connection
# ... network sending code ...
# Process received network data
ws.receive_data(received_bytes)
# Handle events
for event in ws.events():
if isinstance(event, AcceptConnection):
print("Connection established!")
elif isinstance(event, TextMessage):
print(f"Received text: {event.data}")from wsproto import WSConnection, ConnectionType
from wsproto.events import Request, AcceptConnection
# Create a server connection
ws = WSConnection(ConnectionType.SERVER)
# Process incoming handshake data
ws.receive_data(received_handshake_bytes)
# Handle events
for event in ws.events():
if isinstance(event, Request):
# Accept the connection
data_to_send = ws.send(AcceptConnection())
# Send data_to_send over networkwsproto uses a state-machine based architecture with clear separation of concerns:
This design enables maximum reusability across different network implementations, event loops, and concurrency models while maintaining complete RFC compliance.
Core connection establishment, state management, and lifecycle control for both client and server WebSocket connections.
class WSConnection:
def __init__(self, connection_type: ConnectionType) -> None: ...
def send(self, event: Event) -> bytes: ...
def receive_data(self, data: Optional[bytes]) -> None: ...
def events(self) -> Generator[Event, None, None]: ...
class ConnectionType(Enum):
CLIENT = 1
SERVER = 2
class ConnectionState(Enum):
CONNECTING = 0
OPEN = 1
REMOTE_CLOSING = 2
LOCAL_CLOSING = 3
CLOSED = 4
REJECTING = 5Comprehensive event-driven API covering all WebSocket operations including handshake, messages, control frames, and connection lifecycle.
class Event(ABC): ...
class Request(Event):
host: str
target: str
extensions: Union[Sequence[Extension], Sequence[str]] = field(default_factory=list)
extra_headers: Headers = field(default_factory=list)
subprotocols: List[str] = field(default_factory=list)
class AcceptConnection(Event):
subprotocol: Optional[str] = None
extensions: List[Extension] = field(default_factory=list)
extra_headers: Headers = field(default_factory=list)
class TextMessage(Event):
data: str
frame_finished: bool = True
message_finished: bool = True
class BytesMessage(Event):
data: bytes
frame_finished: bool = True
message_finished: bool = TrueWebSocket extensions support including RFC 7692 permessage-deflate compression with configurable parameters and negotiation.
class Extension:
name: str
def enabled(self) -> bool: ...
def offer(self) -> Union[bool, str]: ...
def accept(self, offer: str) -> Optional[Union[bool, str]]: ...
class PerMessageDeflate(Extension):
name = "permessage-deflate"
def __init__(
self,
client_no_context_takeover: bool = False,
client_max_window_bits: Optional[int] = None,
server_no_context_takeover: bool = False,
server_max_window_bits: Optional[int] = None,
) -> None: ...Direct frame protocol access for advanced use cases requiring fine-grained control over WebSocket frame generation and parsing.
class FrameProtocol:
def __init__(self, client: bool, extensions: List[Extension]) -> None: ...
def send_data(self, payload: Union[bytes, bytearray, str] = b"", fin: bool = True) -> bytes: ...
def ping(self, payload: bytes = b"") -> bytes: ...
def pong(self, payload: bytes = b"") -> bytes: ...
def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> bytes: ...
class Opcode(IntEnum):
CONTINUATION = 0x0
TEXT = 0x1
BINARY = 0x2
CLOSE = 0x8
PING = 0x9
PONG = 0xA
class CloseReason(IntEnum):
NORMAL_CLOSURE = 1000
GOING_AWAY = 1001
PROTOCOL_ERROR = 1002
# ... other close codesHelper functions for WebSocket handshake token generation and header processing.
def generate_nonce() -> bytes:
"""
Generate a WebSocket handshake nonce.
Creates a random 16-byte value encoded in base64, used for the
Sec-WebSocket-Key header in client handshake requests.
Returns:
Base64-encoded random nonce bytes
"""
def generate_accept_token(token: bytes) -> bytes:
"""
Generate WebSocket accept token for handshake response.
Takes the client's Sec-WebSocket-Key value and generates the
corresponding Sec-WebSocket-Accept value using SHA-1 and
the WebSocket GUID as specified in RFC 6455.
Args:
token: The Sec-WebSocket-Key value from client
Returns:
Base64-encoded accept token for server response
"""
def normed_header_dict(headers: Union[Headers, H11Headers]) -> Dict[bytes, bytes]:
"""
Normalize HTTP headers to a dictionary.
Converts header list to dictionary, joining multiple values
with commas where appropriate.
Args:
headers: Headers as list of tuples or h11 headers
Returns:
Dictionary mapping header names to values
"""
def split_comma_header(value: bytes) -> List[str]:
"""
Split comma-separated header value.
Parses header values that contain comma-separated lists,
such as extension and subprotocol headers.
Args:
value: Header value as bytes
Returns:
List of individual header values
"""from typing import List, Tuple, Generator, Optional, Union, Sequence, Dict
from dataclasses import dataclass, field
from enum import Enum, IntEnum
from h11._headers import Headers as H11Headers
Headers = List[Tuple[bytes, bytes]]
class ProtocolError(Exception): ...
class LocalProtocolError(ProtocolError): ...
class RemoteProtocolError(ProtocolError):
def __init__(self, message: str, event_hint: Optional[Event] = None) -> None: ...
event_hint: Optional[Event]