use super::*; use bytes::Bytes; use http_body_util::{BodyExt, Empty}; use hyper_util::rt::TokioIo; use std::sync::atomic::{AtomicUsize, Ordering}; use tokio::sync::Semaphore; mod lifecycle; mod performance; mod transport; async fn client( port: i64, scope: &Scope<'_>, ) -> ( hyper::client::conn::http1::SendRequest>, dever_runtime::task::Task<()>, ) { let socket = tokio::net::TcpStream::connect(("127.0.0.1", port as u16)) .await .unwrap(); let (sender, connection) = hyper::client::conn::http1::handshake(TokioIo::new(socket)) .await .unwrap(); let driver = scope .spawn_leaf(move || async move { let _ = connection.await; }) .unwrap(); (sender, driver) } fn message(method: &str) -> hyper::Request> { hyper::Request::builder() .method(method) .uri("/") .header("Host", "fixture.test") .body(Empty::new()) .unwrap() } struct Running(Arc); impl Drop for Running { fn drop(&mut self) { self.0.fetch_sub(1, Ordering::SeqCst); } } #[test] fn sse_encoding_preserves_lines_ids_and_exact_limit() { let event = http::Event { event: "change".into(), data: "一\r\n二\r\n\r".into(), id: Some("".into()), retry_ms: Some(0), }; let expected = "event: change\nid: \nretry: 0\ndata: 一\ndata: 二\ndata: \ndata: \n\n"; assert_eq!(event.encode(expected.len()).unwrap(), expected); assert!(event.encode(expected.len() - 1).is_err()); for event in [ http::Event { event: "bad\n".into(), ..event.clone() }, http::Event { id: Some("bad\0".into()), ..event.clone() }, http::Event { retry_ms: Some(-1), ..event.clone() }, ] { assert!(event.encode(1024).is_err()); } } #[test] fn first_chunk_arrives_before_handler_finishes_and_outlives_head_deadline() { 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 gate = Arc::new(Semaphore::new(0)); let waiting = gate.clone(); let server = scope .spawn(move |child| async move { let limits = Limits { timeout_ms: 100, ..Limits::default() }; http::serve_live( &incoming, &limits, &http::LiveLimits::default(), &child, move |_, reply, _| { let gate = waiting.clone(); async move { reply .start(200, vec![]) .await .map_err(|error| error.to_string())?; reply .write(b"first") .await .map_err(|error| error.to_string())?; let _permit = gate.acquire().await.unwrap(); reply .write(b"last") .await .map_err(|error| error.to_string()) } }, ) .await }) .unwrap(); let (mut sender, driver) = client(port, scope).await; let response = sender.send_request(message("GET")).await.unwrap(); assert_eq!(response.status(), 200); let mut body = response.into_body(); assert_eq!( body.frame().await.unwrap().unwrap().into_data().unwrap(), b"first"[..] ); tokio::time::sleep(Duration::from_millis(180)).await; gate.add_permits(1); assert_eq!(body.collect().await.unwrap().to_bytes(), b"last"[..]); drop(sender); driver.wait().await.unwrap(); listener.close().unwrap(); server.wait().await.unwrap().unwrap(); }) .unwrap(); } #[test] fn sse_heartbeats_keep_session_alive_and_stop_drains_handler() { runtime() .run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let incoming = listener.clone(); let running = Arc::new(AtomicUsize::new(0)); let active = running.clone(); let server = scope .spawn(move |child| async move { http::serve_live( &incoming, &Limits { timeout_ms: 80, ..Limits::default() }, &http::LiveLimits { chunk_bytes: 64, idle_ms: 100, heartbeat_ms: 20, }, &child, move |_, reply, _| { let active = active.clone(); async move { active.fetch_add(1, Ordering::SeqCst); let _running = Running(active); reply .sse_start(vec![]) .await .map_err(|error| error.to_string())?; std::future::pending::>().await } }, ) .await }) .unwrap(); let (mut sender, driver) = client(listener.port().unwrap(), scope).await; let response = sender.send_request(message("GET")).await.unwrap(); assert_eq!(response.headers()["content-type"], "text/event-stream"); assert_eq!(response.headers()["cache-control"], "no-cache"); let mut body = response.into_body(); for _ in 0..8 { assert_eq!( body.frame().await.unwrap().unwrap().into_data().unwrap(), b":\n\n"[..] ); } assert_eq!(running.load(Ordering::SeqCst), 1); server.stop().await.unwrap(); assert_eq!(running.load(Ordering::SeqCst), 0); drop(body); drop(sender); driver.wait().await.unwrap(); listener.close().unwrap(); }) .unwrap(); } #[test] fn invalid_reply_operations_preserve_started_response_and_close_aliases() { runtime() .run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let incoming = listener.clone(); let (sent, mut saved) = tokio::sync::mpsc::channel(1); let server = scope .spawn(move |child| async move { http::serve_live( &incoming, &Limits::default(), &http::LiveLimits { chunk_bytes: 32, ..http::LiveLimits::default() }, &child, move |_, reply, _| { let sent = sent.clone(); async move { assert!(reply.write(b"before").await.is_err()); assert!(reply.start(204, vec![]).await.is_err()); reply.sse_start(vec![]).await.unwrap(); assert!(reply.start(200, vec![]).await.is_err()); assert!(reply.write(b"ordinary").await.is_err()); assert!( reply .sse_send(&http::Event { event: "".into(), data: "x".repeat(33), id: None, retry_ms: None }) .await .is_err() ); reply .sse_send(&http::Event { event: "".into(), data: "ok".into(), id: None, retry_ms: None, }) .await .unwrap(); reply.finish().await.unwrap(); assert!(reply.finish().await.is_err()); assert!( reply .sse_send(&http::Event { event: "".into(), data: "late".into(), id: None, retry_ms: None, }) .await .is_err() ); sent.send(reply).await.unwrap(); Ok(()) } }, ) .await }) .unwrap(); let (mut sender, driver) = client(listener.port().unwrap(), scope).await; let response = sender.send_request(message("GET")).await.unwrap(); assert_eq!( response.into_body().collect().await.unwrap().to_bytes(), b"data: ok\n\n"[..] ); let alias = saved.recv().await.unwrap(); assert!(alias.start(200, vec![]).await.is_err()); drop(sender); driver.wait().await.unwrap(); listener.close().unwrap(); server.wait().await.unwrap().unwrap(); }) .unwrap(); }