ugi 0.2.1

Runtime-agnostic Rust request client with HTTP/1.1, HTTP/2, HTTP/3, H2C, WebSocket, SSE, and gRPC support
Documentation
#[cfg(feature = "h3")]
use crate::dns::{DnsCache, DnsConfig};
#[cfg(feature = "h3")]
use crate::error::{Error, ErrorKind, Result};
#[cfg(feature = "h3")]
use crate::tls::TlsConfig;
#[cfg(feature = "h3")]
use crate::url::Url;
#[cfg(feature = "h3")]
use async_net::UdpSocket;

#[cfg(feature = "h3")]
const MAX_DATAGRAM_SIZE: usize = 1350;

#[cfg(feature = "h3")]
pub(crate) fn resolve_quic_peer_addr(
    url: &Url,
    dns_cache: &DnsCache,
    dns_config: DnsConfig,
) -> Result<std::net::SocketAddr> {
    dns_cache
        .resolve_socket_addrs(url.host(), url.effective_port(), dns_config)?
        .into_iter()
        .next()
        .ok_or_else(|| Error::new(ErrorKind::Transport, "failed to resolve http3 peer address"))
}

#[cfg(feature = "h3")]
pub(crate) async fn bind_quic_socket(
    peer_addr: std::net::SocketAddr,
    local_addr: Option<std::net::SocketAddr>,
) -> Result<(UdpSocket, std::net::SocketAddr)> {
    let bind_addr = local_addr.unwrap_or_else(|| match peer_addr {
        std::net::SocketAddr::V4(_) => std::net::SocketAddr::from(([0, 0, 0, 0], 0)),
        std::net::SocketAddr::V6(_) => std::net::SocketAddr::from(([0, 0, 0, 0, 0, 0, 0, 0], 0)),
    });
    let socket = UdpSocket::bind(bind_addr).await.map_err(|err| {
        Error::with_source(ErrorKind::Transport, "failed to bind http3 udp socket", err)
    })?;
    let local_addr = socket.local_addr().map_err(|err| {
        Error::with_source(
            ErrorKind::Transport,
            "failed to inspect http3 udp socket",
            err,
        )
    })?;
    Ok((socket, local_addr))
}

#[cfg(feature = "h3")]
pub(crate) fn build_quiche_config(tls_config: &TlsConfig) -> Result<quiche::Config> {
    let _protocols = tls_config
        .validate_h3_alpn()
        .map_err(|message| Error::new(ErrorKind::Transport, message))?;
    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).map_err(|err| {
        Error::with_source(
            ErrorKind::Transport,
            "failed to initialize quiche config",
            err,
        )
    })?;
    config.verify_peer(!tls_config.accept_invalid_certs);
    if let Some(root_store) = &tls_config.root_store {
        match root_store {
            crate::RootStore::System | crate::RootStore::WebPki => {}
            crate::RootStore::PemFile(path) => config
                .load_verify_locations_from_file(path.to_str().ok_or_else(|| {
                    Error::new(ErrorKind::Transport, "invalid PEM root store path")
                })?)
                .map_err(|err| {
                    Error::with_source(
                        ErrorKind::Transport,
                        "failed to load HTTP/3 PEM root store",
                        err,
                    )
                })?,
            crate::RootStore::Pem(_) => {
                return Err(Error::new(
                    ErrorKind::Transport,
                    "http3 root_store currently supports PEM files only",
                ));
            }
        }
    }
    config
        .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)
        .map_err(|err| {
            Error::with_source(
                ErrorKind::Transport,
                "failed to configure quiche application protocols",
                err,
            )
        })?;
    config.set_max_idle_timeout(30_000);
    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);
    config.set_initial_max_data(10_000_000);
    config.set_initial_max_stream_data_bidi_local(1_000_000);
    config.set_initial_max_stream_data_bidi_remote(1_000_000);
    config.set_initial_max_stream_data_uni(1_000_000);
    config.set_initial_max_streams_bidi(100);
    config.set_initial_max_streams_uni(100);
    config.set_disable_active_migration(true);
    Ok(config)
}