CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-hyperframe

Pure-Python HTTP/2 framing library providing comprehensive frame type support for creating, serializing, and parsing HTTP/2 frames.

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

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.

Capabilities

PUSH_PROMISE Frames

PUSH_PROMISE frames notify peers of streams the sender intends to initiate, enabling server push functionality.

class PushPromiseFrame(Padding, Frame):
    def __init__(self, stream_id: int, promised_stream_id: int = 0, 
                 data: bytes = b"", **kwargs) -> None:
        """
        Create a PUSH_PROMISE frame.
        
        Parameters:
        - stream_id (int): Stream identifier (must be non-zero)
        - promised_stream_id (int): Stream ID being promised (must be even and non-zero)
        - data (bytes): HPACK-encoded header block for promised request
        - pad_length (int): Padding length if PADDED flag set
        - flags (Iterable[str]): Frame flags ("END_HEADERS", "PADDED")
        - **kwargs: Additional arguments for parent classes
        
        Raises:
        - InvalidDataError: If stream_id is zero or promised_stream_id is invalid
        """
    
    @property
    def promised_stream_id(self) -> int:
        """Stream ID that will be used for the promised stream."""
    
    @property
    def data(self) -> bytes:
        """HPACK-encoded header block for the promised request."""
    
    # Inherited from Frame
    def serialize_body(self) -> bytes: ...
    def parse_body(self, data: memoryview) -> None: ...

Defined Flags:

  • END_HEADERS (0x04): Indicates end of header list
  • PADDED (0x08): Indicates frame contains padding

ALTSVC Frames

ALTSVC frames advertise alternate services that can handle requests, as defined in RFC 7838.

class AltSvcFrame(Frame):
    def __init__(self, stream_id: int, origin: bytes = b"", field: bytes = b"", **kwargs) -> None:
        """
        Create an ALTSVC frame.
        
        Parameters:
        - stream_id (int): Stream identifier (0 for connection, non-zero for stream)
        - origin (bytes): Origin for alternate service (empty if stream_id != 0)
        - field (bytes): Alt-Svc field value
        - **kwargs: Additional arguments for parent classes
        
        Note: Either stream_id must be 0 XOR origin must be empty (not both)
        
        Raises:
        - InvalidDataError: If origin or field are not bytes
        """
    
    @property
    def origin(self) -> bytes:
        """Origin that the alternate service applies to."""
    
    @property
    def field(self) -> bytes:
        """Alt-Svc field value describing the alternate service."""
    
    # Inherited from Frame
    def serialize_body(self) -> bytes: ...
    def parse_body(self, data: memoryview) -> None: ...

Defined Flags: None

Extension Frames

Extension frames wrap unknown frame types to enable processing of non-standard frames.

class ExtensionFrame(Frame):
    def __init__(self, type: int, stream_id: int, flag_byte: int = 0x0, 
                 body: bytes = b"", **kwargs) -> None:
        """
        Create an extension frame for unknown frame types.
        
        Parameters:
        - type (int): Frame type identifier
        - stream_id (int): Stream identifier
        - flag_byte (int): Raw flag byte value
        - body (bytes): Frame body data
        - **kwargs: Additional arguments for parent classes
        """
    
    @property
    def type(self) -> int:
        """Frame type identifier."""
    
    @property
    def flag_byte(self) -> int:
        """Raw flag byte from frame header."""
    
    @property
    def body(self) -> bytes:
        """Raw frame body data."""
    
    def parse_flags(self, flag_byte: int) -> None:
        """
        Store raw flag byte instead of parsing individual flags.
        
        Parameters:
        - flag_byte (int): 8-bit flag value from frame header
        """
    
    def serialize(self) -> bytes:
        """
        Serialize extension frame exactly as received.
        
        Returns:
        bytes: Complete frame with original type and flag byte
        """
    
    # Inherited from Frame
    def parse_body(self, data: memoryview) -> None: ...

Flag Management

Type-safe flag management system ensuring only valid flags are set per frame type.

class Flag:
    def __init__(self, name: str, bit: int) -> None:
        """
        Create a flag definition.
        
        Parameters:
        - name (str): Flag name (e.g., "END_STREAM")
        - bit (int): Bit value for the flag (e.g., 0x01)
        """
    
    @property
    def name(self) -> str:
        """Flag name."""
    
    @property
    def bit(self) -> int:
        """Flag bit value."""

class Flags:
    def __init__(self, defined_flags: Iterable[Flag]) -> None:
        """
        Create a flags container for a specific frame type.
        
        Parameters:
        - defined_flags (Iterable[Flag]): Valid flags for this frame type
        """
    
    def add(self, value: str) -> None:
        """
        Add a flag to the set.
        
        Parameters:
        - value (str): Flag name to add
        
        Raises:
        - ValueError: If flag is not defined for this frame type
        """
    
    def discard(self, value: str) -> None:
        """
        Remove a flag from the set if present.
        
        Parameters:
        - value (str): Flag name to remove
        """
    
    def __contains__(self, flag: str) -> bool:
        """
        Check if flag is set.
        
        Parameters:
        - flag (str): Flag name to check
        
        Returns:
        bool: True if flag is set
        """
    
    def __iter__(self) -> Iterator[str]:
        """
        Iterate over set flags.
        
        Returns:
        Iterator[str]: Iterator over flag names
        """
    
    def __len__(self) -> int:
        """
        Get number of set flags.
        
        Returns:
        int: Number of flags currently set
        """

Usage Examples

PUSH_PROMISE Operations

from hyperframe.frame import PushPromiseFrame

# Server promising to push a resource
push_promise = PushPromiseFrame(
    stream_id=1,              # Original request stream
    promised_stream_id=2,     # Stream for pushed response
    data=b"\\x00\\x05:path\\x0A/style.css",  # HPACK headers for promised request
    flags=["END_HEADERS"]
)

print(f"Promising stream {push_promise.promised_stream_id} from stream {push_promise.stream_id}")

# PUSH_PROMISE with padding
padded_promise = PushPromiseFrame(
    stream_id=1,
    promised_stream_id=4,
    data=b"\\x00\\x05:path\\x0B/script.js",
    pad_length=8,
    flags=["END_HEADERS", "PADDED"]
)

ALTSVC Frame Usage

from hyperframe.frame import AltSvcFrame

# Connection-level alternate service
connection_altsvc = AltSvcFrame(
    stream_id=0,  # Connection level
    origin=b"example.com",
    field=b'h2="alt.example.com:443"; ma=3600'
)

# Stream-specific alternate service
stream_altsvc = AltSvcFrame(
    stream_id=1,  # Stream specific
    origin=b"",   # Empty for stream-specific
    field=b'h2="alt2.example.com:443"'
)

print(f"Connection Alt-Svc: {connection_altsvc.field}")
print(f"Stream Alt-Svc: {stream_altsvc.field}")

Extension Frame Handling

from hyperframe.frame import Frame, ExtensionFrame

# Parse unknown frame type
unknown_frame_data = b'\\x00\\x00\\x05\\xEE\\x0F\\x00\\x00\\x00\\x01Hello'  # Type 0xEE
frame, length = Frame.parse_frame_header(unknown_frame_data[:9], strict=False)

if isinstance(frame, ExtensionFrame):
    frame.parse_body(memoryview(unknown_frame_data[9:9 + length]))
    print(f"Unknown frame type: 0x{frame.type:02X}")
    print(f"Flag byte: 0x{frame.flag_byte:02X}")
    print(f"Body: {frame.body}")
    
    # Round-trip serialization
    serialized = frame.serialize()
    assert serialized == unknown_frame_data

# Create extension frame manually
custom_frame = ExtensionFrame(
    type=0xCC,
    stream_id=3,
    flag_byte=0x05,
    body=b"Custom frame data"
)

Flag Management

from hyperframe.flags import Flag, Flags
from hyperframe.frame import DataFrame

# Working with frame flags
data_frame = DataFrame(stream_id=1, data=b"Test data")

# Add flags
data_frame.flags.add("END_STREAM")
print(f"END_STREAM set: {'END_STREAM' in data_frame.flags}")

# Try to add invalid flag
try:
    data_frame.flags.add("INVALID_FLAG")
except ValueError as e:
    print(f"Flag error: {e}")

# Check all set flags
print(f"Set flags: {list(data_frame.flags)}")

# Remove flag
data_frame.flags.discard("END_STREAM")
print(f"Flags after discard: {list(data_frame.flags)}")

# Create custom flag set
custom_flags = [
    Flag("CUSTOM_FLAG_1", 0x01),
    Flag("CUSTOM_FLAG_2", 0x02)
]
flag_set = Flags(custom_flags)
flag_set.add("CUSTOM_FLAG_1")
print(f"Custom flags: {list(flag_set)}")

Complex Frame Scenarios

from hyperframe.frame import HeadersFrame, ContinuationFrame, PushPromiseFrame

# Server push scenario with large headers
large_headers = b"\\x00\\x07:method\\x03GET" + b"\\x00" * 2000

# Initial PUSH_PROMISE without END_HEADERS
push_start = PushPromiseFrame(
    stream_id=1,
    promised_stream_id=2,
    data=large_headers[:1000]
    # No END_HEADERS flag
)

# CONTINUATION to complete the promise
push_continuation = ContinuationFrame(
    stream_id=1,
    data=large_headers[1000:],
    flags=["END_HEADERS"]
)

print(f"Push promise starts: {len(push_start.data)} bytes")
print(f"Continuation completes: {len(push_continuation.data)} bytes")

# Later, server sends response on promised stream
response_headers = HeadersFrame(
    stream_id=2,  # Promised stream
    data=b"\\x00\\x07:status\\x03200",
    flags=["END_HEADERS"]
)

Error Handling and Validation

from hyperframe.frame import PushPromiseFrame, AltSvcFrame
from hyperframe.exceptions import InvalidDataError, InvalidFrameError

# Invalid PUSH_PROMISE stream ID
try:
    PushPromiseFrame(stream_id=1, promised_stream_id=1)  # Must be even
except InvalidDataError as e:
    print(f"Push promise error: {e}")

# Invalid ALTSVC parameters
try:
    AltSvcFrame(stream_id=1, origin="not bytes", field=b"valid")  # Origin must be bytes
except InvalidDataError as e:
    print(f"AltSvc error: {e}")

# Parse extension frame with malformed data
try:
    bad_data = b'\\x00\\x00\\x05\\xFF\\x00\\x00\\x00\\x00\\x01ABC'  # Only 3 bytes, claims 5
    frame, length = Frame.parse_frame_header(bad_data[:9])
    frame.parse_body(memoryview(bad_data[9:]))  # Will be truncated
    print(f"Extension frame body: {frame.body}")  # Only gets available data
except Exception as e:
    print(f"Parse error: {e}")

Install with Tessl CLI

npx tessl i tessl/pypi-hyperframe

docs

advanced-features.md

control-frames.md

data-headers.md

frame-operations.md

index.md

tile.json