CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-h11

A pure-Python, bring-your-own-I/O implementation of HTTP/1.1

Pending
Overview
Eval results
Files

states.mddocs/

Protocol States

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.

Capabilities

Role Constants

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
"""

Connection State Constants

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
"""

State Machine Behavior

State Transitions

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 close

State Checking

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

Usage Patterns

Basic State Monitoring

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}")  # IDLE

Error Handling

try:
    # 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.ERROR

Connection Lifecycle Management

def 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)

Keep-Alive vs Connection Close

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
    pass

Install with Tessl CLI

npx tessl i tessl/pypi-h11

docs

connection.md

events.md

exceptions.md

index.md

states.md

tile.json