{"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,"search_vec":"'5.0.11':37 'allow':23 'base':17 'cadenc':43 'connect':31 'current':34 'depend':54 'driven':45 'h2':1,6 'http/2':2,21,30,57 'implement':18 'improv':48 'interact':28 'level':27,65 'librari':10 'low':26,64 'low-level':25,63 'machin':5,16,62 'maintain':39 'network':58 'often':44 'perform':47 'protocol':3,22,59 'provid':12 'pyo3':56 'python':9 'regular':41 'releas':42 'rust':53 'state':4,15,61 'state-machin':14,60 'under':52 'updat':50 'version':36 'via':55","created_at":"2026-03-28T17:30:19.806050+00:00","updated_at":"2026-04-16T15:31:26.485719+00:00","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,"quickstart_score":0,"quickstart_tag":"stale","pypi_latest":"4.3.0","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/python-hyper/h2","docs":"https://python-hyper.org/","changelog":null,"pypi":"https://pypi.org/project/h2/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["http-networking"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"passing","verified_at":"2026-06-28","last_verified":"2026-06-28","next_check":"2026-07-28","install_tag":"verified"}}