51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
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
|