{"id":737,"library":"h2","title":"h2: HTTP/2 Protocol State Machine","description":"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`.","status":"active","version":"5.0.11","language":"python","source_language":"en","source_url":"https://github.com/jawah/h2","tags":["http/2","networking","protocol","state-machine","low-level"],"install":[{"cmd":"pip install h2","lang":"bash"}],"dependencies":[],"imports":[{"symbol":"H2Connection","correct":"from h2.connection import H2Connection"},{"symbol":"events","correct":"from h2 import events"}],"quickstart":{"code":"import h2.connection\nimport h2.events\n\n# 1. Initialize the H2Connection object for a client-side connection.\nconn = h2.connection.H2Connection(client_side=True)\nprint(\"Connection initialized.\")\n\n# 2. Initiate the HTTP/2 connection by sending the preamble (SETTINGS frame).\nconn.initiate_connection()\npreamble_bytes = conn.data_to_send()\nprint(f\"Preamble bytes to send: {preamble_bytes!r}\")\n# In a real application, you would send 'preamble_bytes' over the network (e.g., after TLS handshake).\n\n# 3. Start a new stream and send headers for a GET request.\nstream_id = conn.get_next_available_stream_id()\nheaders = [\n    (':method', 'GET'),\n    (':authority', 'example.com'),\n    (':scheme', 'https'),\n    (':path', '/'),\n    ('user-agent', 'h2-example/1.0'),\n]\nconn.send_headers(stream_id, headers, end_stream=True)\nrequest_bytes = conn.data_to_send()\nprint(f\"Request headers bytes for stream {stream_id} to send: {request_bytes!r}\")\n# In a real application, you would send 'request_bytes' over the network.\n\nprint(\"\\n--- Event Processing Explanation ---\")\nprint(\"After sending data, your application would continuously:\")\nprint(\"1. Read bytes from the network (e.g., from a socket or asynchronous transport).\")\nprint(\"2. Call `conn.receive_data(received_bytes)` to process them.\")\nprint(\"3. Iterate through the `events` (e.g., `h2.events.ResponseReceived`, `h2.events.DataReceived`) returned by `receive_data`.\")\nprint(\"4. Handle each event type to manage connection and stream state.\")\nprint(\"5. For `DataReceived`, call `conn.acknowledge_received_data(...)` for flow control.\")\nprint(\"6. Call `conn.data_to_send()` regularly to get bytes to write back to the network (e.g., `WINDOW_UPDATE`, `ACK` frames).\")\nprint(\"This example demonstrates the core API calls for a client-side connection and sending an initial request.\")","lang":"python","description":"Initializes an HTTP/2 client connection, sends the connection preamble, and then sends a GET request with headers. It then explains the event-driven nature of `h2` for receiving and processing data, which requires explicit integration with a network I/O layer."},"warnings":[{"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.","message":"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.","severity":"breaking","affected_versions":"<3.0 to >=3.0"},{"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.","message":"`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.","severity":"gotcha","affected_versions":"All versions"},{"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.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"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.","message":"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.","severity":"gotcha","affected_versions":">=3.0"}],"env_vars":null,"last_verified":"2026-05-12T18:27:29.533Z","next_check":"2026-07-10T00:00:00.000Z","problems":[{"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:\n\n```python\nimport h2.connection\n\nconn = h2.connection.H2Connection()\nstream_id = conn.get_next_available_stream_id()\n\n# ... (send headers, etc.)\n\ndata_to_send = b'some data'\nmax_send = conn.local_flow_control_window(stream_id)\nif len(data_to_send) > max_send:\n    # Split data or wait for a WINDOW_UPDATE event\n    print(f'Cannot send all data. Available window: {max_send}')\nelse:\n    conn.send_data(stream_id, data_to_send)\n```","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).","error":"h2.exceptions.ProtocolError: An action was attempted in violation of the HTTP/2 protocol."},{"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.","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.","error":"h2.exceptions.StreamClosedError: A stream-specific action referenced a stream that does not exist."},{"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.","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.","error":"AttributeError: module 'h2.settings' has no attribute 'ENABLE_PUSH'"},{"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.\n\n```python\nimport h2.connection\n\nconn = h2.connection.H2Connection()\n# ... process settings from remote peer ...\n\n# Example: if remote_max_concurrent_streams is known from SETTINGS_MAX_CONCURRENT_STREAMS\n# if conn.open_outbound_streams < remote_max_concurrent_streams:\n#     stream_id = conn.get_next_available_stream_id()\n#     conn.send_headers(stream_id, headers, end_stream=False)\n# else:\n#     print('Too many concurrent streams, wait for one to close.')\n```","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.","error":"h2.exceptions.TooManyStreamsError"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":"4.3.0","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":1.1,"mem_mb":3.1,"disk_size":"19.0M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.98,"mem_mb":3.1,"disk_size":"19.0M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.8,"import_time_s":1.51,"mem_mb":3.1,"disk_size":"20M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":1.81,"mem_mb":3.1,"disk_size":"20M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.56,"mem_mb":3.6,"disk_size":"20.7M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.6,"mem_mb":3.6,"disk_size":"20.7M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.8,"import_time_s":0.49,"mem_mb":3.6,"disk_size":"21M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.49,"mem_mb":3.6,"disk_size":"21M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.43,"mem_mb":3.5,"disk_size":"12.6M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.49,"mem_mb":3.5,"disk_size":"12.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.9,"import_time_s":0.47,"mem_mb":3.5,"disk_size":"13M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.52,"mem_mb":3.5,"disk_size":"13M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.4,"mem_mb":3.8,"disk_size":"12.3M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.74,"mem_mb":3.8,"disk_size":"12.2M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.46,"mem_mb":3.6,"disk_size":"13M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.54,"mem_mb":3.6,"disk_size":"13M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":3,"disk_size":"18.2M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.08,"mem_mb":3,"disk_size":"18.2M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2,"import_time_s":0.07,"mem_mb":3,"disk_size":"19M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":3,"disk_size":"19M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}