from __future__ import annotations import asyncio from dataclasses import dataclass @dataclass class Http3Info: supported: bool = False error: str | None = None async def probe_http3(host: str, port: int, sni: str | None, timeout: float = 6.0) -> Http3Info: """Attempt a QUIC handshake to determine HTTP/3 availability. Uses aioquic if available. On any failure returns supported=False. """ try: from aioquic.asyncio.client import connect from aioquic.quic.configuration import QuicConfiguration from aioquic.quic.events import HandshakeCompleted except Exception as e: return Http3Info(supported=False, error=f"aioquic unavailable: {e}") info = Http3Info() try: host_for_quic = sni or host configuration = QuicConfiguration( is_client=True, alpn_protocols=["h3", "h3-29"], verify_mode=None, server_name=host_for_quic, ) try: import ssl as _ssl configuration.verify_mode = _ssl.CERT_NONE except Exception: pass async def _run(): # aioquic ≥ 0.9 dropped the `server_name` kwarg on connect(); SNI # is taken from QuicConfiguration.server_name instead. async with connect(host, port, configuration=configuration) as client: await client.wait_connected() return True ok = await asyncio.wait_for(_run(), timeout=timeout) info.supported = bool(ok) except Exception as e: info.supported = False info.error = f"{e.__class__.__name__}: {e}" return info