h11
h11 is a pure-Python, bring-your-own-I/O implementation of HTTP/1.1, heavily inspired by hyper-h2. It contains no networking code whatsoever — you supply the bytes in and out — making it usable with any I/O model: synchronous, threaded, asyncio, trio, or custom. It models the HTTP exchange as a strict state machine emitting typed event objects (Request, Response, Data, EndOfMessage, etc.) and enforces spec conformance on both incoming and outgoing messages. Current version is 0.16.0, released in 2025 as a security fix; releases are infrequent and focused on correctness. It has no dependencies outside the Python standard library.
Common errors
-
AttributeError: module 'h11' has no attribute 'Event'
cause This error typically arises when a dependent library (like `httpx` or `gradio`) expects a specific `Event` object or attribute from `h11` that is either not exposed directly, or due to version incompatibility where `h11` or its dependents are outdated.fixUpgrade `h11` to the latest version (`pip install --upgrade h11`) and also upgrade any libraries that depend on `h11` (e.g., `pip install --upgrade httpcore httpx`). If `Event` is being directly referenced, check the `h11` documentation for the correct event class (e.g., `h11.Request`, `h11.Response`) as a generic `h11.Event` is not a top-level class. -
ModuleNotFoundError: No module named 'h11._util'
cause This error occurs when a Python package, often one that indirectly depends on `h11` (like the OpenAI Python client), attempts to import an internal submodule `h11._util` that cannot be found. This usually indicates a broken `h11` installation, conflicting dependencies, or an incompatibility after an upgrade of `h11` or a dependent library.fixReinstall or upgrade `h11` (`pip install --upgrade h11`) and its direct dependencies (e.g., `pip install --upgrade httpcore httpx`). Using a virtual environment and ensuring all packages are installed fresh can help resolve dependency conflicts. -
h11._util.LocalProtocolError: Too much data for declared Content-Length
cause `h11` raises this `LocalProtocolError` when your application, acting as either a client or server, sends a message body that is larger than the `Content-Length` header it declared, violating the HTTP/1.1 protocol.fixEnsure that the `Content-Length` header in your HTTP request or response accurately reflects the actual byte length of the body being sent. If using chunked transfer encoding, do not set `Content-Length`. -
h11._util.RemoteProtocolError: illegal request line
cause `h11` raises this `RemoteProtocolError` when it receives an HTTP request or response line from a remote peer that does not conform to the HTTP/1.1 specification (e.g., malformed syntax, incorrect method/target, or unsupported HTTP version).fixThis error indicates a problem with the remote peer's HTTP message. If you control the peer, fix its HTTP implementation. If you are a server receiving this, it suggests a client is sending invalid HTTP. If you are a client, the server might be sending an invalid initial response. You may need to inspect the raw bytes received to diagnose the specific non-compliance.
Warnings
- breaking ProtocolError was split into LocalProtocolError and RemoteProtocolError in v0.10. Catching the old bare ProtocolError still works (it is now an abstract base class), but code that previously caught ProtocolError to distinguish client vs server errors must be updated.
- breaking Python 2 support was dropped in v0.12; Python 3.6 support dropped in v0.14. h11 now requires Python >= 3.8 (as of v0.16).
- breaking Outgoing header validation became strict in v0.10+: headers with illegal characters or leading/trailing whitespace in values now raise LocalProtocolError. Previously, whitespace was silently stripped.
- breaking v0.16.0 rejects previously-accepted malformed Transfer-Encoding: chunked bodies (CVE / GHSA-vqfr-h8mv-ghfj). Code or test suites that construct intentionally malformed chunked bodies will now get a RemoteProtocolError.
- gotcha Protocol errors are unrecoverable. Once LocalProtocolError or RemoteProtocolError is raised, the Connection state becomes ERROR and all subsequent send()/next_event() calls also raise. You cannot reset or reuse the connection.
- gotcha start_next_cycle() for keep-alive reuse raises LocalProtocolError('not in a reusable state') if called before both sides have reached DONE state. Forgetting to send or fully consume EndOfMessage is a common cause.
- gotcha Response bodies may be delivered across multiple Data events because h11 buffers as little as possible. Assuming a single Data event contains the full body will silently truncate data.
Install
-
pip install h11
Imports
- Connection
import h11; conn = h11.Connection(our_role=h11.CLIENT)
- Request / Response / Data / EndOfMessage / InformationalResponse / ConnectionClosed
import h11 # then use h11.Request, h11.Response, etc.
- LocalProtocolError / RemoteProtocolError / ProtocolError
from h11._util import LocalProtocolError
import h11 # catch h11.LocalProtocolError, h11.RemoteProtocolError
- NEED_DATA / PAUSED / CLIENT / SERVER
import h11 # use h11.NEED_DATA, h11.PAUSED, h11.CLIENT, h11.SERVER
Quickstart
import socket
import ssl
import h11
HOST = "httpbin.org"
PORT = 443
ctx = ssl.create_default_context()
sock = ctx.wrap_socket(
socket.create_connection((HOST, PORT)),
server_hostname=HOST,
)
conn = h11.Connection(our_role=h11.CLIENT)
# Send request
sock.sendall(conn.send(h11.Request(
method="GET",
target="/get",
headers=[("Host", HOST), ("Connection", "close")],
)))
sock.sendall(conn.send(h11.EndOfMessage()))
# Receive events
while True:
event = conn.next_event()
if event is h11.NEED_DATA:
data = sock.recv(4096)
conn.receive_data(data)
continue
if isinstance(event, h11.Response):
print("Status:", event.status_code)
elif isinstance(event, h11.Data):
print("Body chunk:", event.data[:80])
elif isinstance(event, (h11.EndOfMessage, h11.ConnectionClosed)):
break
sock.close()