0
# Data Structures
1
2
Core data structures for HTTP headers, WebSocket frames, close codes, opcodes, and type definitions used throughout the WebSocket implementation. These provide the foundation for protocol handling and message processing.
3
4
## Capabilities
5
6
### HTTP Headers Management
7
8
Case-insensitive HTTP headers container with dict-like interface for WebSocket handshake processing.
9
10
```python { .api }
11
class Headers:
12
"""
13
Case-insensitive HTTP headers container.
14
15
Provides dict-like interface for managing HTTP headers used in
16
WebSocket handshake with proper case-insensitive handling.
17
"""
18
19
def __init__(self, *args, **kwargs):
20
"""
21
Initialize headers container.
22
23
Parameters:
24
- *args: Dict, list of tuples, or Headers instance
25
- **kwargs: Header key-value pairs
26
27
Examples:
28
Headers({"Content-Type": "text/html"})
29
Headers([("Host", "example.com"), ("Origin", "https://app.com")])
30
Headers(host="example.com", origin="https://app.com")
31
"""
32
33
def __getitem__(self, key: str) -> str:
34
"""
35
Get header value by name (case-insensitive).
36
37
Parameters:
38
- key: Header name
39
40
Returns:
41
str: Header value
42
43
Raises:
44
- KeyError: If header doesn't exist
45
"""
46
47
def __setitem__(self, key: str, value: str) -> None:
48
"""
49
Set header value by name (case-insensitive).
50
51
Parameters:
52
- key: Header name
53
- value: Header value
54
"""
55
56
def __delitem__(self, key: str) -> None:
57
"""
58
Delete header by name (case-insensitive).
59
60
Parameters:
61
- key: Header name
62
63
Raises:
64
- KeyError: If header doesn't exist
65
"""
66
67
def __contains__(self, key: str) -> bool:
68
"""
69
Check if header exists (case-insensitive).
70
71
Parameters:
72
- key: Header name
73
74
Returns:
75
bool: True if header exists
76
"""
77
78
def __iter__(self):
79
"""Iterate over header names."""
80
81
def __len__(self) -> int:
82
"""Get number of headers."""
83
84
def get(self, key: str, default: str = None) -> str | None:
85
"""
86
Get header value with default.
87
88
Parameters:
89
- key: Header name (case-insensitive)
90
- default: Default value if header doesn't exist
91
92
Returns:
93
str | None: Header value or default
94
"""
95
96
def get_all(self, key: str) -> List[str]:
97
"""
98
Get all values for a header name.
99
100
Parameters:
101
- key: Header name (case-insensitive)
102
103
Returns:
104
List[str]: List of all values for the header
105
"""
106
107
def add(self, key: str, value: str) -> None:
108
"""
109
Add header value (allows multiple values for same name).
110
111
Parameters:
112
- key: Header name
113
- value: Header value to add
114
"""
115
116
def items(self):
117
"""Iterate over (name, value) pairs."""
118
119
def keys(self):
120
"""Get header names."""
121
122
def values(self):
123
"""Get header values."""
124
125
def copy(self) -> Headers:
126
"""Create a copy of the headers."""
127
128
def update(self, *args, **kwargs) -> None:
129
"""
130
Update headers with new values.
131
132
Parameters same as __init__()
133
"""
134
135
# Type alias for header input formats
136
HeadersLike = Union[Headers, Dict[str, str], List[Tuple[str, str]]]
137
138
class MultipleValuesError(Exception):
139
"""
140
Raised when single-value header has multiple values.
141
142
Some HTTP headers should only have one value, but the headers
143
container received multiple values for the same header name.
144
"""
145
146
def __init__(self, key: str, values: List[str]):
147
"""
148
Initialize multiple values error.
149
150
Parameters:
151
- key: Header name that has multiple values
152
- values: List of values found for the header
153
"""
154
self.key = key
155
self.values = values
156
super().__init__(f"Multiple values for header {key}: {values}")
157
```
158
159
### WebSocket Frame Structures
160
161
Low-level WebSocket frame representation and frame opcodes for protocol implementation.
162
163
```python { .api }
164
class Frame:
165
"""
166
WebSocket frame representation.
167
168
Represents a single WebSocket frame with opcode, payload data,
169
and control flags according to RFC 6455.
170
"""
171
172
def __init__(
173
self,
174
opcode: Opcode,
175
data: bytes,
176
fin: bool = True,
177
rsv1: bool = False,
178
rsv2: bool = False,
179
rsv3: bool = False
180
):
181
"""
182
Initialize WebSocket frame.
183
184
Parameters:
185
- opcode: Frame opcode (text, binary, control, etc.)
186
- data: Frame payload data
187
- fin: Final fragment flag (True for complete messages)
188
- rsv1: Reserved bit 1 (used by extensions)
189
- rsv2: Reserved bit 2 (used by extensions)
190
- rsv3: Reserved bit 3 (used by extensions)
191
"""
192
self.opcode = opcode
193
self.data = data
194
self.fin = fin
195
self.rsv1 = rsv1
196
self.rsv2 = rsv2
197
self.rsv3 = rsv3
198
199
def __repr__(self) -> str:
200
"""String representation of frame."""
201
202
@property
203
def is_data_frame(self) -> bool:
204
"""Check if frame carries data (text/binary)."""
205
206
@property
207
def is_control_frame(self) -> bool:
208
"""Check if frame is control frame (close/ping/pong)."""
209
210
class Opcode(Enum):
211
"""
212
WebSocket frame opcodes as defined in RFC 6455.
213
214
Specifies the type of WebSocket frame and how the
215
payload should be interpreted.
216
"""
217
CONT = 0 # Continuation frame
218
TEXT = 1 # Text frame (UTF-8 data)
219
BINARY = 2 # Binary frame (arbitrary data)
220
# 3-7 reserved for future data frames
221
CLOSE = 8 # Close connection frame
222
PING = 9 # Ping frame
223
PONG = 10 # Pong frame
224
# 11-15 reserved for future control frames
225
226
# Opcode constants for convenience
227
OP_CONT = Opcode.CONT
228
OP_TEXT = Opcode.TEXT
229
OP_BINARY = Opcode.BINARY
230
OP_CLOSE = Opcode.CLOSE
231
OP_PING = Opcode.PING
232
OP_PONG = Opcode.PONG
233
234
# Opcode groups
235
DATA_OPCODES = frozenset([Opcode.CONT, Opcode.TEXT, Opcode.BINARY])
236
CTRL_OPCODES = frozenset([Opcode.CLOSE, Opcode.PING, Opcode.PONG])
237
```
238
239
### Close Frame and Close Codes
240
241
WebSocket connection close frame structure and standardized close codes.
242
243
```python { .api }
244
class Close:
245
"""
246
WebSocket close frame representation.
247
248
Represents close frame with close code and optional reason,
249
used for graceful connection termination.
250
"""
251
252
def __init__(self, code: int, reason: str = ""):
253
"""
254
Initialize close frame.
255
256
Parameters:
257
- code: Close code (see CloseCode enum)
258
- reason: Optional human-readable close reason (max 123 bytes UTF-8)
259
260
Raises:
261
- ValueError: If code is invalid or reason is too long
262
"""
263
self.code = code
264
self.reason = reason
265
266
def __repr__(self) -> str:
267
"""String representation of close frame."""
268
269
@classmethod
270
def parse(cls, data: bytes) -> Close:
271
"""
272
Parse close frame payload.
273
274
Parameters:
275
- data: Close frame payload bytes
276
277
Returns:
278
Close: Parsed close frame
279
280
Raises:
281
- ValueError: If payload format is invalid
282
"""
283
284
def serialize(self) -> bytes:
285
"""
286
Serialize close frame to bytes.
287
288
Returns:
289
bytes: Close frame payload for transmission
290
"""
291
292
class CloseCode(Enum):
293
"""
294
Standard WebSocket close codes as defined in RFC 6455.
295
296
Indicates the reason for closing the WebSocket connection.
297
"""
298
NORMAL_CLOSURE = 1000 # Normal closure
299
GOING_AWAY = 1001 # Endpoint going away
300
PROTOCOL_ERROR = 1002 # Protocol error
301
UNSUPPORTED_DATA = 1003 # Unsupported data type
302
# 1004 reserved
303
NO_STATUS_RCVD = 1005 # No status received (reserved)
304
ABNORMAL_CLOSURE = 1006 # Abnormal closure (reserved)
305
INVALID_FRAME_PAYLOAD_DATA = 1007 # Invalid UTF-8 or extension data
306
POLICY_VIOLATION = 1008 # Policy violation
307
MESSAGE_TOO_BIG = 1009 # Message too big
308
MANDATORY_EXTENSION = 1010 # Missing mandatory extension
309
INTERNAL_ERROR = 1011 # Internal server error
310
SERVICE_RESTART = 1012 # Service restart
311
TRY_AGAIN_LATER = 1013 # Try again later
312
BAD_GATEWAY = 1014 # Bad gateway
313
TLS_HANDSHAKE = 1015 # TLS handshake failure (reserved)
314
315
# Close code constants for convenience
316
CLOSE_CODES = {
317
1000: "Normal Closure",
318
1001: "Going Away",
319
1002: "Protocol Error",
320
1003: "Unsupported Data",
321
1005: "No Status Received",
322
1006: "Abnormal Closure",
323
1007: "Invalid Frame Payload Data",
324
1008: "Policy Violation",
325
1009: "Message Too Big",
326
1010: "Mandatory Extension",
327
1011: "Internal Error",
328
1012: "Service Restart",
329
1013: "Try Again Later",
330
1014: "Bad Gateway",
331
1015: "TLS Handshake"
332
}
333
```
334
335
### Type Definitions
336
337
Common type aliases and definitions used throughout the WebSocket implementation.
338
339
```python { .api }
340
# Core data types
341
Data = Union[str, bytes] # WebSocket message content (text or binary)
342
343
# Header-related types
344
HeadersLike = Union[Headers, Dict[str, str], List[Tuple[str, str]]]
345
346
# WebSocket protocol types
347
Origin = str # Origin header value
348
Subprotocol = str # WebSocket subprotocol name
349
ExtensionName = str # WebSocket extension name
350
ExtensionParameter = Tuple[str, Optional[str]] # Extension parameter (name, value)
351
352
# HTTP-related types
353
StatusLike = Union[int, HTTPStatus] # HTTP status code types
354
355
# Logging types
356
LoggerLike = Union[logging.Logger, logging.LoggerAdapter] # Logger compatibility
357
358
# Async types (for type hints)
359
if TYPE_CHECKING:
360
from typing import AsyncIterator, Awaitable, Callable, Coroutine
361
362
# Handler function types
363
AsyncHandler = Callable[[Any], Awaitable[None]]
364
SyncHandler = Callable[[Any], None]
365
366
# Connection types
367
ConnectionType = Union['ClientConnection', 'ServerConnection']
368
```
369
370
## Usage Examples
371
372
### Headers Management
373
374
```python
375
from websockets.datastructures import Headers, MultipleValuesError
376
377
def headers_examples():
378
"""Demonstrate Headers usage."""
379
380
# Create headers in different ways
381
headers1 = Headers({"Host": "example.com", "Origin": "https://app.com"})
382
headers2 = Headers([("Content-Type", "text/html"), ("Accept", "text/html")])
383
headers3 = Headers(host="example.com", origin="https://app.com")
384
385
print("=== Basic Headers Operations ===")
386
387
# Case-insensitive access
388
headers = Headers({"Content-Type": "application/json"})
389
print(f"Content-Type: {headers['content-type']}") # Works!
390
print(f"CONTENT-TYPE: {headers['CONTENT-TYPE']}") # Also works!
391
392
# Check existence
393
print(f"Has Content-Type: {'content-type' in headers}")
394
395
# Get with default
396
print(f"Authorization: {headers.get('authorization', 'None')}")
397
398
# Add multiple values
399
headers.add("Accept", "text/html")
400
headers.add("Accept", "application/json")
401
print(f"All Accept values: {headers.get_all('accept')}")
402
403
# Iterate over headers
404
print("\nAll headers:")
405
for name, value in headers.items():
406
print(f" {name}: {value}")
407
408
# Copy and update
409
new_headers = headers.copy()
410
new_headers.update({"User-Agent": "MyApp/1.0", "Accept-Language": "en-US"})
411
412
print(f"\nUpdated headers count: {len(new_headers)}")
413
414
# Handle multiple values error
415
try:
416
# This would raise MultipleValuesError if header should be single-value
417
single_value = headers["accept"] # But Accept can have multiple values
418
print(f"Single Accept value: {single_value}")
419
except MultipleValuesError as e:
420
print(f"Multiple values error: {e}")
421
422
headers_examples()
423
```
424
425
### Frame Processing
426
427
```python
428
from websockets.datastructures import Frame, Opcode, Close, CloseCode
429
430
def frame_examples():
431
"""Demonstrate Frame and Close usage."""
432
433
print("=== WebSocket Frame Examples ===")
434
435
# Create different frame types
436
text_frame = Frame(Opcode.TEXT, b"Hello, WebSocket!", fin=True)
437
binary_frame = Frame(Opcode.BINARY, b"\x00\x01\x02\x03", fin=True)
438
ping_frame = Frame(Opcode.PING, b"ping-data")
439
pong_frame = Frame(Opcode.PONG, b"pong-data")
440
441
frames = [text_frame, binary_frame, ping_frame, pong_frame]
442
443
for frame in frames:
444
print(f"Frame: {frame}")
445
print(f" Opcode: {frame.opcode}")
446
print(f" Data length: {len(frame.data)}")
447
print(f" Is data frame: {frame.is_data_frame}")
448
print(f" Is control frame: {frame.is_control_frame}")
449
print()
450
451
# Close frame examples
452
print("=== Close Frame Examples ===")
453
454
# Create close frames
455
normal_close = Close(CloseCode.NORMAL_CLOSURE, "Normal shutdown")
456
error_close = Close(CloseCode.PROTOCOL_ERROR, "Invalid frame received")
457
458
close_frames = [normal_close, error_close]
459
460
for close_frame in close_frames:
461
print(f"Close frame: {close_frame}")
462
print(f" Code: {close_frame.code}")
463
print(f" Reason: {close_frame.reason}")
464
465
# Serialize and parse
466
serialized = close_frame.serialize()
467
parsed = Close.parse(serialized)
468
print(f" Serialized: {serialized.hex()}")
469
print(f" Parsed back: {parsed}")
470
print()
471
472
# Close code information
473
print("=== Close Codes ===")
474
for code, description in CLOSE_CODES.items():
475
print(f" {code}: {description}")
476
477
frame_examples()
478
```
479
480
### Type Usage Examples
481
482
```python
483
from websockets.datastructures import Data, HeadersLike, LoggerLike
484
import logging
485
from typing import Union, List, Tuple, Dict
486
487
def type_examples():
488
"""Demonstrate type usage."""
489
490
print("=== Type Usage Examples ===")
491
492
# Data type examples
493
def process_message(data: Data) -> str:
494
"""Process WebSocket message data."""
495
if isinstance(data, str):
496
return f"Text message: {data}"
497
elif isinstance(data, bytes):
498
return f"Binary message: {len(data)} bytes"
499
500
# Test with different data types
501
text_msg = "Hello, World!"
502
binary_msg = b"\x00\x01\x02\x03"
503
504
print(process_message(text_msg))
505
print(process_message(binary_msg))
506
507
# HeadersLike examples
508
def process_headers(headers: HeadersLike) -> Headers:
509
"""Process headers in various input formats."""
510
if isinstance(headers, Headers):
511
return headers
512
elif isinstance(headers, dict):
513
return Headers(headers)
514
elif isinstance(headers, list):
515
return Headers(headers)
516
else:
517
raise ValueError("Invalid headers format")
518
519
# Test different header formats
520
header_formats = [
521
{"Host": "example.com"},
522
[("Host", "example.com"), ("Origin", "https://app.com")],
523
Headers(host="example.com")
524
]
525
526
for headers in header_formats:
527
processed = process_headers(headers)
528
print(f"Processed headers: {dict(processed.items())}")
529
530
# Logger type examples
531
def setup_logging(logger: LoggerLike) -> None:
532
"""Setup logging with different logger types."""
533
if hasattr(logger, 'info'):
534
logger.info("Logger setup complete")
535
536
# Test with different logger types
537
standard_logger = logging.getLogger("test")
538
adapter_logger = logging.LoggerAdapter(standard_logger, {"context": "websocket"})
539
540
setup_logging(standard_logger)
541
setup_logging(adapter_logger)
542
543
type_examples()
544
```
545
546
### Custom Data Structures
547
548
```python
549
from websockets.datastructures import Headers, Frame, Opcode
550
from typing import NamedTuple, Optional
551
import json
552
553
class WebSocketMessage(NamedTuple):
554
"""Custom message structure for application-level messages."""
555
type: str
556
data: Union[str, bytes, dict]
557
timestamp: float
558
headers: Optional[Headers] = None
559
560
class MessageFrame:
561
"""Custom frame wrapper with application metadata."""
562
563
def __init__(self, frame: Frame, message_id: str = None):
564
self.frame = frame
565
self.message_id = message_id
566
self.processed_at = time.time()
567
568
@property
569
def is_text(self) -> bool:
570
return self.frame.opcode == Opcode.TEXT
571
572
@property
573
def is_binary(self) -> bool:
574
return self.frame.opcode == Opcode.BINARY
575
576
def to_json(self) -> str:
577
"""Convert frame to JSON representation."""
578
return json.dumps({
579
"message_id": self.message_id,
580
"opcode": self.frame.opcode.name,
581
"data_length": len(self.frame.data),
582
"processed_at": self.processed_at,
583
"fin": self.frame.fin
584
})
585
586
def custom_structures_example():
587
"""Demonstrate custom data structures."""
588
import time
589
590
print("=== Custom Data Structures ===")
591
592
# Create custom message
593
message = WebSocketMessage(
594
type="chat_message",
595
data={"user": "Alice", "text": "Hello everyone!"},
596
timestamp=time.time(),
597
headers=Headers({"Room": "general", "Priority": "normal"})
598
)
599
600
print(f"Message: {message}")
601
print(f"Message type: {message.type}")
602
print(f"Message headers: {dict(message.headers.items())}")
603
604
# Create custom frame wrapper
605
frame = Frame(Opcode.TEXT, json.dumps(message.data).encode())
606
wrapped_frame = MessageFrame(frame, message_id="msg_001")
607
608
print(f"\nWrapped frame: {wrapped_frame.to_json()}")
609
print(f"Is text frame: {wrapped_frame.is_text}")
610
print(f"Is binary frame: {wrapped_frame.is_binary}")
611
612
custom_structures_example()
613
```
614
615
### WebSocket Protocol Constants
616
617
```python
618
from websockets.datastructures import Opcode, CloseCode, DATA_OPCODES, CTRL_OPCODES
619
620
def protocol_constants_example():
621
"""Demonstrate protocol constants usage."""
622
623
print("=== WebSocket Protocol Constants ===")
624
625
# Opcode information
626
print("Data Opcodes:")
627
for opcode in DATA_OPCODES:
628
print(f" {opcode.name} ({opcode.value})")
629
630
print("\nControl Opcodes:")
631
for opcode in CTRL_OPCODES:
632
print(f" {opcode.name} ({opcode.value})")
633
634
# Frame type checking
635
def classify_frame(opcode: Opcode) -> str:
636
if opcode in DATA_OPCODES:
637
return "data"
638
elif opcode in CTRL_OPCODES:
639
return "control"
640
else:
641
return "unknown"
642
643
test_opcodes = [Opcode.TEXT, Opcode.BINARY, Opcode.CLOSE, Opcode.PING]
644
645
print(f"\nFrame Classification:")
646
for opcode in test_opcodes:
647
classification = classify_frame(opcode)
648
print(f" {opcode.name}: {classification}")
649
650
# Close code categories
651
def categorize_close_code(code: int) -> str:
652
if 1000 <= code <= 1003:
653
return "normal"
654
elif 1004 <= code <= 1006:
655
return "reserved"
656
elif 1007 <= code <= 1011:
657
return "error"
658
elif 1012 <= code <= 1014:
659
return "service"
660
elif code == 1015:
661
return "tls"
662
elif 3000 <= code <= 3999:
663
return "library"
664
elif 4000 <= code <= 4999:
665
return "application"
666
else:
667
return "invalid"
668
669
test_codes = [1000, 1002, 1006, 1011, 3000, 4000, 9999]
670
671
print(f"\nClose Code Categories:")
672
for code in test_codes:
673
category = categorize_close_code(code)
674
description = CLOSE_CODES.get(code, "Unknown")
675
print(f" {code} ({description}): {category}")
676
677
protocol_constants_example()
678
```