v0.0.25 - One socket, many QUIC connections 🌐
🌐 One socket, many QUIC connections
zttp 0.0.25 gives HTTP/3 servers a shared-socket control plane. QuicEndpoint routes QUIC connections, answers unsupported versions, performs stateless Retry, tracks connection IDs, and aggregates timers without taking ownership of your UDP socket or clock.
pip install --upgrade zttp==0.0.25✨ Highlights
- A sans-I/O QUIC endpoint.
QuicEndpointroutes long and short headers to the correctH3Connection, retains state only after authenticating an Initial, bounds active connections, and produces addressedOutboundDatagramvalues. Optional Retry tokens bind the client address, original destination connection ID, Retry source connection ID, and expiry without retaining per-client state (#204). - Backpressure follows the application. HTTP/3 DATA receive credit is now returned through
H3Connection.consume_data(), after the application accepts bytes, rather than when the parser advances. Frame overhead remains automatic, and reset paths safely release unused credit (#206). - Explicit QUIC configuration and routing.
QuicTransportParametersprovides named, range-checked transport settings.local_connection_ids()exposes active local IDs, and endpoint-owned issuance keeps shared-socket routes synchronized (#203, #205). - Complete certificate chains. HTTP/3 credentials can send an ordered leaf-and-intermediate chain while preserving the single-certificate form (#202).
- Buffers go straight in. Every receive path now accepts contiguous buffer-protocol objects, including
bytes,bytearray, andmemoryview. Immutablebyteskeep the HTTP/1 zero-copy fast path, while mutable input is copied before its buffer export is released (#212).
🔌 Serving a shared UDP socket
import time
import zttp
endpoint = zttp.QuicEndpoint(
retry=True,
token_secret=b"replace-with-at-least-32-secret-bytes",
)
def now_us() -> int:
return time.monotonic_ns() // 1_000
def receive(datagram: bytes, peer_address: bytes) -> zttp.H3Connection | None:
connection = endpoint.receive_datagram(datagram, peer_address, now_us())
for outbound in endpoint.data_to_send():
send_datagram(outbound.data, outbound.peer_address)
return connectionThe endpoint remains sans-I/O. Your application owns the socket, address encoding, monotonic clock, and timer scheduling. Use next_timeout() and handle_timeout() to drive loss recovery and idle timeouts, then drain data_to_send() after each receive, timeout, or endpoint control call.
⚠️ HTTP/3 migration notes
Return flow-control credit after your application consumes each Data event. A sender may stall when you omit this call:
if isinstance(event, zttp.Data):
deliver(event.data)
connection.consume_data(event.stream_id, len(event.data))Raw encoded transport_params are replaced by a typed dictionary with validated values:
transport_params: zttp.QuicTransportParameters = {
"initial_max_data": 1_048_576,
"initial_max_streams_bidi": 100,
}
connection = zttp.Connection(
zttp.SERVER,
zttp.HTTP3,
transport_params=transport_params,
)For certificate chains, place the leaf first and intermediates after it:
credentials: zttp.TlsCredentials = {
"certificates": (leaf_certificate, intermediate_certificate),
"private_key": leaf_private_key,
}data_to_send_with_addresses() now returns OutboundDatagram objects. Read .data and .peer_address instead of unpacking two-item tuples.
HTTP/1.1 and HTTP/2 integrations require no changes. HTTP/3 remains experimental.
📦 Wheels
Binary wheels cover CPython 3.10 through 3.15 on Linux, macOS, and Windows, including free-threaded 3.14 and 3.15 builds. Free-threaded interpreters still enable the compatibility GIL when importing zttp.
Full changelog: v0.0.24...v0.0.25