h2: HTTP/2 Protocol State Machine
raw JSON → 5.0.11 verified Tue May 12 auth: no python install: verified quickstart: stale
h2 is a Python library that provides a state-machine based implementation of the HTTP/2 protocol, allowing for low-level interaction with HTTP/2 connections. It is currently at version 5.0.11 and maintains a regular release cadence, often driven by performance improvements and updates to underlying Rust dependencies via `pyo3`.
pip install h2 Common errors
error h2.exceptions.ProtocolError: An action was attempted in violation of the HTTP/2 protocol. ↓
cause This general error often indicates a violation of HTTP/2 protocol rules, such as sending more data than allowed by flow control windows, or sending malformed HTTP/2 header blocks (e.g., missing mandatory pseudo-headers like ':method', ':scheme', ':path' for requests, or sending connection-specific headers).
fix
For flow control issues, ensure you check the available window using
conn.local_flow_control_window(stream_id) before sending data and segment large data into smaller frames. For header issues, verify that your header lists adhere to RFC 7540 specifications, including correct pseudo-headers and avoiding forbidden connection-specific headers (like 'connection', 'keep-alive', 'transfer-encoding'). Example for checking flow control:
import h2.connection
conn = h2.connection.H2Connection()
stream_id = conn.get_next_available_stream_id()
# ... (send headers, etc.)
data_to_send = b'some data'
max_send = conn.local_flow_control_window(stream_id)
if len(data_to_send) > max_send:
# Split data or wait for a WINDOW_UPDATE event
print(f'Cannot send all data. Available window: {max_send}')
else:
conn.send_data(stream_id, data_to_send) error h2.exceptions.StreamClosedError: A stream-specific action referenced a stream that does not exist. ↓
cause This error occurs when you attempt an operation (like sending data, headers, or a reset) on an HTTP/2 stream that the `h2` state machine considers already closed. This often happens due to timing mismatches with the remote peer's state or if `h2` has already cleaned up the stream's state, for instance, after receiving an `END_STREAM` flag or a `RST_STREAM` frame.
fix
Implement robust state management for your HTTP/2 streams. After receiving
StreamEnded or StreamReset events, avoid further operations on that specific stream_id. If interacting with a remote peer that sends frames (like RST_STREAM or WINDOW_UPDATE) on a stream that your h2 connection has already marked as closed, consider gracefully ignoring these events in your event loop after the stream has transitioned to a terminal state. error AttributeError: module 'h2.settings' has no attribute 'ENABLE_PUSH' ↓
cause This `AttributeError` typically arises when using code written for an older version of the `h2` library (or a library that depends on an older `h2` API) with a newer `h2` installation (e.g., `h2` version 3.0.0 or higher). In newer versions, the HTTP/2 setting constants, such as `ENABLE_PUSH`, were moved from direct attributes of the `h2.settings` module into the `h2.settings.SettingCodes` enumeration.
fix
Update your code to reference the setting constants through
h2.settings.SettingCodes. For example, change h2.settings.ENABLE_PUSH to h2.settings.SettingCodes.ENABLE_PUSH. If you are using an external library that has this issue, you might need to upgrade that library to a version compatible with your h2 installation, or temporarily pin your h2 version to an older release (e.g., pip install 'h2<3.0.0') if compatible updates are not available. error h2.exceptions.TooManyStreamsError ↓
cause This error is raised when an attempt is made to open a new HTTP/2 stream, but doing so would exceed the maximum number of concurrent streams allowed by the remote peer (as defined by its `SETTINGS_MAX_CONCURRENT_STREAMS` setting) or by your local configuration.
fix
Ensure that your application respects the
SETTINGS_MAX_CONCURRENT_STREAMS advertised by the remote peer. If you are acting as a client, you should not initiate more streams than the remote server allows. If you are acting as a server, you can configure your own SETTINGS_MAX_CONCURRENT_STREAMS and ensure client requests don't exceed it. When a stream is closed, its slot for concurrent streams becomes available again. Wait for streams to close before opening new ones if you are near the limit.
import h2.connection
conn = h2.connection.H2Connection()
# ... process settings from remote peer ...
# Example: if remote_max_concurrent_streams is known from SETTINGS_MAX_CONCURRENT_STREAMS
# if conn.open_outbound_streams < remote_max_concurrent_streams:
# stream_id = conn.get_next_available_stream_id()
# conn.send_headers(stream_id, headers, end_stream=False)
# else:
# print('Too many concurrent streams, wait for one to close.') Warnings
breaking A significant API rewrite occurred in v3.0 (released March 2017). Users migrating from `h2 < 3.0` will encounter breaking changes, including `dict` objects no longer being accepted for headers (requiring sequences of 2-tuples) and various method renamings or alterations. ↓
fix Refer to the `h2` documentation and changelog for v3.0 migration. Update header arguments to a list of `(name, value)` byte-string tuples. Review API calls for changed method signatures.
gotcha `h2` is a pure HTTP/2 state machine and does NOT handle any network I/O (sockets, TLS, buffering). Users must integrate `h2.connection.H2Connection` with their chosen I/O layer (e.g., `socket`, `asyncio`, `trio`) to send and receive raw bytes. ↓
fix Implement explicit I/O loops that read data from the network, pass it to `H2Connection.receive_data()`, get output bytes from `H2Connection.data_to_send()`, and write them back to the network.
gotcha The library is event-driven. Your application MUST diligently process ALL events returned by `H2Connection.receive_data()`. Crucially, you must call `H2Connection.acknowledge_received_data()` for received data frames and then `H2Connection.data_to_send()` to send `WINDOW_UPDATE` frames, otherwise, flow control will stall the connection. ↓
fix Implement a comprehensive event handling loop that iterates through all `h2.events.Event` objects. Ensure `acknowledge_received_data` is called for every `DataReceived` event and `data_to_send` is regularly invoked to push out flow control updates.
gotcha Headers provided to methods like `H2Connection.send_headers()` must be a list of 2-tuple byte-strings `[(b':method', b'GET'), (b'host', b'example.com')]`. Dictionaries are not allowed, as HTTP/2 requires pseudo-headers to appear first and maintains header field order. ↓
fix Always construct headers as `list[tuple[bytes, bytes]]` (or automatically encoded tuples if `header_encoding` is set) to maintain order and adhere to HTTP/2 specifications.
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 1.10s 19.0M
3.10 alpine (musl) - - 0.98s 19.0M
3.10 slim (glibc) wheel 1.8s 1.51s 20M
3.10 slim (glibc) - - 1.81s 20M
3.11 alpine (musl) wheel - 0.56s 20.7M
3.11 alpine (musl) - - 0.60s 20.7M
3.11 slim (glibc) wheel 1.8s 0.49s 21M
3.11 slim (glibc) - - 0.49s 21M
3.12 alpine (musl) wheel - 0.43s 12.6M
3.12 alpine (musl) - - 0.49s 12.6M
3.12 slim (glibc) wheel 1.9s 0.47s 13M
3.12 slim (glibc) - - 0.52s 13M
3.13 alpine (musl) wheel - 0.40s 12.3M
3.13 alpine (musl) - - 0.74s 12.2M
3.13 slim (glibc) wheel 1.7s 0.46s 13M
3.13 slim (glibc) - - 0.54s 13M
3.9 alpine (musl) wheel - 0.07s 18.2M
3.9 alpine (musl) - - 0.08s 18.2M
3.9 slim (glibc) wheel 2.0s 0.07s 19M
3.9 slim (glibc) - - 0.06s 19M
Imports
- H2Connection
from h2.connection import H2Connection - events
from h2 import events
Quickstart stale last tested: 2026-04-24
import h2.connection
import h2.events
# 1. Initialize the H2Connection object for a client-side connection.
conn = h2.connection.H2Connection(client_side=True)
print("Connection initialized.")
# 2. Initiate the HTTP/2 connection by sending the preamble (SETTINGS frame).
conn.initiate_connection()
preamble_bytes = conn.data_to_send()
print(f"Preamble bytes to send: {preamble_bytes!r}")
# In a real application, you would send 'preamble_bytes' over the network (e.g., after TLS handshake).
# 3. Start a new stream and send headers for a GET request.
stream_id = conn.get_next_available_stream_id()
headers = [
(':method', 'GET'),
(':authority', 'example.com'),
(':scheme', 'https'),
(':path', '/'),
('user-agent', 'h2-example/1.0'),
]
conn.send_headers(stream_id, headers, end_stream=True)
request_bytes = conn.data_to_send()
print(f"Request headers bytes for stream {stream_id} to send: {request_bytes!r}")
# In a real application, you would send 'request_bytes' over the network.
print("\n--- Event Processing Explanation ---")
print("After sending data, your application would continuously:")
print("1. Read bytes from the network (e.g., from a socket or asynchronous transport).")
print("2. Call `conn.receive_data(received_bytes)` to process them.")
print("3. Iterate through the `events` (e.g., `h2.events.ResponseReceived`, `h2.events.DataReceived`) returned by `receive_data`.")
print("4. Handle each event type to manage connection and stream state.")
print("5. For `DataReceived`, call `conn.acknowledge_received_data(...)` for flow control.")
print("6. Call `conn.data_to_send()` regularly to get bytes to write back to the network (e.g., `WINDOW_UPDATE`, `ACK` frames).")
print("This example demonstrates the core API calls for a client-side connection and sending an initial request.")