0
# Protocol Implementation
1
2
Sans-I/O WebSocket protocol implementation providing the core WebSocket functionality independent of I/O handling. This enables custom integrations, advanced use cases, and provides the foundation for both asyncio and synchronous implementations.
3
4
## Capabilities
5
6
### Protocol Base Classes
7
8
Core protocol implementation that handles WebSocket frame processing, state management, and message assembly without performing any I/O operations.
9
10
```python { .api }
11
class Protocol:
12
"""
13
Base WebSocket protocol implementation (Sans-I/O).
14
15
Handles WebSocket frame processing, connection state management,
16
and message assembly/disassembly without performing I/O operations.
17
"""
18
19
def __init__(
20
self,
21
side: Side,
22
*,
23
logger: LoggerLike = None,
24
max_size: int = 2**20,
25
extensions: List[ExtensionFactory] = None
26
):
27
"""
28
Initialize protocol handler.
29
30
Parameters:
31
- side: Side.CLIENT or Side.SERVER
32
- logger: Logger instance for protocol logging
33
- max_size: Maximum message size (bytes)
34
- extensions: List of WebSocket extensions
35
"""
36
37
@property
38
def state(self) -> State:
39
"""Get current connection state."""
40
41
@property
42
def side(self) -> Side:
43
"""Get protocol side (CLIENT or SERVER)."""
44
45
@property
46
def close_code(self) -> int | None:
47
"""Get close code if connection is closed."""
48
49
@property
50
def close_reason(self) -> str | None:
51
"""Get close reason if connection is closed."""
52
53
def send_text(self, data: str) -> List[bytes]:
54
"""
55
Create frames for sending text message.
56
57
Parameters:
58
- data: Text message to send
59
60
Returns:
61
List[bytes]: List of frame bytes to transmit
62
63
Raises:
64
- ProtocolError: If connection state doesn't allow sending
65
"""
66
67
def send_binary(self, data: bytes) -> List[bytes]:
68
"""
69
Create frames for sending binary message.
70
71
Parameters:
72
- data: Binary message to send
73
74
Returns:
75
List[bytes]: List of frame bytes to transmit
76
77
Raises:
78
- ProtocolError: If connection state doesn't allow sending
79
"""
80
81
def send_ping(self, data: bytes = b"") -> List[bytes]:
82
"""
83
Create ping frame.
84
85
Parameters:
86
- data: Optional ping payload
87
88
Returns:
89
List[bytes]: List of frame bytes to transmit
90
91
Raises:
92
- ProtocolError: If connection state doesn't allow ping
93
"""
94
95
def send_pong(self, data: bytes = b"") -> List[bytes]:
96
"""
97
Create pong frame.
98
99
Parameters:
100
- data: Pong payload (should match received ping)
101
102
Returns:
103
List[bytes]: List of frame bytes to transmit
104
105
Raises:
106
- ProtocolError: If connection state doesn't allow pong
107
"""
108
109
def send_close(self, code: int = 1000, reason: str = "") -> List[bytes]:
110
"""
111
Create close frame and update connection state.
112
113
Parameters:
114
- code: Close code (1000 for normal closure)
115
- reason: Human-readable close reason
116
117
Returns:
118
List[bytes]: List of frame bytes to transmit
119
120
Raises:
121
- ProtocolError: If close code is invalid
122
"""
123
124
def receive_data(self, data: bytes) -> List[Event]:
125
"""
126
Process received data and return events.
127
128
Parameters:
129
- data: Raw bytes received from network
130
131
Returns:
132
List[Event]: List of protocol events (messages, pings, pongs, close)
133
134
Raises:
135
- ProtocolError: If data violates WebSocket protocol
136
"""
137
138
def receive_eof(self) -> List[Event]:
139
"""
140
Handle end-of-file condition.
141
142
Returns:
143
List[Event]: List of protocol events (typically ConnectionClosed)
144
"""
145
146
class ClientProtocol(Protocol):
147
"""
148
WebSocket client protocol implementation.
149
150
Handles client-specific protocol behavior including handshake validation
151
and client-side frame masking.
152
"""
153
154
def __init__(
155
self,
156
*,
157
logger: LoggerLike = None,
158
max_size: int = 2**20,
159
extensions: List[ClientExtensionFactory] = None
160
):
161
"""
162
Initialize client protocol.
163
164
Parameters:
165
- logger: Logger instance for protocol logging
166
- max_size: Maximum message size (bytes)
167
- extensions: List of client WebSocket extensions
168
"""
169
170
class ServerProtocol(Protocol):
171
"""
172
WebSocket server protocol implementation.
173
174
Handles server-specific protocol behavior including handshake processing
175
and server-side frame handling (no masking).
176
"""
177
178
def __init__(
179
self,
180
*,
181
logger: LoggerLike = None,
182
max_size: int = 2**20,
183
extensions: List[ServerExtensionFactory] = None
184
):
185
"""
186
Initialize server protocol.
187
188
Parameters:
189
- logger: Logger instance for protocol logging
190
- max_size: Maximum message size (bytes)
191
- extensions: List of server WebSocket extensions
192
"""
193
```
194
195
### Protocol State Management
196
197
Enumerations and utilities for managing WebSocket connection state and side identification.
198
199
```python { .api }
200
class Side(Enum):
201
"""WebSocket connection side identification."""
202
CLIENT = "client"
203
SERVER = "server"
204
205
class State(Enum):
206
"""WebSocket connection states."""
207
CONNECTING = "connecting" # Initial state during handshake
208
OPEN = "open" # Connection established and ready
209
CLOSING = "closing" # Close frame sent, waiting for close response
210
CLOSED = "closed" # Connection fully closed
211
```
212
213
### Protocol Events
214
215
Event types returned by the protocol when processing received data.
216
217
```python { .api }
218
class Event:
219
"""Base class for protocol events."""
220
pass
221
222
class TextMessage(Event):
223
"""Text message received event."""
224
def __init__(self, data: str):
225
self.data = data
226
227
class BinaryMessage(Event):
228
"""Binary message received event."""
229
def __init__(self, data: bytes):
230
self.data = data
231
232
class Ping(Event):
233
"""Ping frame received event."""
234
def __init__(self, data: bytes):
235
self.data = data
236
237
class Pong(Event):
238
"""Pong frame received event."""
239
def __init__(self, data: bytes):
240
self.data = data
241
242
class Close(Event):
243
"""Close frame received event."""
244
def __init__(self, code: int, reason: str):
245
self.code = code
246
self.reason = reason
247
248
class ConnectionClosed(Event):
249
"""Connection closed event (EOF or error)."""
250
def __init__(self, exception: Exception = None):
251
self.exception = exception
252
```
253
254
## Usage Examples
255
256
### Basic Protocol Usage
257
258
```python
259
from websockets.protocol import ClientProtocol, Side, State
260
from websockets.frames import Frame, Opcode
261
262
def basic_protocol_example():
263
"""Demonstrate basic protocol usage."""
264
# Create client protocol
265
protocol = ClientProtocol(max_size=1024*1024) # 1MB max message
266
267
print(f"Initial state: {protocol.state}")
268
print(f"Protocol side: {protocol.side}")
269
270
# Send a text message
271
frames_to_send = protocol.send_text("Hello, WebSocket!")
272
print(f"Frames to send: {len(frames_to_send)}")
273
274
# Send a ping
275
ping_frames = protocol.send_ping(b"ping-data")
276
print(f"Ping frames: {len(ping_frames)}")
277
278
# Simulate receiving data (this would come from network)
279
# In real usage, you'd receive actual bytes from socket
280
received_data = b"..." # Raw frame bytes
281
# events = protocol.receive_data(received_data)
282
283
# Close connection
284
close_frames = protocol.send_close(1000, "Normal closure")
285
print(f"Close frames: {len(close_frames)}")
286
print(f"Final state: {protocol.state}")
287
288
basic_protocol_example()
289
```
290
291
### Custom Protocol Integration
292
293
```python
294
import socket
295
from websockets.protocol import ServerProtocol
296
from websockets.exceptions import ProtocolError
297
298
class CustomWebSocketServer:
299
"""Example of custom WebSocket server using Sans-I/O protocol."""
300
301
def __init__(self, host: str, port: int):
302
self.host = host
303
self.port = port
304
self.socket = None
305
306
def start(self):
307
"""Start the custom server."""
308
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
309
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
310
self.socket.bind((self.host, self.port))
311
self.socket.listen(5)
312
313
print(f"Custom WebSocket server listening on {self.host}:{self.port}")
314
315
while True:
316
try:
317
client_socket, address = self.socket.accept()
318
print(f"Client connected: {address}")
319
320
# Handle client in separate method
321
self.handle_client(client_socket, address)
322
323
except KeyboardInterrupt:
324
break
325
except Exception as e:
326
print(f"Server error: {e}")
327
328
self.socket.close()
329
330
def handle_client(self, client_socket: socket.socket, address):
331
"""Handle individual client connection."""
332
protocol = ServerProtocol(max_size=64*1024) # 64KB max message
333
334
try:
335
while True:
336
# Receive data from client
337
data = client_socket.recv(4096)
338
if not data:
339
# Client disconnected
340
events = protocol.receive_eof()
341
break
342
343
# Process data through protocol
344
try:
345
events = protocol.receive_data(data)
346
except ProtocolError as e:
347
print(f"Protocol error: {e}")
348
break
349
350
# Handle protocol events
351
for event in events:
352
response_frames = self.process_event(event, protocol)
353
354
# Send response frames
355
for frame_bytes in response_frames:
356
client_socket.send(frame_bytes)
357
358
# Check if connection should close
359
if protocol.state == State.CLOSED:
360
break
361
362
except Exception as e:
363
print(f"Client error: {e}")
364
finally:
365
client_socket.close()
366
print(f"Client disconnected: {address}")
367
368
def process_event(self, event, protocol):
369
"""Process protocol events and return response frames."""
370
from websockets.protocol import TextMessage, BinaryMessage, Ping, Close
371
372
if isinstance(event, TextMessage):
373
# Echo text messages
374
return protocol.send_text(f"Echo: {event.data}")
375
376
elif isinstance(event, BinaryMessage):
377
# Echo binary messages
378
return protocol.send_binary(b"Echo: " + event.data)
379
380
elif isinstance(event, Ping):
381
# Respond to pings with pongs
382
return protocol.send_pong(event.data)
383
384
elif isinstance(event, Close):
385
# Respond to close frames
386
return protocol.send_close(event.code, event.reason)
387
388
return []
389
390
# Example usage
391
def run_custom_server():
392
server = CustomWebSocketServer("localhost", 8765)
393
try:
394
server.start()
395
except KeyboardInterrupt:
396
print("\nServer stopped")
397
398
# Uncomment to run
399
# run_custom_server()
400
```
401
402
### Protocol State Machine
403
404
```python
405
from websockets.protocol import ClientProtocol, State
406
from websockets.exceptions import ProtocolError
407
408
def protocol_state_machine_example():
409
"""Demonstrate protocol state transitions."""
410
protocol = ClientProtocol()
411
412
def print_state():
413
print(f"Current state: {protocol.state}")
414
415
print("=== Protocol State Machine Example ===")
416
417
# Initial state
418
print_state() # Should be CONNECTING initially
419
420
# Simulate successful handshake (this would be done by connection layer)
421
# protocol._state = State.OPEN # Private attribute, don't do this in real code
422
423
try:
424
# Try to send message in OPEN state
425
frames = protocol.send_text("Hello")
426
print(f"Sent text message: {len(frames)} frames")
427
print_state()
428
429
# Send ping
430
ping_frames = protocol.send_ping(b"test")
431
print(f"Sent ping: {len(ping_frames)} frames")
432
print_state()
433
434
# Start closing
435
close_frames = protocol.send_close(1000, "Normal closure")
436
print(f"Sent close frame: {len(close_frames)} frames")
437
print_state() # Should be CLOSING
438
439
# Try to send after close (should fail)
440
try:
441
protocol.send_text("This should fail")
442
except ProtocolError as e:
443
print(f"Expected error: {e}")
444
445
# Simulate receiving close response
446
# In real usage, this would come from receive_data()
447
# protocol._state = State.CLOSED
448
print_state()
449
450
except ProtocolError as e:
451
print(f"Protocol error: {e}")
452
453
protocol_state_machine_example()
454
```
455
456
### Frame Processing Example
457
458
```python
459
from websockets.protocol import ServerProtocol
460
from websockets.frames import Frame, Opcode
461
import struct
462
463
def frame_processing_example():
464
"""Demonstrate low-level frame processing."""
465
protocol = ServerProtocol()
466
467
# Create a simple text frame manually (for demonstration)
468
message = "Hello, WebSocket!"
469
message_bytes = message.encode('utf-8')
470
471
# WebSocket frame format (simplified)
472
frame_data = bytearray()
473
474
# First byte: FIN=1, RSV=000, Opcode=TEXT(1)
475
frame_data.append(0x81) # 10000001
476
477
# Payload length
478
if len(message_bytes) < 126:
479
frame_data.append(len(message_bytes))
480
elif len(message_bytes) < 65536:
481
frame_data.append(126)
482
frame_data.extend(struct.pack('!H', len(message_bytes)))
483
else:
484
frame_data.append(127)
485
frame_data.extend(struct.pack('!Q', len(message_bytes)))
486
487
# Payload data
488
frame_data.extend(message_bytes)
489
490
print(f"Created frame: {len(frame_data)} bytes")
491
print(f"Frame hex: {frame_data.hex()}")
492
493
# Process frame through protocol
494
try:
495
events = protocol.receive_data(bytes(frame_data))
496
print(f"Generated {len(events)} events")
497
498
for event in events:
499
if hasattr(event, 'data'):
500
print(f"Event data: {event.data}")
501
502
except Exception as e:
503
print(f"Frame processing error: {e}")
504
505
frame_processing_example()
506
```
507
508
### Extension Integration
509
510
```python
511
from websockets.protocol import ClientProtocol
512
from websockets.extensions.base import Extension
513
514
class SimpleExtension(Extension):
515
"""Example custom extension."""
516
517
def __init__(self, name: str):
518
super().__init__()
519
self.name = name
520
521
def encode(self, frame):
522
"""Process outgoing frames."""
523
# Simple example: add prefix to text frames
524
if frame.opcode == 1: # TEXT frame
525
modified_data = f"[{self.name}] ".encode() + frame.data
526
return frame._replace(data=modified_data)
527
return frame
528
529
def decode(self, frame):
530
"""Process incoming frames."""
531
# Simple example: remove prefix from text frames
532
if frame.opcode == 1: # TEXT frame
533
prefix = f"[{self.name}] ".encode()
534
if frame.data.startswith(prefix):
535
modified_data = frame.data[len(prefix):]
536
return frame._replace(data=modified_data)
537
return frame
538
539
def extension_example():
540
"""Demonstrate protocol with custom extensions."""
541
# Create extension
542
extension = SimpleExtension("MyExt")
543
544
# Create protocol with extension
545
# Note: Real extension integration is more complex
546
protocol = ClientProtocol()
547
548
print("Extension integration example")
549
print(f"Extension name: {extension.name}")
550
551
# In a real implementation, extensions would be negotiated
552
# during handshake and integrated into the protocol
553
554
extension_example()
555
```