diff --git a/impit/src/errors.rs b/impit/src/errors.rs index d815590b..97d01570 100644 --- a/impit/src/errors.rs +++ b/impit/src/errors.rs @@ -84,7 +84,7 @@ pub enum ImpitError { UrlMissingHostnameError(String), #[error("The URL uses an unsupported protocol (`{0}`). Currently, only HTTP and HTTPS are supported.")] UrlProtocolError(String), - #[error("The request was made with http3_prior_knowledge, but HTTP/3 usage wasn't enabled.")] + #[error("The request was made with http3_prior_knowledge, but HTTP/3 usage wasn't enabled (or was disabled because a proxy is set).")] Http3Disabled, #[error("The request method `{0}` is invalid. Only GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS and TRACE are supported.")] InvalidMethod(String), diff --git a/impit/src/impit.rs b/impit/src/impit.rs index 559967b0..9a8164cc 100644 --- a/impit/src/impit.rs +++ b/impit/src/impit.rs @@ -1,7 +1,7 @@ #[cfg(feature = "http3")] use tokio::sync::RwLock; -use log::debug; +use log::{debug, warn}; use reqwest::{cookie::CookieStore, header::HeaderMap, Method, Response, Version}; use std::{fmt::Debug, net::IpAddr, sync::Arc, time::Duration}; use url::Url; @@ -179,6 +179,10 @@ impl ImpitBuilder { /// /// Requires the `http3` feature of this crate (and the `--cfg reqwest_unstable` rustflag). /// + /// HTTP/3 is never used with a proxy, set either with [`with_proxy`](Self::with_proxy) or in the + /// `HTTPS_PROXY` or `ALL_PROXY` environment variables: proxies only carry TCP, so QUIC requests would + /// skip the proxy and go out from this machine's IP address. With a proxy, the client uses HTTP/2. + /// /// Note that this feature is experimental and may not work as expected with all servers. #[cfg(feature = "http3")] pub fn with_http3(mut self) -> Self { @@ -231,6 +235,15 @@ impl ImpitBuilder { self } + /// Whether requests go through a proxy: the one set with [`with_proxy`](Self::with_proxy), or one + /// that `reqwest` reads from the environment for HTTPS URLs. + fn uses_proxy(&self) -> bool { + !self.proxy_url.is_empty() + || ["HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy"] + .iter() + .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty())) + } + /// Builds the [`Impit`] instance. pub fn build(self) -> Result, ImpitError> { Impit::new(self) @@ -322,7 +335,12 @@ impl Impit { } /// Creates a new [`Impit`] instance based on the options stored in the [`ImpitBuilder`] instance. - fn new(config: ImpitBuilder) -> Result { + fn new(mut config: ImpitBuilder) -> Result { + if config.max_http_version == Version::HTTP_3 && config.uses_proxy() { + warn!("HTTP/3 is disabled because a proxy is set: QUIC requests would skip the proxy"); + config.max_http_version = Version::HTTP_2; + } + let mut h3_client: Option = None; let mut base_client = Self::new_reqwest_client(&config)?; @@ -742,4 +760,19 @@ mod tests { Err(ImpitError::Http3Disabled) )); } + + #[cfg(feature = "http3")] + #[tokio::test] + async fn http3_is_disabled_with_a_proxy() { + let impit = Impit::::builder() + .with_http3() + .with_proxy("http://127.0.0.1:9".to_string()) + .build() + .unwrap(); + assert!(impit.h3_client.is_none()); + assert!(matches!( + get_with_http3_prior_knowledge(&impit).await, + Err(ImpitError::Http3Disabled) + )); + } }