aioquic
aioquic is a pure-Python library for the QUIC network protocol and HTTP/3, featuring a minimal TLS 1.3 implementation. It follows a 'bring your own I/O' (sans-I/O) design, making it suitable for embedding in various frameworks. As of version 1.3.0, it supports QUIC v1 (RFC 9000), QUIC v2 (RFC 9369), and HTTP/3 (RFC 9114). The library is actively maintained with frequent updates and is regularly tested for interoperability against other QUIC implementations.
Warnings
- breaking As of version 1.3.0, aioquic has dropped support for Python 3.8 and 3.9. Users must use Python 3.10 or newer. Newer versions also include support for Python 3.13 and 3.14.
- gotcha When building aioquic from source (e.g., if pre-built wheels are not available for your platform), you must have OpenSSL development headers installed on your system. This is a common non-Python dependency that can cause installation failures.
- gotcha The core QUIC and HTTP/3 APIs in aioquic follow a 'sans-I/O' pattern, meaning they do not perform network I/O directly. Users are responsible for sending and receiving UDP datagrams. While the `aioquic.asyncio` API provides convenience, direct interaction with `QuicConnection` requires manual datagram handling, which can be a point of confusion for new users.
- gotcha Running an aioquic server, especially for HTTP/3, requires a TLS certificate and private key. For development or testing, self-signed certificates can be generated, but their presence and correct configuration in `QuicConfiguration` are mandatory.
Install
-
pip install aioquic
Imports
- QuicConnectionProtocol
from aioquic.asyncio import QuicConnectionProtocol
- connect
from aioquic.asyncio import connect
- serve
from aioquic.asyncio import serve
- QuicConfiguration
from aioquic.quic.configuration import QuicConfiguration
- H3_ALPN
from aioquic.h3.connection import H3_ALPN
- H3Connection
from aioquic.h3.connection import H3Connection
- HeadersReceived
from aioquic.h3.events import HeadersReceived
- DataReceived
from aioquic.h3.events import DataReceived
Quickstart
import asyncio
import logging
from typing import Optional
from aioquic.asyncio import connect
from aioquic.asyncio.protocol import QuicConnectionProtocol
from aioquic.h3.connection import H3_ALPN, H3Connection
from aioquic.h3.events import DataReceived, HeadersReceived, H3Event
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import QuicEvent
class HttpClientProtocol(QuicConnectionProtocol):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._http = H3Connection(self._quic)
self._buffer = b''
self._http_events = asyncio.Queue()
self._task = asyncio.create_task(self._http_event_handler())
async def _http_event_handler(self):
while True:
event = await self._http_events.get()
if isinstance(event, HeadersReceived):
for k, v in event.headers:
print(f"Header: {k.decode()} = {v.decode()}")
elif isinstance(event, DataReceived):
self._buffer += event.data
if event.stream_ended:
print(f"Body: {self._buffer.decode()}")
self._buffer = b''
def quic_event_received(self, event: QuicEvent) -> None:
self._http.handle_event(event)
while True:
http_event = self._http.pull_http_event()
if http_event is None:
break
self._http_events.put_nowait(http_event)
async def get(self, url: str):
headers = [
(b":method", b"GET"),
(b":scheme", b"https"),
(b":authority", url.encode()),
(b":path", b"/"),
(b"user-agent", b"aioquic-client/1.0"),
]
stream_id = self._http.send_headers(stream_id=self._http.get_next_available_stream_id(), headers=headers)
self._http.send_data(stream_id=stream_id, data=b'', end_stream=True)
self.transmit()
async def main():
logging.basicConfig(level=logging.INFO)
configuration = QuicConfiguration(
is_client=True,
alpn_protocols=H3_ALPN,
)
# For testing against a local server with a self-signed cert, you might need to disable certificate verification
# configuration.verify_mode = ssl.CERT_NONE
host = "quic.aiortc.org"
port = 4433
async with connect(
host=host,
port=port,
configuration=configuration,
create_protocol=HttpClientProtocol,
) as client_protocol:
await client_protocol.get(host)
await asyncio.sleep(1) # Give time for events to be processed
if __name__ == "__main__":
asyncio.run(main())