{"record":{"id":"23cfb8016bdd1fc9","repo":"seanmonstar/reqwest","slug":"http-3-only-supports-https-or-h3-schemes-got","errorCode":null,"errorMessage":"HTTP/3 only supports 'https' or 'h3' schemes, got: {}","messagePattern":"HTTP/3 only supports 'https' or 'h3' schemes, got: (.+?)","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"src/async_impl/h3_client/pool.rs","lineNumber":370,"sourceCode":"        }\n    }\n\n    fn size_hint(&self) -> hyper::body::SizeHint {\n        if let Some(content_length) = self.content_length {\n            hyper::body::SizeHint::with_exact(content_length)\n        } else {\n            hyper::body::SizeHint::default()\n        }\n    }\n}\n\npub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {\n    let uri_clone = uri.clone();\n    match (uri_clone.scheme(), uri_clone.authority()) {\n        (Some(scheme), Some(auth)) => {\n            let scheme_str = scheme.as_str();\n            if scheme_str != \"https\" && scheme_str != \"h3\" {\n                return Err(Error::new(\n                    Kind::Request,\n                    Some(Box::new(std::io::Error::new(\n                        std::io::ErrorKind::InvalidInput,\n                        format!(\n                            \"HTTP/3 only supports 'https' or 'h3' schemes, got: {}\",\n                            scheme_str\n                        ),\n                    ))),\n                ));\n            }\n            Ok((scheme.clone(), auth.clone()))\n        }\n        _ => Err(Error::new(Kind::Request, None::<Error>)),\n    }\n}\n\npub(crate) fn domain_as_uri((scheme, auth): Key) -> Uri {\n    http::uri::Builder::new()","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/seanmonstar/reqwest/blob/9f06fd28abe53e5ff84a091825ea5ce8984b51e0/src/async_impl/h3_client/pool.rs#L352-L388","documentation":"Thrown by extract_domain() in the HTTP/3 connection pool (src/async_impl/h3_client/pool.rs:364-385) when a request routed to the h3 client carries a URI whose scheme is neither 'https' nor 'h3'. HTTP/3 runs over QUIC, which requires TLS 1.3, so plaintext 'http' URIs are fundamentally invalid for the h3 transport. The guard fires before any connection is opened and the offending scheme string is interpolated into the message.","triggerScenarios":"Building a Client with the 'http3' feature and forcing .version(http::Version::HTTP_3) on a RequestBuilder pointed at an 'http://' URL. Also occurs when HttpVersionPref::Http3 / .http3_prior_config() is set but the request URL is plaintext http. The check at pool.rs:369 compares scheme.as_str() strictly to 'https' or 'h3', so any other scheme (http, ws, ftp) on a request dispatched to the h3 client triggers it.","commonSituations":"Mixing .version(http::Version::HTTP_3) with a non-TLS endpoint during local testing against 'http://localhost'. Misconfigured base URLs in env vars (HTTP where HTTPS is required). A load balancer terminating TLS and forwarding plain http URLs while the client is pinned to HTTP/3. Feature-flag mismatch: enabling 'http3' but targeting servers that only serve HTTP/1.1 or h2 cleartext.","solutions":["Ensure the request URL uses the 'https://' scheme when forcing HTTP/3: replace 'http://host' with 'https://host' before calling .version(http::Version::HTTP_3).","Remove the forced .version(http::Version::HTTP_3) and let reqwest negotiate the protocol; only the server's ALPN-advertised h3 over QUIC will actually use HTTP/3.","If you must use a custom scheme internally, register an 'h3' URI scheme and pass an 'h3://host' URL, since pool.rs:369 explicitly accepts 'h3'.","Add a runtime guard that asserts url.scheme() == \"https\" before constructing an HTTP/3 request so the failure surfaces at call site."],"exampleFix":"// before\nlet resp = client\n    .get(\"http://api.example.com/data\")\n    .version(http::Version::HTTP_3)\n    .send()\n    .await?;\n\n// after\nlet resp = client\n    .get(\"https://api.example.com/data\")\n    .version(http::Version::HTTP_3)\n    .send()\n    .await?;","handlingStrategy":"validation","validationCode":"fn assert_h3_compatible(url: &url::Url) -> Result<(), &'static str> {\n    match url.scheme() {\n        \"https\" | \"h3\" => Ok(()),\n        other => Err(\"HTTP/3 requires an https:// or h3:// URL\"),\n    }\n}\n\n// call before .version(http::Version::HTTP_3)\nlet url = url::Url::parse(&raw_url)?;\nassert_h3_compatible(&url)?;","typeGuard":"fn is_h3_scheme(url: &url::Url) -> bool {\n    matches!(url.scheme(), \"https\" | \"h3\")\n}","tryCatchPattern":"// reqwest::Error has no public kind() for this; match on the Display source.\nif let Err(e) = resp {\n    if e.to_string().contains(\"HTTP/3 only supports\") {\n        // downgrade to HTTP/2/1.1 over TLS and retry\n    } else {\n        return Err(e);\n    }\n}","preventionTips":["Centralize HTTP/3 request construction in one helper that enforces https:// and never accepts raw strings.","Wire a unit test that asserts every .version(HTTP_3) call site targets an https URL.","When enabling the http3 feature, document that all forced-HTTP/3 endpoints must be TLS endpoints."],"tags":["http3","scheme","tls","quic","url"],"backgroundTag":null,"analyzedSha":"9f06fd28abe53e5ff84a091825ea5ce8984b51e0","analyzedAt":"2026-08-10T17:01:13.368Z","contentChangedAt":"2026-08-10T17:01:13.368Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}