#![cfg(feature = "http3")]
#![cfg(not(target_arch = "wasm32"))]
mod support;
use http::header::CONTENT_LENGTH;
use std::error::Error;
use support::server;
fn assert_send_sync<T: Send + Sync>(_: &T) {}
#[tokio::test]
async fn http3_request_full() {
use http_body_util::BodyExt;
let server = server::Http3::new().build(move |req| async move {
assert_eq!(req.headers()[CONTENT_LENGTH], "5");
let reqb = req.collect().await.unwrap().to_bytes();
assert_eq!(reqb, "hello");
http::Response::default()
});
let url = format!("https://{}/content-length", server.addr());
let res_fut = reqwest::Client::builder()
.http3_prior_knowledge()
.danger_accept_invalid_certs(true)
.build()
.expect("client builder")
.post(url)
.version(http::Version::HTTP_3)
.body("hello")
.send();
assert_send_sync(&res_fut);
let res = res_fut.await.expect("request");
assert_eq!(res.version(), http::Version::HTTP_3);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
async fn find_free_tcp_addr() -> std::net::SocketAddr {
let listener = tokio::net::TcpListener::bind("[::1]:0").await.unwrap();
listener.local_addr().unwrap()
}
#[cfg(feature = "http3")]
#[tokio::test]
async fn http3_test_failed_connection() {
let addr = find_free_tcp_addr().await;
let port = addr.port();
let url = format!("https://[::1]:{port}/");
let client = reqwest::Client::builder()
.http3_prior_knowledge()
.danger_accept_invalid_certs(true)
.http3_max_idle_timeout(std::time::Duration::from_millis(20))
.build()
.expect("client builder");
let err = client
.get(&url)
.version(http::Version::HTTP_3)
.send()
.await
.unwrap_err();
let err = err
.source()
.unwrap()
.source()
.unwrap()
.downcast_ref::<quinn::ConnectionError>()
.unwrap();
assert_eq!(*err, quinn::ConnectionError::TimedOut);
let err = client
.get(&url)
.version(http::Version::HTTP_3)
.send()
.await
.unwrap_err();
let err = err
.source()
.unwrap()
.source()
.unwrap()
.downcast_ref::<quinn::ConnectionError>()
.unwrap();
assert_eq!(*err, quinn::ConnectionError::TimedOut);
let server = server::Http3::new()
.with_addr(addr)
.build(|_| async { http::Response::default() });
let res = client
.post(&url)
.version(http::Version::HTTP_3)
.body("hello")
.send()
.await
.expect("request");
assert_eq!(res.version(), http::Version::HTTP_3);
assert_eq!(res.status(), reqwest::StatusCode::OK);
drop(server);
}
#[cfg(feature = "http3")]
#[tokio::test]
async fn http3_test_concurrent_request() {
let server = server::Http3::new().build(|req| async move {
let mut res = http::Response::default();
*res.body_mut() = reqwest::Body::from(format!("hello {}", req.uri().path()));
res
});
let addr = server.addr();
let client = reqwest::Client::builder()
.http3_prior_knowledge()
.danger_accept_invalid_certs(true)
.http3_max_idle_timeout(std::time::Duration::from_millis(20))
.build()
.expect("client builder");
let mut tasks = vec![];
for i in 0..10 {
let client = client.clone();
tasks.push(async move {
let url = format!("https://{}/{}", addr, i);
client
.post(&url)
.version(http::Version::HTTP_3)
.send()
.await
.expect("request")
});
}
let handlers = tasks.into_iter().map(tokio::spawn).collect::<Vec<_>>();
for (i, handler) in handlers.into_iter().enumerate() {
let result = handler.await.unwrap();
assert_eq!(result.version(), http::Version::HTTP_3);
assert_eq!(result.status(), reqwest::StatusCode::OK);
let body = result.text().await.unwrap();
assert_eq!(body, format!("hello /{}", i));
}
drop(server);
}
#[cfg(feature = "http3")]
#[tokio::test]
async fn http3_test_reconnection() {
use std::error::Error;
use h3::error::{ConnectionError, StreamError};
let server = server::Http3::new().build(|_| async { http::Response::default() });
let addr = server.addr();
let url = format!("https://{}/", addr);
let client = reqwest::Client::builder()
.http3_prior_knowledge()
.danger_accept_invalid_certs(true)
.http3_max_idle_timeout(std::time::Duration::from_millis(20))
.build()
.expect("client builder");
let res = client
.post(&url)
.version(http::Version::HTTP_3)
.send()
.await
.expect("request");
assert_eq!(res.version(), http::Version::HTTP_3);
assert_eq!(res.status(), reqwest::StatusCode::OK);
drop(server);
let err = client
.get(&url)
.version(http::Version::HTTP_3)
.send()
.await
.unwrap_err();
let err = err
.source()
.unwrap()
.source()
.unwrap()
.downcast_ref::<StreamError>()
.unwrap();
assert!(matches!(
err,
StreamError::ConnectionError {
0: ConnectionError::Timeout { .. },
..
}
));
let server = server::Http3::new()
.with_addr(addr)
.build(|_| async { http::Response::default() });
let res = client
.post(&url)
.version(http::Version::HTTP_3)
.body("hello")
.send()
.await
.expect("request");
assert_eq!(res.version(), http::Version::HTTP_3);
assert_eq!(res.status(), reqwest::StatusCode::OK);
drop(server);
}
#[cfg(all(feature = "http3", feature = "stream"))]
#[tokio::test]
async fn http3_request_stream() {
use http_body_util::BodyExt;
let server = server::Http3::new().build(move |req| async move {
let reqb = req.collect().await.unwrap().to_bytes();
assert_eq!(reqb, "hello world");
http::Response::default()
});
let url = format!("https://{}", server.addr());
let body = reqwest::Body::wrap_stream(futures_util::stream::iter(vec![
Ok::<_, std::convert::Infallible>("hello"),
Ok::<_, std::convert::Infallible>(" "),
Ok::<_, std::convert::Infallible>("world"),
]));
let res = reqwest::Client::builder()
.http3_prior_knowledge()
.danger_accept_invalid_certs(true)
.build()
.expect("client builder")
.post(url)
.version(http::Version::HTTP_3)
.body(body)
.send()
.await
.expect("request");
assert_eq!(res.version(), http::Version::HTTP_3);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}
#[cfg(all(feature = "http3", feature = "stream"))]
#[tokio::test]
async fn http3_request_stream_error() {
use http_body_util::BodyExt;
let server = server::Http3::new().build(move |req| async move {
let _ = req.collect().await;
http::Response::default()
});
let url = format!("https://{}", server.addr());
let body = reqwest::Body::wrap_stream(futures_util::stream::iter(vec![
Ok::<_, std::io::Error>("first chunk"),
Err::<_, std::io::Error>(std::io::Error::other("oh no!")),
]));
let res = reqwest::Client::builder()
.http3_prior_knowledge()
.danger_accept_invalid_certs(true)
.build()
.expect("client builder")
.post(url)
.version(http::Version::HTTP_3)
.body(body)
.send()
.await;
let err = res.unwrap_err();
assert!(err.is_request());
let err = err
.source()
.unwrap()
.source()
.unwrap()
.downcast_ref::<reqwest::Error>()
.unwrap();
assert!(err.is_body());
}