A pure-Python, bring-your-own-I/O implementation of HTTP/1.1
—
State constants and role definitions for managing HTTP/1.1 connection lifecycle and protocol compliance. h11 uses a state machine to track the current phase of HTTP message exchange for both client and server roles.
Define the role of each side in the HTTP connection.
CLIENT = ...
"""
Represents the client role in an HTTP connection.
Usage:
conn = h11.Connection(h11.CLIENT)
The client role:
- Sends requests and receives responses
- Initiates HTTP conversations
- Can send Request events when in IDLE state
- Receives Response events when waiting for server
"""
SERVER = ...
"""
Represents the server role in an HTTP connection.
Usage:
conn = h11.Connection(h11.SERVER)
The server role:
- Receives requests and sends responses
- Responds to HTTP conversations
- Receives Request events when in IDLE state
- Can send Response events after receiving complete request
"""States that track the progression of HTTP message exchange.
IDLE = ...
"""
Initial state - ready for new request/response cycle.
Transitions:
- CLIENT: Can send Request → SEND_BODY or DONE
- SERVER: Waits to receive Request → SEND_RESPONSE
"""
SEND_RESPONSE = ...
"""
Server is ready to send response after receiving complete request.
Transitions:
- SERVER: Can send Response/InformationalResponse → SEND_BODY or DONE
- Only valid for SERVER role
"""
SEND_BODY = ...
"""
Ready to send message body data.
Transitions:
- Can send Data events with body chunks
- Send EndOfMessage → DONE
- Both CLIENT and SERVER can enter this state
"""
DONE = ...
"""
Finished with current request/response cycle.
Transitions:
- Wait for peer to finish their side
- Both sides DONE → PAUSED (for keep-alive) or connection closes
"""
MUST_CLOSE = ...
"""
Connection must be closed after current message completes.
Causes:
- HTTP/1.0 without keep-alive
- Connection: close header
- Protocol errors requiring connection termination
"""
CLOSED = ...
"""
Connection is closed - no further communication possible.
Terminal state:
- Cannot transition to other states
- Connection object should be discarded
"""
ERROR = ...
"""
Error state due to protocol violation.
Causes:
- Invalid HTTP messages
- State machine violations
- Calling send_failed()
- Protocol specification violations
"""
SWITCHED_PROTOCOL = ...
"""
Successfully switched to different protocol.
Usage:
- After successful HTTP/1.1 protocol upgrade
- Connection no longer follows HTTP/1.1 state machine
- Application handles new protocol directly
"""The state machine enforces HTTP/1.1 protocol rules through valid state transitions:
# CLIENT state flow
IDLE → SEND_BODY # After sending Request with body
IDLE → DONE # After sending Request without body
SEND_BODY → DONE # After sending EndOfMessage
# SERVER state flow
IDLE → SEND_RESPONSE # After receiving Request
SEND_RESPONSE → SEND_BODY # After sending Response with body
SEND_RESPONSE → DONE # After sending Response without body
SEND_BODY → DONE # After sending EndOfMessage
# Common flows
DONE → IDLE # Start new cycle (keep-alive)
DONE → CLOSED # Connection closes
* → ERROR # Protocol violation
* → MUST_CLOSE # Connection must closeCheck connection states to determine valid operations:
# Check our current state
if conn.our_state is h11.IDLE:
# Can send new request (CLIENT) or expect request (SERVER)
pass
elif conn.our_state is h11.SEND_BODY:
# Can send Data or EndOfMessage events
pass
elif conn.our_state is h11.DONE:
# Finished our side, wait for peer or start new cycle
pass
# Check peer state
if conn.their_state is h11.DONE:
# Peer finished, might be able to start new cycle
pass
# Check both states
if conn.states == {h11.CLIENT: h11.DONE, h11.SERVER: h11.DONE}:
# Both sides done, can start new cycle
conn.start_next_cycle()import h11
conn = h11.Connection(h11.CLIENT)
print(f"Initial state: {conn.our_state}") # IDLE
# Send request
req = h11.Request(method=b'GET', target=b'/', headers=[(b'host', b'example.com')])
data = conn.send(req)
print(f"After request: {conn.our_state}") # DONE (no body)
# Process response
while conn.our_state is not h11.DONE or conn.their_state is not h11.DONE:
raw_data = receive_from_socket()
conn.receive_data(raw_data)
event = conn.next_event()
if event is h11.NEED_DATA:
continue
elif isinstance(event, h11.Response):
print(f"Response received, their state: {conn.their_state}")
elif isinstance(event, h11.EndOfMessage):
print(f"Response complete, their state: {conn.their_state}") # DONE
# Both sides done - can start new cycle if keep-alive
if conn.our_state is h11.DONE and conn.their_state is h11.DONE:
if conn.next_event() is h11.PAUSED:
conn.start_next_cycle()
print(f"New cycle started: {conn.our_state}") # IDLEtry:
# Attempt invalid state transition
invalid_event = h11.Response(status_code=200) # SERVER event from CLIENT
data = conn.send(invalid_event)
except h11.LocalProtocolError as e:
print(f"Protocol error: {e}")
print(f"Connection state: {conn.our_state}") # ERROR
# Connection is now in ERROR state
assert conn.our_state is h11.ERRORdef handle_connection(socket):
conn = h11.Connection(h11.SERVER)
while True:
# Check if connection should close
if conn.our_state is h11.MUST_CLOSE or conn.our_state is h11.CLOSED:
socket.close()
break
if conn.our_state is h11.ERROR:
# Protocol error - log and close
print("Protocol error, closing connection")
socket.close()
break
# Normal processing
raw_data = socket.recv(4096)
if not raw_data:
break
conn.receive_data(raw_data)
while True:
event = conn.next_event()
if event is h11.NEED_DATA:
break
elif event is h11.PAUSED:
# Ready for new request/response cycle
conn.start_next_cycle()
break
elif isinstance(event, h11.Request):
# Handle request based on current state
handle_request(conn, event, socket)def check_connection_persistence(conn):
"""Determine if connection will be kept alive or closed."""
if conn.our_state is h11.MUST_CLOSE:
return "must_close"
elif conn.our_state is h11.CLOSED:
return "closed"
elif conn.our_state is h11.DONE and conn.their_state is h11.DONE:
# Both sides done - check if keep-alive
next_event = conn.next_event()
if next_event is h11.PAUSED:
return "keep_alive"
else:
return "closing"
else:
return "in_progress"
# Usage
status = check_connection_persistence(conn)
if status == "keep_alive":
conn.start_next_cycle()
# Can process new request/response
elif status in ("must_close", "closed", "closing"):
# Clean up and close socket
passInstall with Tessl CLI
npx tessl i tessl/pypi-h11