0
# Advanced Features
1
2
Support for PUSH_PROMISE, CONTINUATION, ALTSVC frames and extension frames for handling unknown frame types, plus comprehensive flag management and error handling. These features provide complete HTTP/2 implementation support and extensibility.
3
4
## Capabilities
5
6
### PUSH_PROMISE Frames
7
8
PUSH_PROMISE frames notify peers of streams the sender intends to initiate, enabling server push functionality.
9
10
```python { .api }
11
class PushPromiseFrame(Padding, Frame):
12
def __init__(self, stream_id: int, promised_stream_id: int = 0,
13
data: bytes = b"", **kwargs) -> None:
14
"""
15
Create a PUSH_PROMISE frame.
16
17
Parameters:
18
- stream_id (int): Stream identifier (must be non-zero)
19
- promised_stream_id (int): Stream ID being promised (must be even and non-zero)
20
- data (bytes): HPACK-encoded header block for promised request
21
- pad_length (int): Padding length if PADDED flag set
22
- flags (Iterable[str]): Frame flags ("END_HEADERS", "PADDED")
23
- **kwargs: Additional arguments for parent classes
24
25
Raises:
26
- InvalidDataError: If stream_id is zero or promised_stream_id is invalid
27
"""
28
29
@property
30
def promised_stream_id(self) -> int:
31
"""Stream ID that will be used for the promised stream."""
32
33
@property
34
def data(self) -> bytes:
35
"""HPACK-encoded header block for the promised request."""
36
37
# Inherited from Frame
38
def serialize_body(self) -> bytes: ...
39
def parse_body(self, data: memoryview) -> None: ...
40
```
41
42
**Defined Flags:**
43
- `END_HEADERS` (0x04): Indicates end of header list
44
- `PADDED` (0x08): Indicates frame contains padding
45
46
### ALTSVC Frames
47
48
ALTSVC frames advertise alternate services that can handle requests, as defined in RFC 7838.
49
50
```python { .api }
51
class AltSvcFrame(Frame):
52
def __init__(self, stream_id: int, origin: bytes = b"", field: bytes = b"", **kwargs) -> None:
53
"""
54
Create an ALTSVC frame.
55
56
Parameters:
57
- stream_id (int): Stream identifier (0 for connection, non-zero for stream)
58
- origin (bytes): Origin for alternate service (empty if stream_id != 0)
59
- field (bytes): Alt-Svc field value
60
- **kwargs: Additional arguments for parent classes
61
62
Note: Either stream_id must be 0 XOR origin must be empty (not both)
63
64
Raises:
65
- InvalidDataError: If origin or field are not bytes
66
"""
67
68
@property
69
def origin(self) -> bytes:
70
"""Origin that the alternate service applies to."""
71
72
@property
73
def field(self) -> bytes:
74
"""Alt-Svc field value describing the alternate service."""
75
76
# Inherited from Frame
77
def serialize_body(self) -> bytes: ...
78
def parse_body(self, data: memoryview) -> None: ...
79
```
80
81
**Defined Flags:** None
82
83
### Extension Frames
84
85
Extension frames wrap unknown frame types to enable processing of non-standard frames.
86
87
```python { .api }
88
class ExtensionFrame(Frame):
89
def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0,
90
body: bytes = b"", **kwargs) -> None:
91
"""
92
Create an extension frame for unknown frame types.
93
94
Parameters:
95
- type (int): Frame type identifier
96
- stream_id (int): Stream identifier
97
- flag_byte (int): Raw flag byte value
98
- body (bytes): Frame body data
99
- **kwargs: Additional arguments for parent classes
100
"""
101
102
@property
103
def type(self) -> int:
104
"""Frame type identifier."""
105
106
@property
107
def flag_byte(self) -> int:
108
"""Raw flag byte from frame header."""
109
110
@property
111
def body(self) -> bytes:
112
"""Raw frame body data."""
113
114
def parse_flags(self, flag_byte: int) -> None:
115
"""
116
Store raw flag byte instead of parsing individual flags.
117
118
Parameters:
119
- flag_byte (int): 8-bit flag value from frame header
120
"""
121
122
def serialize(self) -> bytes:
123
"""
124
Serialize extension frame exactly as received.
125
126
Returns:
127
bytes: Complete frame with original type and flag byte
128
"""
129
130
# Inherited from Frame
131
def parse_body(self, data: memoryview) -> None: ...
132
```
133
134
### Flag Management
135
136
Type-safe flag management system ensuring only valid flags are set per frame type.
137
138
```python { .api }
139
class Flag:
140
def __init__(self, name: str, bit: int) -> None:
141
"""
142
Create a flag definition.
143
144
Parameters:
145
- name (str): Flag name (e.g., "END_STREAM")
146
- bit (int): Bit value for the flag (e.g., 0x01)
147
"""
148
149
@property
150
def name(self) -> str:
151
"""Flag name."""
152
153
@property
154
def bit(self) -> int:
155
"""Flag bit value."""
156
157
class Flags:
158
def __init__(self, defined_flags: Iterable[Flag]) -> None:
159
"""
160
Create a flags container for a specific frame type.
161
162
Parameters:
163
- defined_flags (Iterable[Flag]): Valid flags for this frame type
164
"""
165
166
def add(self, value: str) -> None:
167
"""
168
Add a flag to the set.
169
170
Parameters:
171
- value (str): Flag name to add
172
173
Raises:
174
- ValueError: If flag is not defined for this frame type
175
"""
176
177
def discard(self, value: str) -> None:
178
"""
179
Remove a flag from the set if present.
180
181
Parameters:
182
- value (str): Flag name to remove
183
"""
184
185
def __contains__(self, flag: str) -> bool:
186
"""
187
Check if flag is set.
188
189
Parameters:
190
- flag (str): Flag name to check
191
192
Returns:
193
bool: True if flag is set
194
"""
195
196
def __iter__(self) -> Iterator[str]:
197
"""
198
Iterate over set flags.
199
200
Returns:
201
Iterator[str]: Iterator over flag names
202
"""
203
204
def __len__(self) -> int:
205
"""
206
Get number of set flags.
207
208
Returns:
209
int: Number of flags currently set
210
"""
211
```
212
213
## Usage Examples
214
215
### PUSH_PROMISE Operations
216
217
```python
218
from hyperframe.frame import PushPromiseFrame
219
220
# Server promising to push a resource
221
push_promise = PushPromiseFrame(
222
stream_id=1, # Original request stream
223
promised_stream_id=2, # Stream for pushed response
224
data=b"\\x00\\x05:path\\x0A/style.css", # HPACK headers for promised request
225
flags=["END_HEADERS"]
226
)
227
228
print(f"Promising stream {push_promise.promised_stream_id} from stream {push_promise.stream_id}")
229
230
# PUSH_PROMISE with padding
231
padded_promise = PushPromiseFrame(
232
stream_id=1,
233
promised_stream_id=4,
234
data=b"\\x00\\x05:path\\x0B/script.js",
235
pad_length=8,
236
flags=["END_HEADERS", "PADDED"]
237
)
238
```
239
240
### ALTSVC Frame Usage
241
242
```python
243
from hyperframe.frame import AltSvcFrame
244
245
# Connection-level alternate service
246
connection_altsvc = AltSvcFrame(
247
stream_id=0, # Connection level
248
origin=b"example.com",
249
field=b'h2="alt.example.com:443"; ma=3600'
250
)
251
252
# Stream-specific alternate service
253
stream_altsvc = AltSvcFrame(
254
stream_id=1, # Stream specific
255
origin=b"", # Empty for stream-specific
256
field=b'h2="alt2.example.com:443"'
257
)
258
259
print(f"Connection Alt-Svc: {connection_altsvc.field}")
260
print(f"Stream Alt-Svc: {stream_altsvc.field}")
261
```
262
263
### Extension Frame Handling
264
265
```python
266
from hyperframe.frame import Frame, ExtensionFrame
267
268
# Parse unknown frame type
269
unknown_frame_data = b'\\x00\\x00\\x05\\xEE\\x0F\\x00\\x00\\x00\\x01Hello' # Type 0xEE
270
frame, length = Frame.parse_frame_header(unknown_frame_data[:9], strict=False)
271
272
if isinstance(frame, ExtensionFrame):
273
frame.parse_body(memoryview(unknown_frame_data[9:9 + length]))
274
print(f"Unknown frame type: 0x{frame.type:02X}")
275
print(f"Flag byte: 0x{frame.flag_byte:02X}")
276
print(f"Body: {frame.body}")
277
278
# Round-trip serialization
279
serialized = frame.serialize()
280
assert serialized == unknown_frame_data
281
282
# Create extension frame manually
283
custom_frame = ExtensionFrame(
284
type=0xCC,
285
stream_id=3,
286
flag_byte=0x05,
287
body=b"Custom frame data"
288
)
289
```
290
291
### Flag Management
292
293
```python
294
from hyperframe.flags import Flag, Flags
295
from hyperframe.frame import DataFrame
296
297
# Working with frame flags
298
data_frame = DataFrame(stream_id=1, data=b"Test data")
299
300
# Add flags
301
data_frame.flags.add("END_STREAM")
302
print(f"END_STREAM set: {'END_STREAM' in data_frame.flags}")
303
304
# Try to add invalid flag
305
try:
306
data_frame.flags.add("INVALID_FLAG")
307
except ValueError as e:
308
print(f"Flag error: {e}")
309
310
# Check all set flags
311
print(f"Set flags: {list(data_frame.flags)}")
312
313
# Remove flag
314
data_frame.flags.discard("END_STREAM")
315
print(f"Flags after discard: {list(data_frame.flags)}")
316
317
# Create custom flag set
318
custom_flags = [
319
Flag("CUSTOM_FLAG_1", 0x01),
320
Flag("CUSTOM_FLAG_2", 0x02)
321
]
322
flag_set = Flags(custom_flags)
323
flag_set.add("CUSTOM_FLAG_1")
324
print(f"Custom flags: {list(flag_set)}")
325
```
326
327
### Complex Frame Scenarios
328
329
```python
330
from hyperframe.frame import HeadersFrame, ContinuationFrame, PushPromiseFrame
331
332
# Server push scenario with large headers
333
large_headers = b"\\x00\\x07:method\\x03GET" + b"\\x00" * 2000
334
335
# Initial PUSH_PROMISE without END_HEADERS
336
push_start = PushPromiseFrame(
337
stream_id=1,
338
promised_stream_id=2,
339
data=large_headers[:1000]
340
# No END_HEADERS flag
341
)
342
343
# CONTINUATION to complete the promise
344
push_continuation = ContinuationFrame(
345
stream_id=1,
346
data=large_headers[1000:],
347
flags=["END_HEADERS"]
348
)
349
350
print(f"Push promise starts: {len(push_start.data)} bytes")
351
print(f"Continuation completes: {len(push_continuation.data)} bytes")
352
353
# Later, server sends response on promised stream
354
response_headers = HeadersFrame(
355
stream_id=2, # Promised stream
356
data=b"\\x00\\x07:status\\x03200",
357
flags=["END_HEADERS"]
358
)
359
```
360
361
### Error Handling and Validation
362
363
```python
364
from hyperframe.frame import PushPromiseFrame, AltSvcFrame
365
from hyperframe.exceptions import InvalidDataError, InvalidFrameError
366
367
# Invalid PUSH_PROMISE stream ID
368
try:
369
PushPromiseFrame(stream_id=1, promised_stream_id=1) # Must be even
370
except InvalidDataError as e:
371
print(f"Push promise error: {e}")
372
373
# Invalid ALTSVC parameters
374
try:
375
AltSvcFrame(stream_id=1, origin="not bytes", field=b"valid") # Origin must be bytes
376
except InvalidDataError as e:
377
print(f"AltSvc error: {e}")
378
379
# Parse extension frame with malformed data
380
try:
381
bad_data = b'\\x00\\x00\\x05\\xFF\\x00\\x00\\x00\\x00\\x01ABC' # Only 3 bytes, claims 5
382
frame, length = Frame.parse_frame_header(bad_data[:9])
383
frame.parse_body(memoryview(bad_data[9:])) # Will be truncated
384
print(f"Extension frame body: {frame.body}") # Only gets available data
385
except Exception as e:
386
print(f"Parse error: {e}")
387
```