h11
raw JSON → 0.16.0 verified Tue May 12 auth: no python install: verified quickstart: verified
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.
pip install h11 Common errors
error 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.
fix
Upgrade
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. error 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.
fix
Reinstall 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. error 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.
fix
Ensure 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. error 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).
fix
This 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. ↓
fix Catch h11.LocalProtocolError for errors you caused and h11.RemoteProtocolError for peer misbehavior. Both inherit from h11.ProtocolError if you need a catch-all.
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). ↓
fix Upgrade to Python >= 3.8. Pin h11 <= 0.11.x only if you must keep Python 2 support.
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. ↓
fix Strip whitespace from header values before passing them to h11 event constructors, or fix the upstream source producing invalid header values.
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. ↓
fix Upgrade to h11 >= 0.16.0. If you are behind a reverse proxy, also ensure the proxy correctly validates chunked encoding to prevent request smuggling.
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. ↓
fix Close the socket and create a brand-new h11.Connection for the next request. Do not attempt to call start_next_cycle() after an error.
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. ↓
fix Verify conn.our_state is h11.DONE and conn.their_state is h11.DONE before calling conn.start_next_cycle(). Always fully drain response events (including EndOfMessage) before reusing.
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. ↓
fix Accumulate all Data event payloads in a list/bytearray and concatenate only after receiving EndOfMessage.
Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) - - 0.05s 18.0M
3.10 slim (glibc) - - 0.03s 18M
3.11 alpine (musl) - - 0.09s 19.9M
3.11 slim (glibc) - - 0.07s 20M
3.12 alpine (musl) - - 0.08s 11.7M
3.12 slim (glibc) - - 0.08s 12M
3.13 alpine (musl) - - 0.07s 11.4M
3.13 slim (glibc) - - 0.07s 12M
3.9 alpine (musl) - - 0.04s 17.5M
3.9 slim (glibc) - - 0.04s 18M
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 wrong
from h11._util import LocalProtocolErrorcorrectimport h11 # catch h11.LocalProtocolError, h11.RemoteProtocolError - NEED_DATA / PAUSED / CLIENT / SERVER
import h11 # use h11.NEED_DATA, h11.PAUSED, h11.CLIENT, h11.SERVER
Quickstart verified last tested: 2026-04-23
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()