use std::future::{Future, poll_fn}; use std::time::Duration; use dever_runtime::{ async_stream::AsyncStream, bytes::Bytes, channel::Channel, http, net, task, tls, }; use tokio::sync::{mpsc, oneshot}; const ROOT: &[u8] = include_bytes!("../fixtures/tls/root.pem"); const CERT: &[u8] = include_bytes!("../fixtures/tls/server.pem"); const KEY: &[u8] = include_bytes!("../fixtures/tls/server-key.pem"); fn bytes(value: &[u8]) -> Bytes { Bytes::new(value.to_vec()) } fn limits(connections: i64, streams: i64) -> http::Limits { http::Limits { header_bytes: 8192, body_bytes: 65536, timeout_ms: 1000, connections, http2: Some(http::Http2Limits { streams, stream_window_bytes: 65535, connection_window_bytes: 65535, }), } } fn pool_limits() -> http::PoolLimits { http::PoolLimits { idle_ms: 1000, chunk_bytes: 4, read_ms: 500, } } fn request(target: &str) -> http::Request { http::Request { method: "GET".into(), target: target.into(), headers: vec![], body: bytes(b""), } } fn trust() -> tls::ClientTls { tls::client(&bytes(ROOT)).unwrap() } fn identity() -> tls::ServerTls { tls::server(&bytes(CERT), &bytes(KEY)).unwrap() } fn run(future: impl Future>) { task::run_entry_with( task::RuntimeConfig { worker_threads: 1, max_blocking_threads: 2, task_capacity: 64, }, async { tokio::time::timeout(Duration::from_secs(8), future) .await .map_err(|_| "HTTP/2 client test deadline exceeded".to_owned())? }, ) .unwrap(); } fn h2_error(error: impl std::fmt::Display) -> String { error.to_string() } async fn next_request( requests: &mut mpsc::Receiver<( hyper::Request, h2::server::SendResponse, )>, ) -> Result< ( hyper::Request, h2::server::SendResponse, ), String, > { requests .recv() .await .ok_or("HTTP/2 connection closed before request".into()) } fn response() -> hyper::Response<()> { hyper::Response::builder().status(200).body(()).unwrap() } async fn start_peer( socket: tokio::net::TcpStream, ) -> Result< ( mpsc::Receiver<( hyper::Request, h2::server::SendResponse, )>, oneshot::Sender>, task::Task<()>, ), String, > { let mut connection = h2::server::handshake(socket).await.map_err(h2_error)?; let (requests, receiver) = mpsc::channel(4); let (shutdown, mut shutdown_request) = oneshot::channel::>(); let driver = task::run(async move { let mut graceful = false; loop { let request = if graceful { connection.accept().await } else { tokio::select! { request = connection.accept() => request, command = &mut shutdown_request => { if let Ok(acknowledge) = command { connection.graceful_shutdown(); graceful = true; let _ = acknowledge.send(()); continue; } connection.accept().await } } }; let Some(request) = request else { return Ok(()); }; requests .send(request.map_err(h2_error)?) .await .map_err(|_| "HTTP/2 peer request receiver closed".to_owned())?; } }) .await?; Ok((receiver, shutdown, driver)) } #[test] fn one_physical_connection_multiplexes_upload_and_streaming_download() { run(async { let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)) .await .map_err(h2_error)?; let port = i64::from(listener.local_addr().map_err(h2_error)?.port()); let peer = task::run(async move { let (socket, _) = listener.accept().await.map_err(h2_error)?; let (mut requests, _shutdown, driver) = start_peer(socket).await?; let (first, mut first_reply) = next_request(&mut requests).await?; assert_eq!(first.method(), hyper::Method::POST); assert_eq!(first.uri().scheme_str(), Some("http")); let authority = format!("localhost:{port}"); assert_eq!( first.uri().authority().map(|value| value.as_str()), Some(authority.as_str()) ); assert_eq!(first.uri().path(), "/upload"); let mut upload = first.into_body(); let mut uploaded = Vec::new(); while let Some(chunk) = upload.data().await { let chunk = chunk.map_err(h2_error)?; uploaded.extend_from_slice(&chunk); } assert_eq!(uploaded, b"abcdefgh"); let mut first_body = first_reply .send_response(response(), false) .map_err(h2_error)?; first_body .send_data(bytes::Bytes::from_static(b"first"), false) .map_err(h2_error)?; let (second, mut second_reply) = next_request(&mut requests).await?; assert_eq!(second.uri().path(), "/second"); let mut second_body = second_reply .send_response(response(), false) .map_err(h2_error)?; second_body .send_data(bytes::Bytes::from_static(b"second"), true) .map_err(h2_error)?; first_body .send_data(bytes::Bytes::from_static(b"last"), true) .map_err(h2_error)?; task::wait(driver).await?; Ok(()) }) .await?; let client = http::client( &format!("http://localhost:{port}"), tls::system(), limits(1, 2), pool_limits(), ) .await?; let first = http::open_stream( &client, http::StreamRequest { method: "POST".into(), target: "/upload".into(), headers: vec![], body: AsyncStream::from_values([Ok(bytes(b"abcd")), Ok(bytes(b"efgh"))]), }, ) .await?; assert_eq!(first.body.pull().await.unwrap()?.values(), b"firs"); assert_eq!(first.body.pull().await.unwrap()?.values(), b"t"); assert_eq!( http::request(&client, request("/second")) .await? .body .values(), b"second" ); let mut tail = Vec::new(); while let Some(chunk) = first.body.pull().await { tail.extend_from_slice(chunk?.values()); } assert_eq!(tail, b"last"); http::close_client(&client).await?; task::wait(peer).await?; Ok(()) }); } #[test] fn canceling_one_response_resets_only_its_stream() { run(async { let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)) .await .map_err(h2_error)?; let port = i64::from(listener.local_addr().map_err(h2_error)?.port()); let peer = task::run(async move { let (socket, _) = listener.accept().await.map_err(h2_error)?; let (mut requests, _shutdown, driver) = start_peer(socket).await?; let (_, mut first_reply) = next_request(&mut requests).await?; let mut first_body = first_reply .send_response(response(), false) .map_err(h2_error)?; first_body .send_data(bytes::Bytes::from_static(b"open"), false) .map_err(h2_error)?; let (_, mut neighbor_reply) = next_request(&mut requests).await?; let mut neighbor_body = neighbor_reply .send_response(response(), false) .map_err(h2_error)?; neighbor_body .send_data(bytes::Bytes::from_static(b"neighbor"), true) .map_err(h2_error)?; let reason = poll_fn(|context| first_body.poll_reset(context)) .await .map_err(h2_error)?; assert_eq!(reason, h2::Reason::CANCEL); let (_, mut final_reply) = next_request(&mut requests).await?; let mut final_body = final_reply .send_response(response(), false) .map_err(h2_error)?; final_body .send_data(bytes::Bytes::from_static(b"still-open"), true) .map_err(h2_error)?; task::wait(driver).await?; Ok(()) }) .await?; let client = http::client( &format!("http://localhost:{port}"), tls::system(), limits(1, 2), pool_limits(), ) .await?; let first = http::open(&client, request("/cancel")).await?; assert_eq!(first.body.pull().await.unwrap()?.values(), b"open"); assert_eq!( http::request(&client, request("/neighbor")) .await? .body .values(), b"neighbor" ); first.body.close(); assert_eq!( http::request(&client, request("/after-reset")) .await? .body .values(), b"still-open" ); http::close_client(&client).await?; task::wait(peer).await?; Ok(()) }); } #[test] fn stream_limit_blocks_without_expiring_an_active_body_then_reaps_at_eof() { run(async { let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)) .await .map_err(h2_error)?; let port = i64::from(listener.local_addr().map_err(h2_error)?.port()); let finish_first = Channel::new(1)?; let finish = finish_first.clone(); let first_connection_closed = Channel::new(1)?; let closed = first_connection_closed.clone(); let peer = task::run(async move { let (first_socket, _) = listener.accept().await.map_err(h2_error)?; let (mut first_requests, _shutdown, first_driver) = start_peer(first_socket).await?; let (_, mut first_reply) = next_request(&mut first_requests).await?; let mut first_body = first_reply .send_response(response(), false) .map_err(h2_error)?; first_body .send_data(bytes::Bytes::from_static(b"open"), false) .map_err(h2_error)?; finish.receive().await?; assert!(matches!( first_requests.try_recv(), Err(mpsc::error::TryRecvError::Empty) )); first_body .send_data(bytes::Bytes::from_static(b"done"), true) .map_err(h2_error)?; task::wait(first_driver).await?; closed.send(()).await?; let (second_socket, _) = listener.accept().await.map_err(h2_error)?; let (mut second_requests, _shutdown, second_driver) = start_peer(second_socket).await?; let (_, mut second_reply) = next_request(&mut second_requests).await?; let mut second_body = second_reply .send_response(response(), false) .map_err(h2_error)?; second_body .send_data(bytes::Bytes::from_static(b"new"), true) .map_err(h2_error)?; task::wait(second_driver).await?; Ok(()) }) .await?; let mut budget = limits(1, 1); budget.timeout_ms = 60; let mut pool = pool_limits(); pool.idle_ms = 30; let client = http::client( &format!("http://localhost:{port}"), tls::system(), budget, pool, ) .await?; let first = http::open(&client, request("/active")).await?; assert_eq!(first.body.pull().await.unwrap()?.values(), b"open"); task::sleep(80).await?; assert!( http::request(&client, request("/blocked")) .await .unwrap_err() .contains("timed out") ); finish_first.send(()).await?; assert_eq!(first.body.pull().await.unwrap()?.values(), b"done"); assert!(first.body.pull().await.is_none()); first_connection_closed.receive().await?; assert_eq!( http::request(&client, request("/after-idle")) .await? .body .values(), b"new" ); http::close_client(&client).await?; task::wait(peer).await?; Ok(()) }); } #[test] fn goaway_replaces_the_connection_within_the_physical_budget() { run(async { let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)) .await .map_err(h2_error)?; let port = i64::from(listener.local_addr().map_err(h2_error)?.port()); let goaway = Channel::new(1)?; let goaway_sent = goaway.clone(); let finish_old_stream = Channel::new(1)?; let finish = finish_old_stream.clone(); let peer = task::run(async move { let (first_socket, _) = listener.accept().await.map_err(h2_error)?; let (mut first_requests, shutdown, first_driver) = start_peer(first_socket).await?; let (_, mut first_reply) = next_request(&mut first_requests).await?; let mut first_body = first_reply .send_response(response(), false) .map_err(h2_error)?; first_body .send_data(bytes::Bytes::from_static(b"open"), false) .map_err(h2_error)?; let (acknowledge, acknowledged) = oneshot::channel(); shutdown .send(acknowledge) .map_err(|_| "HTTP/2 peer driver closed before GOAWAY".to_owned())?; acknowledged .await .map_err(|_| "HTTP/2 peer did not begin GOAWAY".to_owned())?; task::sleep(100).await?; goaway_sent.send(()).await?; finish.receive().await?; assert!(matches!( first_requests.try_recv(), Err(mpsc::error::TryRecvError::Empty) )); first_body .send_data(bytes::Bytes::from_static(b"done"), true) .map_err(h2_error)?; task::wait(first_driver).await?; let (second_socket, _) = listener.accept().await.map_err(h2_error)?; let (mut second_requests, _shutdown, second_driver) = start_peer(second_socket).await?; let (_, mut second_reply) = next_request(&mut second_requests).await?; let mut second_body = second_reply .send_response(response(), false) .map_err(h2_error)?; second_body .send_data(bytes::Bytes::from_static(b"second"), true) .map_err(h2_error)?; task::wait(second_driver).await?; Ok(()) }) .await?; let mut budget = limits(1, 2); budget.timeout_ms = 80; let client = http::client( &format!("http://localhost:{port}"), tls::system(), budget, pool_limits(), ) .await?; let first = http::open(&client, request("/before-goaway")).await?; assert_eq!(first.body.pull().await.unwrap()?.values(), b"open"); goaway.receive().await?; let blocked = http::request(&client, request("/must-wait")) .await .unwrap_err(); assert!(blocked.contains("timed out"), "{blocked}"); finish_old_stream.send(()).await?; assert_eq!(first.body.pull().await.unwrap()?.values(), b"done"); assert!(first.body.pull().await.is_none()); assert_eq!( http::request(&client, request("/after-goaway")) .await? .body .values(), b"second" ); http::close_client(&client).await?; task::wait(peer).await?; Ok(()) }); } #[test] fn https_requires_h2_alpn_without_protocol_fallback() { run(async { let listener = net::listen("127.0.0.1", 0).await?; let port = net::port(&listener)?; let mut http1 = limits(1, 1); http1.http2 = None; let server_listener = listener.clone(); let server = task::run(async move { http::serve_tls( |_| async { Ok(http::Response { status: 200, headers: vec![], body: bytes(b"unexpected"), }) }, server_listener, http1, identity(), ) .await }) .await?; let client = http::client( &format!("https://localhost:{port}"), trust(), limits(1, 1), pool_limits(), ) .await?; let error = http::request(&client, request("/alpn")).await.unwrap_err(); assert!(error.contains("NoApplicationProtocol"), "{error}"); http::close_client(&client).await?; task::stop(server).await?; listener.close()?; Ok(()) }); } #[test] fn invalid_http2_budgets_and_outgoing_headers_fail_before_connecting() { run(async { let base = limits(1, 2); let mut cases = Vec::new(); for streams in [0, 65537] { let mut budget = base; budget.http2.as_mut().unwrap().streams = streams; cases.push((budget, "connections * streams")); } let mut multiplied = base; multiplied.connections = 32769; cases.push((multiplied, "connections * streams")); for window in [0, i64::from(i32::MAX) + 1] { let mut budget = base; budget.http2.as_mut().unwrap().stream_window_bytes = window; cases.push((budget, "stream_window_bytes")); } for window in [65534, i64::from(i32::MAX) + 1] { let mut budget = base; budget.http2.as_mut().unwrap().connection_window_bytes = window; cases.push((budget, "connection_window_bytes")); } for (budget, expected) in cases { let failure = http::client("http://127.0.0.1:1", tls::system(), budget, pool_limits()) .await .unwrap_err(); assert!(failure.contains(expected), "{failure}"); } let client = http::client("http://127.0.0.1:1", tls::system(), base, pool_limits()).await?; for (name, value) in [ ("Connection", "close"), ("Keep-Alive", "timeout=5"), ("TE", "gzip"), ] { let mut outgoing = request("/"); outgoing.headers.push(http::Header { name: name.into(), value: bytes(value.as_bytes()), }); let failure = http::request(&client, outgoing).await.unwrap_err(); assert!( failure.contains("HTTP/2 forbids connection-specific header"), "{failure}" ); } http::close_client(&client).await?; Ok(()) }); }