AIOQUIC(1)
๐ AIOQUIC(1) ๐
Okay bestie, this is the page for aioquic ๐, a library for the QUIC network protocol in Python and it is โจ so good โจ.
โจ NAME โจ
aioquic - aioquic ๐ฆ The badges are lowkey iconic, and each one has an unlabelled image beside it:
- License badge
https://pypi.python.org/pypi/aioquic๐, then the Version badgehttps://pypi.python.org/pypi/aioquic๐, then the Python versions badgehttps://pypi.python.org/pypi/aioquic๐, then the Tests badgehttps://github.com/aiortc/aioquic/actions๐, then the Coverage badgehttps://codecov.io/gh/aiortc/aioquic๐
So, aioquic is a library for the QUIC network protocol in Python ๐ธ, and it comes with several APIs:
- a QUIC API following the "bring your own I/O" pattern, suitable for embedding in any framework, ๐
- an HTTP/3 API which also follows the "bring your own I/O" pattern, ๐ง
- a QUIC convenience API built on top of asyncio (
https://docs.python.org/3/library/asyncio.html#module-asyncio), Python's standard asynchronous I/O framework. ๐
๐ DESIGN ๐
Sans-IO APIs
Both the QUIC and the HTTP/3 APIs follow the sans I/O pattern (https://sans-io.readthedocs.io/) ๐ซถ, which means they leave the actual I/O operations to the API user. This approach has a number of advantages, including making the code testable and allowing integration with different concurrency models. It's giving clean architecture, no cap โจ.
TLS and encryption
TLS 1.3
aioquic features a minimal TLS 1.3 implementation built upon the cryptography library (https://cryptography.io/) ๐. Why not just use the normal stuff? Because QUIC needs some APIs that are currently unavailable in mainstream TLS implementations such as OpenSSL ๐:
- the ability to extract traffic secrets
- the ability to operate directly on TLS messages, without using the TLS record layer
Header protection and payload encryption
QUIC makes extensive use of cryptographic operations to protect QUIC packet headers and encrypt packet payloads ๐ฅ. These operations happen for every single packet, and they are a determining factor for performance. That's why they are implemented as a C extension linked to OpenSSL (https://www.openssl.org/). Main character energy for performance ๐.
๐ง QUIC API ๐ง
The QUIC API performs no I/O on its own, leaving that to the API user. This means you can integrate QUIC in any Python application, regardless of the concurrency model you're using ๐ฆ.
Connection
The class signature is:
aioquic.quic.connection.QuicConnection(*, configuration, original_destination_connection_id=None, retry_source_connection_id=None, session_ticket_fetcher=None, session_ticket_handler=None, token_handler=None)
A QUIC connection. โจ
The state machine is driven by three kinds of sources:
- the API user requesting data to be sent out (see
connect(),reset_stream(),send_ping(),send_datagram_frame()andsend_stream_data()) ๐ - data being received from the network (see
receive_datagram()) ๐ฉ - a timer firing (see
handle_timer()) โฐ
Parameters
configuration(QuicConfiguration) โ the QUIC configuration to use. ๐
change_connection_id()
Switch to the next available connection ID and retire the previous one. Bestie, this one is a glow-up ๐.
Note: After calling this method you need to call the QUIC connectiondatagrams_to_send()method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling thetransmit()method (aioquic.asyncio.QuicConnectionProtocol.transmit) will do it for you. ๐
Return type: None
close(error_code=QuicErrorCode.NO_ERROR, frame_type=None, reason_phrase='')
Close the connection. Bye bye, connection ๐.
Note: After calling this method you need to call the QUIC connectiondatagrams_to_send()method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling thetransmit()method (aioquic.asyncio.QuicConnectionProtocol.transmit) will do it for you. ๐
Parameters
error_code(int) โ an error code telling why the connection is being closed. ๐ชreason_phrase(str) โ a human-readable explanation of why the connection is being closed. ๐
Return type: None
connect(addr, now)
Initiate the TLS handshake. This can only be called for clients, and only a single time. Ngl, that's a strict vibe ๐ โโ๏ธ.
Note: After calling this method you need to call the QUIC connectiondatagrams_to_send()method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling thetransmit()method (aioquic.asyncio.QuicConnectionProtocol.transmit) will do it for you. ๐
Parameters
addr(Any) โ the network address of the remote peer. ๐now(float) โ the current time. โฑ๏ธ
Return type: None
datagrams_to_send(now)
Return a list of (data, addr) tuples of datagrams which need to be sent, plus the network address each one needs to be sent to ๐ฆ.
After calling this method, call get_timer() to find out when the next timer needs to be set ๐ซถ.
Parameters
now(float) โ the current time. โฑ๏ธ
Return type: list[tuple[bytes, Any]]
get_next_available_stream_id(is_unidirectional=False)
Return the stream ID for the next stream created by this endpoint ๐.
Return type: int
get_timer()
Return the time at which the timer should fire, or None if no timer is needed. โฐ Simple and iconic.
Return type: Optional[float]
handle_timer(now)
Handle the timer. Periodt โฐ.
Note: After calling this method you need to call the QUIC connectiondatagrams_to_send()method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling thetransmit()method (aioquic.asyncio.QuicConnectionProtocol.transmit) will do it for you. ๐
Parameters
now(float) โ the current time. โฑ๏ธ
Return type: None
next_event()
Retrieve the next event from the event buffer. It returns None if there are no buffered events, so don't panic if it's empty ๐ซ .
Return type: Optional[QuicEvent]
receive_datagram(data, addr, now)
Handle an incoming datagram ๐ฌ.
Note: After calling this method you need to call the QUIC connectiondatagrams_to_send()method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling thetransmit()method (aioquic.asyncio.QuicConnectionProtocol.transmit) will do it for you. ๐
Parameters
data(bytes) โ the datagram which was received. ๐ฆaddr(Any) โ the network address from which the datagram was received. ๐now(float) โ the current time. โฑ๏ธ
Return type: None
request_key_update()
Request an update of the encryption keys. Key rotation hits different ๐.
Note: After calling this method you need to call the QUIC connectiondatagrams_to_send()method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling thetransmit()method (aioquic.asyncio.QuicConnectionProtocol.transmit) will do it for you. ๐
Return type: None
reset_stream(stream_id, error_code)
Abruptly terminate the sending part of a stream. Yikes, no warning, just vibes ๐.
This method has no effect if a reset has already been triggered, either by a call toreset_stream()or by the reception of aSTOP_SENDINGframe. ๐ โโ๏ธ
Okay bestie, the good stuff is here ๐ฆ Ready for the rest of the methods?
๐จ Heads up, bestie: after calling this one you still have to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network ๐ก If you're on the asyncio API, callingtransmit()(that'saioquic.asyncio.QuicConnectionProtocol.transmit) does that step for you ๐
Parameters
stream_id(int) โ the stream's ID ๐ธerror_code(int) โ an error code explaining why the stream is being reset ๐
Return type: None
send_datagram_frame(data) ๐ฆ
Sends a DATAGRAM frame ๐จ
๐จ Heads up, bestie: after calling this one you still have to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network ๐ก If you're on the asyncio API, callingtransmit()(that'saioquic.asyncio.QuicConnectionProtocol.transmit) does that step for you ๐
Parameters
data(bytes) โ the data you want to send ๐
Return type: None
send_ping(uid) ๐
Sends a PING frame over to the peer ๐
๐จ Heads up, bestie: after calling this one you still have to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network ๐ก If you're on the asyncio API, callingtransmit()(that'saioquic.asyncio.QuicConnectionProtocol.transmit) does that step for you ๐
Parameters
uid(int) โ a unique ID for this PING ๐
Return type: None
send_stream_data(stream_id, data, end_stream=False) ๐ง
Sends data on a specific stream ๐
๐จ Heads up, bestie: after calling this one you still have to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network ๐ก If you're on the asyncio API, callingtransmit()(that'saioquic.asyncio.QuicConnectionProtocol.transmit) does that step for you ๐
Parameters
stream_id(int) โ the stream's ID ๐ธdata(bytes) โ the data you want to send ๐end_stream(bool) โ if you set this toTrue, the FIN bit gets set ๐ (the default isFalse)
Return type: None
stop_stream(stream_id, error_code) ๐
Requests termination of the receiving side of a stream ๐ โโ๏ธ
๐จ Heads up, bestie: after calling this one you still have to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network ๐ก If you're on the asyncio API, callingtransmit()(that'saioquic.asyncio.QuicConnectionProtocol.transmit) does that step for you ๐
Parameters
stream_id(int) โ the stream's ID ๐ธerror_code(int) โ an error code explaining why the stream is being stopped ๐
Return type: None
โจ Configuration โจ
Here's where the settings live, so you can set up your connection exactly how you want it ๐
aioquic.quic.configuration.QuicConfiguration
class aioquic.quic.configuration.QuicConfiguration(alpn_protocols=None, congestion_control_algorithm='reno', connection_id_length=8, idle_timeout=60.0, is_client=True, max_data=1048576, max_datagram_size=1200, max_stream_data=1048576, quic_logger=None, secrets_log_file=None, server_name=None, session_ticket=None, token=b'', cadata=None, cafile=None, capath=None, certificate=None, certificate_chain=<factory>, cipher_suites=None, initial_rtt=0.1, max_datagram_frame_size=None, original_version=None, private_key=None, quantum_readiness_test=False, supported_versions=<factory>, verify_mode=None)
This is your QUIC configuration class, the whole vibe for your connection ๐
alpn_protocols: Optional[list[str]] = None ๐
A list of the ALPN protocols you support ๐
congestion_control_algorithm: str = 'reno' ๐
The name of the congestion control algorithm you want to use ๐ฆ
Currently supported algorithms are "reno" and "cubic" โจ
connection_id_length: int = 8 ๐ชช
The length in bytes of your local connection IDs ๐ข
idle_timeout: float = 60.0 โฐ
The idle timeout in seconds โณ
โ ๏ธ The connection gets terminated if nothing is received for the given duration, so don't ghost your peer for too long ๐
is_client: bool = True ๐โโ๏ธ
Whether this is the client side of the QUIC connection ๐
load_cert_chain(certfile, keyfile=None, password=None) ๐
Loads a private key and its matching certificate ๐๏ธ
Return type: None
load_verify_locations(cafile=None, capath=None, cadata=None) ๐
Loads a set of "certification authority" (CA) certificates, which are used to validate other peers' certificates ๐ต๏ธโโ๏ธ
Return type: None
max_data: int = 1048576 ๐ชฃ
The connection-wide flow control limit ๐ฆ
max_datagram_size: int = 1200 ๐ฆ
The maximum QUIC payload size in bytes to send, not counting UDP or IP overhead ๐
max_stream_data: int = 1048576 ๐
The per-stream flow control limit ๐ฅ
quic_logger: Optional[QuicLogger] = None ๐
The QuicLogger instance you want to log events to ๐ช
secrets_log_file: Optional[TextIO] = None ๐
A file-like object where traffic secrets get logged ๐ซฃ
This is super useful for analyzing traffic captures with Wireshark ๐ฆ
server_name: Optional[str] = None ๐ท๏ธ
The server name to use when verifying the server's TLS certificate. It can be either a DNS name or an IP address ๐
If it's a DNS name, it also gets sent during the TLS handshake in the Server Name Indication (SNI) extension ๐
๐ Note: this is only used by clients ๐ซถ
session_ticket: Optional[SessionTicket] = None ๐๏ธ
The TLS session ticket that should be used for session resumption ๐
token: bytes = b'' ๐ช
The address validation token that can be used to validate future connections ๐งฟ
๐ Note: this is only used by clients ๐ซถ
aioquic.quic.logger.QuicLogger
class aioquic.quic.logger.QuicLogger
A QUIC event logger that stores traces in memory ๐ง
to_dict() ๐
Returns the traces as a dictionary, which you can write out as JSON ๐ค
Return type: dict[str, Any]
โจ Events โจ
Events are how your connection tells you what just happened, so pay attention ๐
aioquic.quic.events.QuicEvent
class aioquic.quic.events.QuicEvent
The base class for all QUIC events ๐
aioquic.quic.events.ConnectionTerminated
class aioquic.quic.events.ConnectionTerminated(error_code, frame_type, reason_phrase)
This event fires when the QUIC connection gets terminated ๐
error_code: int ๐ซ
The error code that was specified when the connection was closed ๐ข
frame_type: Optional[int] ๐งฉ
The frame type that caused the connection to close, or None ๐คทโโ๏ธ
reason_phrase: str ๐ฌ
The human-readable reason the connection was closed ๐
aioquic.quic.events.HandshakeCompleted
class aioquic.quic.events.HandshakeCompleted(alpn_protocol, early_data_accepted, session_resumed)
This event fires when the TLS handshake completes, and that's a whole main-character moment ๐โจ
Okay so the events are literally where the QUIC API gets its glow-up ๐
Here's the rest of the HandshakeCompleted attributes โจ
alpn_protocol(Optional[str]) โ The protocol that got negotiated through ALPN, orNoneif nothing did ๐early_data_accepted(bool) โ Whether the remote peer said yes to early (0-RTT) data ๐session_resumed(bool) โ Whether a TLS session got resumed ๐
class aioquic.quic.events.PingAcknowledged(uid)
The PingAcknowledged event pops off when a PING frame gets acknowledged. ๐
uid(int) โ The unique ID of that PING ๐
class aioquic.quic.events.StopSendingReceived(error_code, stream_id)
The StopSendingReceived event fires when the remote peer basically says "hey, please stop sending data on this stream." ๐
โโ๏ธ
error_code(int) โ The error code the peer sent along with that request ๐stream_id(int) โ The ID of the stream the peer wants you to stop transmitting data on ๐
class aioquic.quic.events.StreamDataReceived(data, end_stream, stream_id)
The StreamDataReceived event fires every time data shows up on a stream. ๐ฌ
data(bytes) โ The data that got received ๐ฆend_stream(bool) โ Whether the STREAM frame had the FIN bit set. Basically, is this the goodbye message? ๐stream_id(int) โ The ID of the stream that the data came in on ๐งต
class aioquic.quic.events.StreamReset(error_code, stream_id)
The StreamReset event fires when the remote peer resets a stream. Rip ๐ญ
error_code(int) โ The error code that caused the reset ๐ซstream_id(int) โ The ID of the stream that got reset ๐
โจ HTTP/3 API โจ
Okay bestie, the HTTP/3 API does no I/O on its own ๐ โโ๏ธ It leaves all the sending and receiving to you, the API user. That means you can plug HTTP/3 into any Python app, no matter what concurrency model you're vibing with ๐
๐ฆ Connection
class aioquic.h3.connection.H3Connection(quic, enable_webtransport=False)
A low-level HTTP/3 connection object. It's giving raw power ๐ช
Parameters
quic(QuicConnection) โ AQuicConnectioninstance ๐ง
create_webtransport_stream(session_id, is_unidirectional=False)
Creates a WebTransport stream and hands you back its stream ID. ๐
Note: After calling this, you need to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network. If you're using the asyncio API, calling thetransmit()method does that part for you. ๐ซถ
Parameters
session_id(int) โ The WebTransport session identifier ๐is_unidirectional(bool) โ Whether you want a unidirectional stream (one-way, no cap) โก๏ธ
Return type: int
handle_event(event)
Handles a QUIC event and returns a list of HTTP events. Main character energy โญ
Parameters
event(QuicEvent) โ The QUIC event you want to handle ๐ฏ
Return type: list[H3Event]
property received_settings: dict[int, int] | None
Returns the received SETTINGS frame, or None if there isn't one yet. โ๏ธ
send_data(stream_id, data, end_stream)
Sends data on the given stream. ๐ค
Note: After calling this, you need to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network. If you're using the asyncio API, calling thetransmit()method does that part for you. ๐ซถ
Parameters
stream_id(int) โ The stream ID you're sending on ๐งตdata(bytes) โ The data you're sending ๐ฆend_stream(bool) โ Whether to end the stream here ๐
Return type: None
send_datagram(stream_id, data)
Sends a datagram for the specified stream. Quick heads up: if the stream ID isn't a client-initiated bidirectional stream, an InvalidStreamTypeError exception gets raised. Not it ๐
โโ๏ธ
Note: After calling this, you need to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network. If you're using the asyncio API, calling thetransmit()method does that part for you. ๐ซถ
Parameters
stream_id(int) โ The stream ID ๐data(bytes) โ The HTTP/3 datagram payload ๐ฆ
Return type: None
send_headers(stream_id, headers, end_stream=False)
Sends headers on the given stream. ๐
Note: After calling this, you need to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network. If you're using the asyncio API, calling thetransmit()method does that part for you. ๐ซถ
Parameters
stream_id(int) โ The stream ID you want the headers sent on ๐งตheaders(list[tuple[bytes, bytes]]) โ The HTTP headers to send ๐end_stream(bool) โ Whether to end the stream ๐
Return type: None
send_push_promise(stream_id, headers)
Sends a push promise related to the specified stream. ๐ฎ It returns the stream ID where headers and data can be sent next.
If the stream ID isn't a client-initiated bidirectional stream, an InvalidStreamTypeError exception is raised. Big oof ๐ฌ
If there aren't any available push IDs, a NoAvailablePushIDError exception is raised. Also a big oof ๐
Note: After calling this, you need to call the QUIC connection'sdatagrams_to_send()method to grab the data that needs to go out over the network. If you're using the asyncio API, calling thetransmit()method does that part for you. ๐ซถ
Parameters
stream_id(int) โ The stream ID the push promise is related to ๐งตheaders(list[tuple[bytes, bytes]]) โ The HTTP request headers for this push ๐จ
Return type: int
The last bit of the connection stuff ๐
property sent_settings: dict[int, int] | None hands you back the SETTINGS frame that was sent, or None if there isn't one ๐ธ
โจ Events โจ
class aioquic.h3.events.H3Event ๐
This is the base class for all HTTP/3 events. Every other event below builds on it.
class aioquic.h3.events.DatagramReceived(data, stream_id) ๐ฆ
This one fires whenever a datagram comes in from the remote peer. Main character moment ๐
data: bytesโ the data that was received โจstream_id: intโ the ID of the stream the data was received for
class aioquic.h3.events.DataReceived(data, stream_id, stream_ended, push_id=None) ๐
The DataReceived event fires whenever data arrives on a stream from the remote peer. It's giving "stuff is happening" ๐
data: bytesโ the data that was receivedpush_id: Optional[int] = Noneโ the Push ID, orNoneif this isn't a push ๐stream_ended: boolโ whether the STREAM frame had the FIN bit set ๐ซถstream_id: intโ the ID of the stream the data was received for
class aioquic.h3.events.HeadersReceived(headers, stream_id, stream_ended, push_id=None) ๐
The HeadersReceived event fires whenever headers are received. Headers are literally the vibe check of every request ๐ฅ
headers: list[tuple[bytes, bytes]]โ the headers ๐push_id: Optional[int] = Noneโ the Push ID, orNoneif this isn't a pushstream_ended: boolโ whether the STREAM frame had the FIN bit setstream_id: intโ the ID of the stream the headers were received for
class aioquic.h3.events.PushPromiseReceived(headers, push_id, stream_id) ๐ง
Okay, so the docs say this one is the PushedStreamReceived event, which fires whenever a pushed stream has been received from the remote peer. The class is actually called PushPromiseReceived though, so ๐ just know they're the same energy.
headers: list[tuple[bytes, bytes]]โ the request headers ๐ธpush_id: intโ the Push ID of the push promisestream_id: intโ the Stream ID of the stream that the push is related to
class aioquic.h3.events.WebTransportStreamDataReceived(data, stream_id, stream_ended, session_id) ๐ฆ
The WebTransportStreamDataReceived event fires whenever data is received for a WebTransport stream. Very iconic, very specific ๐
data: bytesโ the data that was receivedsession_id: intโ the ID of the session the data was received forstream_ended: boolโ whether the STREAM frame had the FIN bit setstream_id: intโ the ID of the stream the data was received for
โจ Exceptions โจ
class aioquic.h3.exceptions.H3Error ๐
This is the base class for all HTTP/3 exceptions. If something goes sideways, this is the parent that catches the drama ๐ญ
class aioquic.h3.exceptions.InvalidStreamTypeError ๐
An action was attempted on an invalid stream type. Not the move ๐
class aioquic.h3.exceptions.NoAvailablePushIDError ๐ธ
This one shows up when there are no available push IDs left, or when push isn't supported by the remote party. No push IDs left, no cap, no vibes ๐ซ
โจ ASYNCIO API โจ
okay bestie, the asyncio API is a high-level QUIC API built on top of https://docs.python.org/3/library/asyncio.html#module-asyncio, which is Python's standard asynchronous I/O framework ๐
if you already know asyncio you're basically already there ๐ซถ
aioquic comes with a few examples you can copy and learn from, including:
- an HTTP/3 client ๐ฆ
- an HTTP/3 server ๐ธ
you can browse all of them on GitHub at https://github.com/aiortc/aioquic/tree/main/examples ๐
๐ Client
async with aioquic.asyncio.connect(host, port, *, configuration=None, create_protocol=<class 'aioquic.asyncio.protocol.QuicConnectionProtocol'>, session_ticket_handler=None, stream_handler=None, token_handler=None, wait_connected=True, local_port=0)
this one connects to a QUIC server at the host and port you give it โจ connect() returns an awaitable, and when you await it you get a QuicConnectionProtocol back, which is what you use to create streams. the return type is AsyncGenerator[QuicConnectionProtocol, None].
connect() also takes these optional args, so pick whatever your vibe needs:
configurationโ aQuicConfigurationconfiguration object (aioquic.quic.configuration.QuicConfiguration) ๐create_protocolโ lets you customize the Protocol (https://docs.python.org/3/library/asyncio-protocol.html#asyncio.Protocol) that manages the connection ๐ง it should be a callable or class that takes the same arguments asQuicConnectionProtocoland returns aQuicConnectionProtocolinstance or a subclass of one.session_ticket_handlerโ a callback the TLS engine calls when it receives a new session ticket ๐ซstream_handlerโ a callback that runs whenever a stream gets created ๐ it has to accept two arguments: anasyncio.StreamReader(https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamReader) and anasyncio.StreamWriter(https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter).wait_connectedโ decides whether the context manager waits for the connection to be established before it yields theQuicConnectionProtocol. it defaults toTrue, but you can set it toFalseif you want to start sending data right away using 0-RTT ๐local_portโ the UDP port number this client wants to bind ๐
๐ Server
await aioquic.asyncio.serve(host, port, *, configuration, create_protocol=<class 'aioquic.asyncio.protocol.QuicConnectionProtocol'>, session_ticket_fetcher=None, session_ticket_handler=None, retry=False, stream_handler=None)
this starts a QUIC server at the host and port you pick ๐ฐ serve() requires a QuicConfiguration that has a TLS certificate and private key, passed in as the configuration argument. the return type is QuicServer.
serve() also takes these optional args:
create_protocolโ same deal as the client version: customize the Protocol (https://docs.python.org/3/library/asyncio-protocol.html#asyncio.Protocol) that manages each connection. it should be a callable or class that takes the same arguments asQuicConnectionProtocoland returns aQuicConnectionProtocolinstance or a subclass.session_ticket_fetcherโ a callback the TLS engine calls when the peer presents a session ticket ๐ it should return the session ticket with the matching ID, orNoneif it can't find one.session_ticket_handlerโ a callback the TLS engine calls when a new session ticket gets issued ๐ฆ it should store the ticket so it can be looked up later.retryโ decides whether client addresses get validated before the cryptographic handshake, using a retry packet ๐ก๏ธ it'sFalseby default.stream_handlerโ a callback that runs whenever a stream is created ๐ it has to accept two arguments: anasyncio.StreamReader(https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamReader) and anasyncio.StreamWriter(https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter).
๐ Common
class aioquic.asyncio.QuicConnectionProtocol(quic, stream_handler=None)
this is the protocol class both sides use to manage a connection, and these are the methods you'll be calling on it ๐ง
change_connection_id()
swaps out the connection ID used to talk to the peer ๐ the old connection ID gets retired. the return type is None.
close(error_code=QuicErrorCode.NO_ERROR, reason_phrase='')
closes the connection ๐ the parameters are:
error_code(int) โ an error code that explains why the connection is closing ๐จreason_phrase(str) โ a human-readable explanation of why the connection is closing ๐ฌ
the return type is None.
connect(addr, transmit=True)
kicks off the TLS handshake ๐ค this can only be called by clients, and only a single time. the return type is None.
await create_stream(is_unidirectional=False)
creates a QUIC stream and gives you back a (reader, writer) pair ๐ the reader and writer are instances of asyncio.StreamReader (https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamReader) and asyncio.StreamWriter (https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamWriter). the return type is tuple[StreamReader, StreamWriter].
await ping()
pings the peer and waits for the response ๐ the return type is None.
quic_event_received(event)
gets called whenever a QUIC event comes in ๐จ if you're subclassing, reimplement this to handle the events. the return type is None.
request_key_update()
asks for an update of the encryption keys ๐ the return type is None.
transmit()
sends any pending datagrams to the peer and arms the timer if it's needed โฐ this gets called automatically when data comes in from the peer or when a timer goes off. but if you poke at the underlying QuicConnection (aioquic.quic.connection.QuicConnection) directly, make sure you call this method whenever data needs to head out to the network. the return type is None.
await wait_closed()
waits for the connection to close ๐ฅ the return type is None.
await wait_connected()
waits for the TLS handshake to finish โ
the return type is None.
โจ CHANGELOG โจ
okay so this is the bit where the devs spill the tea on every release ๐ we're going newest to oldest, bestie ๐ซถ
๐ 1.3.0
- Ensure
PATH_CHALLENGEframes get sent before any other frame types ๐ฆ - Reclaim memory when HTTP/3 streams end ๐งนโจ
- Limit the number of remote
PATH_CHALLENGEstored per path ๐ - Avoid an assertion error when receiving multiple
STOP_SENDINGframes ๐ - Improve type annotations ๐
- Make it possible to use LibreSSL instead of OpenSSL ๐ธ
- Add support for Python 3.13 and 3.14, and drop support for Python 3.8 and 3.9 ๐
- Build binary wheels for musllinux <https://peps.python.org/pep-0656/> ๐
- Build binary wheels against OpenSSL <https://www.openssl.org/> 3.5.4 ๐ฅ
- Add a command-line argument to the
http3_clientexample to request a key update during interoperability tests ๐ง
๐ 1.2.0
- Add support for compatible version handling as defined in RFC 9368 <https://datatracker.ietf.org/doc/html/rfc9368.html> ๐ฆ
- Add support for QUIC Version 2, as defined in RFC 9369 <https://datatracker.ietf.org/doc/html/rfc9369.html> ๐
- Drop support for draft QUIC versions which were obsoleted by RFC 9000 <https://datatracker.ietf.org/doc/html/rfc9000.html> ๐
- Improve datagram padding to allow better packet coalescing and reduce the number of roundtrips during connection establishement โญ
- Fix server anti-amplification checks during address validation so they take invalid packets, such as datagram-level padding, into account ๐ซถ
- Allow asyncio clients to make efficient use of 0-RTT by passing
wait_connected=Falsetoconnect()<#aioquic.asyncio.connect> ๐ - Add command-line arguments to the
http3_clientexample for client certificates and for negotiating QUIC Version 2 ๐
๐ 1.1.0
- Improve path challenge handling and compliance with RFC 9000 <https://datatracker.ietf.org/doc/html/rfc9000.html> ๐
- Limit the amount of buffered CRYPTO data to avoid memory exhaustion ๐ก๏ธ
- Enable SHA-384 based signature algorithms and SECP384R1 key exchange ๐
- Build binary wheels against OpenSSL <https://www.openssl.org/> 3.3.0 ๐ฆ
๐ 1.0.0
- Ensure no data is sent after a stream reset ๐ โโ๏ธ
- Make
H3Connection<#aioquic.h3.connection.H3Connection>'ssend_datagram()<#aioquic.h3.connection.H3Connection.send_datagram> andsend_push_promise()<#aioquic.h3.connection.H3Connection.send_push_promise> methods raise anInvalidStreamTypeError<#aioquic.h3.exceptions.InvalidStreamTypeError> exception if an invalid stream ID is specified ๐จ - Improve the documentation for
QuicConnectionProtocol<#aioquic.asyncio.QuicConnectionProtocol>'stransmit()<#aioquic.asyncio.QuicConnectionProtocol.transmit> method ๐ - Fix
utcnow()<https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow> deprecation warning on Python 3.12 by using cryptography <https://cryptography.io/> 42.0 and timezone-aware datetime <https://docs.python.org/3/library/datetime.html#datetime.datetime> instances when validating TLS certificates ๐งผ - Build binary wheels against OpenSSL <https://www.openssl.org/> 3.2.0 ๐ฆ
- Ignore any non-ASCII ALPN values received ๐
- Perform more extensive HTTP/3 header validation in
H3Connection<#aioquic.h3.connection.H3Connection> ๐ - Fix exceptions when draining stream writers in the asyncio API <> ๐
- Set the
QuicConnection<#aioquic.quic.connection.QuicConnection> idle timer according to RFC 9000 <https://datatracker.ietf.org/doc/html/rfc9000.html> section 10.1 โฐ - Implement fairer stream scheduling in
QuicConnection<#aioquic.quic.connection.QuicConnection> to avoid head-of-line blocking ๐ฆ - Only load certifi <https://github.com/certifi/python-certifi> root certificates if none was specified in the
QuicConfiguration<#aioquic.quic.configuration.QuicConfiguration> ๐ฏ - Improve padding of UDP datagrams containing Initial packets to comply with RFC 9000 <https://datatracker.ietf.org/doc/html/rfc9000.html> section 14.1 ๐
- Limit the number of pending connection IDs marked for retirement to prevent a possible DoS attack ๐
โจ LICENSE โจ
okay the legal part, but make it cute ๐ this is the BSD 3-Clause license, so the vibe is: keep the copyright notice, keep the disclaimer, and don't use the names of the contributors to promote stuff without permission ๐ โโ๏ธ
Copyright (c) Jeremy Lainรฉ. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of aioquic nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ๐ translation: no warranties, no promises, nothing. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ๐ญ
โจ AUTHOR โจ
the genius behind it all is Jeremy Lainรฉ ๐๐ซถ
โจ COPYRIGHT โจ
Jeremy Lainรฉ owns the copyright ๐
Written up on Jan 17, 2026 for AIOQUIC(1) ๐ฆ bestie, that's the whole manual, go touch some grass and then go read the source ๐ โจ
๐ฆ where this page comes from
- fedora 44: python3-aioquic 1.3.0-2.fc44 ยท BSD-3-Clause
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms