use super::*; use http_body_util::{BodyExt, Full}; use hyper_util::rt::{TokioExecutor, TokioIo}; use tokio::sync::Notify; fn limits() -> Limits { Limits { http2: Some(http::Http2Limits::default()), ..Limits::default() } } #[test] fn idle_http2_connection_releases_capacity_after_response() { runtime() .run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let port = listener.port().unwrap(); let incoming = listener.clone(); let limits = Limits { connections: 1, timeout_ms: 50, ..limits() }; let serving = limits.clone(); let server = scope .spawn(move |child| async move { http::serve::<_, _, false>(&incoming, &serving, &child, route).await }) .unwrap(); let stream = tokio::net::TcpStream::connect(("127.0.0.1", port as u16)) .await .unwrap(); let (mut sender, connection) = hyper::client::conn::http2::Builder::new(TokioExecutor::new()) .handshake::<_, Full>(TokioIo::new(stream)) .await .unwrap(); let driver = scope.spawn_leaf(move || connection).unwrap(); let response = sender .send_request( hyper::Request::builder() .uri("http://fixture.test/echo") .body(Full::new(bytes::Bytes::from_static(b"first"))) .unwrap(), ) .await .unwrap(); assert_eq!( response.into_body().collect().await.unwrap().to_bytes(), b"first"[..] ); tokio::time::timeout(Duration::from_secs(2), driver.wait()) .await .unwrap() .unwrap() .unwrap(); assert_eq!( http::send("127.0.0.1", port, request("/echo", b"second"), &limits) .await .unwrap() .body .as_ref(), b"second" ); listener.close().unwrap(); server.wait().await.unwrap().unwrap(); }) .unwrap(); } #[test] fn http2_single_request_preserves_body_headers_and_validates_protocol_options() { runtime() .run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let port = listener.port().unwrap(); let incoming = listener.clone(); let server = scope .spawn(move |child| async move { http::serve::<_, _, false>(&incoming, &limits(), &child, route).await }) .unwrap(); let response = http::send("127.0.0.1", port, request("/echo", b"http2"), &limits()) .await .unwrap(); assert_eq!(response.body.as_ref(), b"http2"); let headers = response .headers .iter() .filter(|header| header.name == "x-data") .collect::>(); assert_eq!(headers.len(), 2); assert_eq!(headers[0].value.as_ref(), [0x80]); for header in ["Connection", "Keep-Alive", "Proxy-Connection", "TE"] { let mut message = request("/", b""); message.headers.push(Header { name: header.into(), value: b"invalid".to_vec().into(), }); assert!( http::send("127.0.0.1", port, message, &limits()) .await .unwrap_err() .contains("connection header") ); } listener.close().unwrap(); server.wait().await.unwrap().unwrap(); }) .unwrap(); } #[test] fn http2_multiplexes_independent_streams_normalizes_authority_and_drains_goaway() { for threads in [1, 2] { let runtime = Runtime::new(dever_runtime::task::Limits { threads, workers: 2, tasks: 32, }) .unwrap(); runtime.run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let port = listener.port().unwrap(); let incoming = listener.clone(); let started = Arc::new(Notify::new()); let ready = started.clone(); let release = Arc::new(Notify::new()); let released = release.clone(); let server = scope.spawn(move |child| async move { http::serve::<_, _, false>(&incoming, &limits(), &child, move |request, _| { let ready = ready.clone(); let released = released.clone(); async move { assert!(request.headers.iter().any(|header| header.name == "host" && header.value.as_ref() == b"fixture.test")); if request.target == "/slow" { ready.notify_one(); released.notified().await; } Ok(Response { status: 200, headers: vec![], body: request.target.into_bytes().into() }) } }).await }).unwrap(); let stream = tokio::net::TcpStream::connect(("127.0.0.1", port as u16)).await.unwrap(); let (mut sender, connection) = hyper::client::conn::http2::Builder::new(TokioExecutor::new()) .handshake::<_, Full>(TokioIo::new(stream)).await.unwrap(); let driver = scope.spawn_leaf(move || connection).unwrap(); let message = |path: &str| hyper::Request::builder().uri(format!("http://fixture.test{path}")) .body(Full::new(bytes::Bytes::new())).unwrap(); let slow = sender.send_request(message("/slow")); tokio::pin!(slow); tokio::select! { result = &mut slow => panic!("slow stream finished before release: {result:?}"), () = started.notified() => (), () = tokio::time::sleep(Duration::from_secs(3)) => panic!("slow stream did not start"), } let fast = tokio::time::timeout(Duration::from_secs(3), sender.send_request(message("/fast"))).await.unwrap().unwrap(); assert_eq!(fast.into_body().collect().await.unwrap().to_bytes(), b"/fast"[..]); listener.close().unwrap(); release.notify_one(); let slow = slow.await.unwrap(); assert_eq!(slow.into_body().collect().await.unwrap().to_bytes(), b"/slow"[..]); drop(sender); tokio::time::timeout(Duration::from_secs(3), server.wait()).await.unwrap().unwrap().unwrap(); driver.stop().await.unwrap(); }).unwrap(); } } #[test] fn http2_limits_reject_invalid_windows_and_total_stream_capacity() { runtime() .run(async |_| { for http2 in [ http::Http2Limits { streams: 2049, ..http::Http2Limits::default() }, http::Http2Limits { streams: 0, ..http::Http2Limits::default() }, http::Http2Limits { stream_window_bytes: 0, ..http::Http2Limits::default() }, http::Http2Limits { connection_window_bytes: 65534, ..http::Http2Limits::default() }, ] { let limits = Limits { http2: Some(http2), ..Limits::default() }; assert!( http::send("127.0.0.1", 1, request("/", b""), &limits) .await .unwrap_err() .contains("HTTP/2 limits") ); } }) .unwrap(); }