use std::future::Future; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::time::Duration; use dever_runtime::{bytes::Bytes, channel::Channel, http, net, task}; fn limits() -> http::Limits { http::Limits { header_bytes: 8192, body_bytes: 1024, timeout_ms: 1000, connections: 4, http2: None, } } fn run_test(test: impl Future>) { task::run_entry_with( task::RuntimeConfig { worker_threads: 1, max_blocking_threads: 2, task_capacity: 32, }, async { tokio::time::timeout(Duration::from_secs(8), test) .await .map_err(|_| "HTTP test deadline exceeded".to_owned())? }, ) .unwrap(); } fn header(name: &str, value: &str) -> http::Header { http::Header { name: name.into(), value: Bytes::from_text(value), } } fn request() -> http::Request { http::Request { method: "GET".into(), target: "/".into(), headers: vec![header("host", "test")], body: Bytes::new(vec![]), } } fn response(body: Bytes) -> http::Response { http::Response { status: 200, headers: vec![], body, } } async fn server( route: F, limits: http::Limits, ) -> Result<(task::Task<()>, net::Listener, i64), String> where F: Fn(http::Request) -> Fut + Clone + Send + Sync + 'static, Fut: Future> + Send + 'static, { let listener = net::listen("127.0.0.1", 0).await?; let port = net::port(&listener)?; let owned = listener.clone(); let task = task::run(async move { http::serve(route, owned, limits).await }).await?; Ok((task, listener, port)) } // 独立、只读的测试线协议观察器:按 Content-Length 保留下一条响应,不调用 Hyper。 struct Peer { socket: net::Socket, buffered: Vec, } impl Peer { async fn connect(port: i64) -> Result { let socket = net::connect_timeout("127.0.0.1", port, 2000).await?; net::timeout(&socket, 2000)?; Ok(Self { socket, buffered: vec![], }) } async fn write(&self, wire: &[u8]) -> Result<(), String> { net::write(&self.socket, &Bytes::new(wire.to_vec())).await } async fn read_more(&mut self) -> Result<(), String> { let bytes = net::read(&self.socket, 4096) .await? .ok_or("unexpected HTTP EOF")?; self.buffered.extend_from_slice(bytes.values()); Ok(()) } async fn response(&mut self, head_only: bool) -> Result<(String, Vec), String> { let end = loop { if let Some(end) = self .buffered .windows(4) .position(|part| part == b"\r\n\r\n") { break end + 4; } self.read_more().await?; }; let headers = String::from_utf8(self.buffered[..end].to_vec()).unwrap(); let length = if head_only { 0 } else { headers .lines() .find_map(|line| { let (name, value) = line.split_once(':')?; name.eq_ignore_ascii_case("content-length") .then(|| value.trim().parse::().unwrap()) }) .unwrap_or(0) }; while self.buffered.len() < end + length { self.read_more().await?; } let body = self.buffered[end..end + length].to_vec(); self.buffered.drain(..end + length); Ok((headers.to_ascii_lowercase(), body)) } } #[test] fn persistent_pipeline_preserves_chunked_binary_body_and_duplicate_headers() { run_test(async { let (server, listener, port) = server( |request| async move { if request.target == "/first" { assert_eq!(request.body.values(), &[0, 255, b'x']); let values: Vec<_> = request .headers .iter() .filter(|header| header.name == "x-many") .map(|header| header.value.values()) .collect(); assert_eq!(values, [b"one".as_slice(), &[0x80]]); } Ok(http::Response { status: 200, headers: vec![header("set-cookie", "a=1"), header("set-cookie", "b=2")], body: request.body, }) }, limits(), ) .await?; let mut peer = Peer::connect(port).await?; peer.write(b"POST /first HTTP/1.1\r\nHost: test\r\nX-Many: one\r\nX-Many: \x80\r\nTransfer-Encoding: chunked\r\n\r\n2\r\n\0\xff\r\n1\r\nx\r\n0\r\n\r\nPOST /next HTTP/1.1\r\nHost: test\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok").await?; let (headers, body) = peer.response(false).await?; assert!(headers.starts_with("http/1.1 200")); assert_eq!(headers.matches("set-cookie:").count(), 2); assert_eq!(body, [0, 255, b'x']); assert_eq!(peer.response(false).await?.1, b"ok"); assert_eq!(net::read(&peer.socket, 1).await?, None); task::stop(server).await?; listener.close()?; Ok(()) }); } #[test] fn head_and_no_content_responses_keep_next_message_aligned() { run_test(async { let (server, listener, port) = server( |request| async move { let mut result = response(Bytes::from_text("head representation")); if request.target == "/empty" { result.status = 204; result.body = Bytes::new(vec![]); } if request.target == "/reset" { result.status = 205; result.body = Bytes::new(vec![]); } if request.target == "/cached" { result.status = 304; result.body = Bytes::new(vec![]); } Ok(result) }, limits(), ) .await?; let mut peer = Peer::connect(port).await?; for (wire, status, head) in [ ( b"HEAD / HTTP/1.1\r\nHost: test\r\n\r\n".as_slice(), "200", true, ), (b"GET /empty HTTP/1.1\r\nHost: test\r\n\r\n", "204", false), (b"GET /reset HTTP/1.1\r\nHost: test\r\n\r\n", "205", false), (b"GET /cached HTTP/1.1\r\nHost: test\r\n\r\n", "304", false), ] { peer.write(wire).await?; let (headers, body) = peer.response(head).await?; assert!( headers.starts_with(&format!("http/1.1 {status}")), "{headers}" ); assert!(body.is_empty()); } task::stop(server).await?; listener.close()?; Ok(()) }); } #[test] fn invalid_peers_and_limits_do_not_reach_the_handler_or_stop_the_server() { run_test(async { let calls = Arc::new(AtomicUsize::new(0)); let observed = calls.clone(); let mut budget = limits(); budget.body_bytes = 4; let (server, listener, port) = server( move |_| { observed.fetch_add(1, Ordering::SeqCst); async { Ok(response(Bytes::new(vec![]))) } }, budget, ) .await?; let oversized = format!( "GET / HTTP/1.1\r\nHost: test\r\nX-Large: {}\r\n\r\n", "a".repeat(9000) ); for (wire, status) in [ (b"POST / HTTP/1.1\r\nHost: test\r\nContent-Length: 1\r\nContent-Length: 2\r\n\r\nx".as_slice(), "400"), (b"GET / HTTP/1.1\r\nHost: a\r\nHost: b\r\n\r\n", "400"), (b"POST / HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nabcde\r\n0\r\n\r\n", "413"), (b"POST / HTTP/1.1\r\nHost: test\r\nContent-Length: 5\r\n\r\n", "413"), (b"POST / HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nX-Trailer: yes\r\n\r\n", "501"), (b"CONNECT test:80 HTTP/1.1\r\nHost: test\r\n\r\n", "501"), (b"GET / HTTP/1.1\r\nHost: test\r\nConnection: upgrade\r\nUpgrade: websocket\r\n\r\n", "501"), (oversized.as_bytes(), "431"), ] { let mut peer = Peer::connect(port).await?; peer.write(wire).await?; let (headers, _) = peer.response(false).await?; assert!(headers.starts_with(&format!("http/1.1 {status}")), "{headers}"); } assert_eq!(calls.load(Ordering::SeqCst), 0); assert_eq!( http::send("127.0.0.1", port, request(), limits()) .await? .status, 200 ); assert_eq!(calls.load(Ordering::SeqCst), 1); task::stop(server).await?; listener.close()?; Ok(()) }); } struct Dropped(Arc); impl Drop for Dropped { fn drop(&mut self) { self.0.store(true, Ordering::SeqCst); } } #[test] fn stop_closes_active_connections_and_drains_handler_descendants() { run_test(async { let entered = Channel::new(1)?; let notify = entered.clone(); let dropped = Arc::new(AtomicBool::new(false)); let observed = dropped.clone(); let (server, listener, port) = server( move |_| { let notify = notify.clone(); let marker = Dropped(observed.clone()); async move { let child = task::run(async move { let _marker = marker; notify.send(()).await?; std::future::pending::<()>().await; Ok(()) }) .await?; task::wait(child).await?; Ok(response(Bytes::new(vec![]))) } }, limits(), ) .await?; let peer = Peer::connect(port).await?; peer.write(b"GET / HTTP/1.1\r\nHost: test\r\n\r\n").await?; entered.receive().await?; task::stop(server).await?; assert!(dropped.load(Ordering::SeqCst)); assert_eq!(net::read(&peer.socket, 1).await?, None); // serve 只释放自己的 listener 别名,调用者仍可决定资源生命周期。 assert_eq!(net::port(&listener)?, port); listener.close()?; Ok(()) }); } #[test] fn handler_fault_returns_500_and_keeps_the_accept_loop_running() { run_test(async { let (server, listener, port) = server( |request| async move { if request.target == "/fault" { return Err("route fault".into()); } Ok(response(Bytes::from_text("healthy"))) }, limits(), ) .await?; let mut peer = Peer::connect(port).await?; peer.write(b"GET /fault HTTP/1.1\r\nHost: test\r\n\r\n") .await?; let (headers, body) = peer.response(false).await?; assert!(headers.starts_with("http/1.1 500"), "{headers}"); assert!(body.is_empty()); let healthy = http::send("127.0.0.1", port, request(), limits()).await?; assert_eq!(healthy.status, 200); assert_eq!(healthy.body.values(), b"healthy"); task::stop(server).await?; listener.close()?; Ok(()) }); } #[test] fn connection_capacity_backpressures_before_dispatching_another_request() { run_test(async { let entered = Channel::new(2)?; let release = Channel::new(2)?; let notify = entered.clone(); let wait = release.clone(); let calls = Arc::new(AtomicUsize::new(0)); let observed = calls.clone(); let mut budget = limits(); budget.connections = 1; let (server, listener, port) = server( move |_| { let notify = notify.clone(); let wait = wait.clone(); let observed = observed.clone(); async move { observed.fetch_add(1, Ordering::SeqCst); notify.send(()).await?; wait.receive().await?; Ok(response(Bytes::new(vec![]))) } }, budget, ) .await?; let mut first = Peer::connect(port).await?; first .write(b"GET / HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n") .await?; entered.receive().await?; let mut second = Peer::connect(port).await?; second .write(b"GET / HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n") .await?; tokio::task::yield_now().await; assert_eq!(calls.load(Ordering::SeqCst), 1); release.send(()).await?; first.response(false).await?; entered.receive().await?; assert_eq!(calls.load(Ordering::SeqCst), 2); release.send(()).await?; second.response(false).await?; task::stop(server).await?; listener.close()?; Ok(()) }); } #[test] fn header_body_and_handler_deadlines_release_the_connection() { run_test(async { let mut budget = limits(); budget.timeout_ms = 100; let (server, listener, port) = server( |_| async { std::future::pending::<()>().await; Ok(response(Bytes::new(vec![]))) }, budget, ) .await?; let peer = Peer::connect(port).await?; peer.write(b"GET / HTTP/1.1\r\nHost:").await?; // Hyper 对未完成的请求头超时直接关闭连接,没有可交给 handler 的请求。 assert_eq!(net::read(&peer.socket, 1).await?, None); for (wire, expected) in [ ( b"POST / HTTP/1.1\r\nHost: test\r\nContent-Length: 2\r\n\r\nx".as_slice(), "408", ), (b"GET / HTTP/1.1\r\nHost: test\r\n\r\n", "504"), ] { let mut peer = Peer::connect(port).await?; peer.write(wire).await?; let (headers, _) = peer.response(false).await?; assert!( headers.starts_with(&format!("http/1.1 {expected}")), "{headers}" ); assert_eq!(net::read(&peer.socket, 1).await?, None); } task::stop(server).await?; listener.close()?; Ok(()) }); } #[test] fn client_accepts_chunked_and_eof_bodies_but_rejects_truncation_and_oversize() { run_test(async { for (wire, expected) in [ (b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nSet-Cookie: a=1\r\nSet-Cookie: b=2\r\n\r\n2\r\nok\r\n0\r\n\r\n".as_slice(), true), (b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nok", true), (b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nok", false), (b"HTTP/1.1 200 OK\r\nContent-Length: 2048\r\n\r\n", false), ] { let listener = net::listen("127.0.0.1", 0).await?; let port = net::port(&listener)?; let peer = task::run(async move { let socket = net::accept(&listener).await?; net::timeout(&socket, 1000)?; let sent = net::read(&socket, 4096).await?.unwrap().to_text()?; assert!(sent.starts_with("GET / HTTP/1.1\r\n")); assert!(sent.contains("host: test\r\n")); for piece in wire.chunks(7) { net::write(&socket, &Bytes::new(piece.to_vec())).await?; tokio::task::yield_now().await; } Ok(()) }).await?; let received = http::send("127.0.0.1", port, request(), limits()).await; if expected { assert_eq!(received?.body.values(), b"ok"); } else { assert!(received.is_err()); } task::wait(peer).await?; } Ok(()) }); } #[test] fn invalid_outgoing_messages_and_configuration_fail_before_connecting() { run_test(async { for invalid in [ header("x-test", "ok\r\ninjected: yes"), header("content-length", "0"), header("transfer-encoding", "chunked"), ] { let mut request = request(); request.headers.push(invalid); let error = http::send("127.0.0.1", 0, request, limits()) .await .unwrap_err(); assert!(!error.contains("Connection refused"), "{error}"); } let mut excessive = request(); excessive.headers.extend(vec![header("x", ""); 32769]); assert_eq!( http::send("127.0.0.1", 0, excessive, limits()) .await .unwrap_err(), "HTTP headers exceed header_bytes" ); for budget in [ http::Limits { header_bytes: 1, ..limits() }, http::Limits { body_bytes: -1, ..limits() }, http::Limits { timeout_ms: 0, ..limits() }, http::Limits { connections: 0, ..limits() }, ] { assert!( http::send("127.0.0.1", 0, request(), budget) .await .unwrap_err() .starts_with("HTTP") ); } Ok(()) }); } #[test] fn invalid_handler_responses_return_500_and_keep_the_accept_loop_running() { run_test(async { for status in [99, 204, 205, 304, 600] { let (server, listener, port) = server( move |request| async move { if request.target != "/invalid" { return Ok(response(Bytes::from_text("healthy"))); } Ok(http::Response { status, headers: vec![], body: Bytes::from_text("invalid body"), }) }, limits(), ) .await?; let mut peer = Peer::connect(port).await?; peer.write(b"GET /invalid HTTP/1.1\r\nHost: test\r\n\r\n") .await?; let (headers, body) = peer.response(false).await?; assert_eq!(headers.split_whitespace().nth(1), Some("500"), "{headers}"); assert!(headers.lines().any(|line| line == "connection: close")); assert!(body.is_empty()); assert!(peer.buffered.is_empty()); assert_eq!(net::read(&peer.socket, 1).await?, None); let healthy = http::send("127.0.0.1", port, request(), limits()).await?; assert_eq!(healthy.status, 200); assert_eq!(healthy.body.values(), b"healthy"); listener.close()?; task::wait(server).await?; } Ok(()) }); } #[test] fn client_timeout_and_stop_release_the_owned_connection_driver() { run_test(async { for stop in [false, true] { let listener = net::listen("127.0.0.1", 0).await?; let port = net::port(&listener)?; let received = Channel::new(1)?; let notify = received.clone(); let peer = task::run(async move { let socket = net::accept(&listener).await?; net::timeout(&socket, 2000)?; assert!(net::read(&socket, 4096).await?.is_some()); notify.send(()).await?; assert_eq!(net::read(&socket, 1).await?, None); Ok(()) }) .await?; let mut budget = limits(); budget.timeout_ms = if stop { 1000 } else { 100 }; let client = task::run(async move { let result = http::send("127.0.0.1", port, request(), budget).await; assert_eq!(result.unwrap_err(), "HTTP request timed out"); Ok(()) }) .await?; received.receive().await?; if stop { task::stop(client).await?; } else { task::wait(client).await?; } task::wait(peer).await?; } Ok(()) }); } #[test] fn a_stalled_writer_releases_its_connection_slot() { run_test(async { let mut budget = limits(); budget.timeout_ms = 100; budget.connections = 1; budget.body_bytes = 16 * 1024 * 1024; let entered = Channel::new(1)?; let notify = entered.clone(); let (server, listener, port) = server( move |request| { let notify = notify.clone(); async move { let body = if request.target == "/slow" { notify.send(()).await?; Bytes::new(vec![b'x'; 16 * 1024 * 1024]) } else { Bytes::from_text("next") }; Ok(response(body)) } }, budget, ) .await?; let slow = Peer::connect(port).await?; slow.write(b"GET /slow HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n") .await?; entered.receive().await?; let mut next = Peer::connect(port).await?; next.write(b"GET /next HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n") .await?; assert_eq!(next.response(false).await?.1, b"next"); task::stop(server).await?; listener.close()?; Ok(()) }); }