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.
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
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()