use super::*; use dever_runtime::{http::PoolLimits, tls}; fn message() -> Request { Request { method: "POST".into(), target: "/echo".into(), headers: vec![], body: b"pooled".to_vec().into(), } } #[test] fn origins_reject_invalid_ports_before_connecting() { runtime() .run(async |scope| { for host in ["127.0.0.1", "[::1]", "localhost"] { for port in ["99999", "65536", "abc", "-1", ""] { for scheme in ["http", "https"] { assert!( http::client( &format!("{scheme}://{host}:{port}"), &tls::system(), &Limits::default(), &PoolLimits::default(), scope ) .is_err() ); } for scheme in ["ws", "wss"] { let error = http::websocket::open( &format!("{scheme}://{host}:{port}/"), &tls::system(), &http::websocket::Limits::default(), scope, ) .await .unwrap_err(); assert!( error.contains("port") || error.contains("invalid WebSocket URL"), "{error}" ); } } for suffix in ["", ":80", ":443", ":65535"] { let client = http::client( &format!("http://{host}{suffix}"), &tls::system(), &Limits::default(), &PoolLimits::default(), scope, ) .unwrap(); client.close().await.unwrap(); } } }) .unwrap(); } #[test] fn pool_bounds_http2_concurrency_and_cancels_active_requests() { use std::sync::atomic::{AtomicUsize, Ordering}; runtime() .run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let port = listener.port().unwrap(); let limits = Limits { connections: 1, http2: Some(http::Http2Limits { streams: 2, ..Default::default() }), ..Default::default() }; let active = Arc::new(AtomicUsize::new(0)); let peak = Arc::new(AtomicUsize::new(0)); let peak_read = peak.clone(); let incoming = listener.clone(); let server_limits = limits.clone(); let server = scope .spawn(move |child| async move { http::serve::<_, _, false>( &incoming, &server_limits, &child, move |request, _| { let active = active.clone(); let peak = peak.clone(); async move { let count = active.fetch_add(1, Ordering::SeqCst) + 1; peak.fetch_max(count, Ordering::SeqCst); tokio::time::sleep(Duration::from_millis(20)).await; active.fetch_sub(1, Ordering::SeqCst); Ok(Response { status: 200, headers: vec![], body: request.body, }) } }, ) .await }) .unwrap(); let client = http::client( &format!("http://127.0.0.1:{port}"), &tls::system(), &limits, &PoolLimits::default(), scope, ) .unwrap(); let replies = futures_util::future::join_all((0..8).map(|_| client.request(message()))).await; for response in replies { assert_eq!(response.unwrap().body.as_ref(), b"pooled"); } assert_eq!(peak_read.load(Ordering::SeqCst), 2); let alias = client.clone(); let pending = scope .spawn_leaf(move || async move { alias.request(message()).await }) .unwrap(); tokio::time::sleep(Duration::from_millis(5)).await; client.close().await.unwrap(); assert!(pending.wait().await.unwrap().is_err()); listener.close().unwrap(); server.wait().await.unwrap().unwrap(); }) .unwrap(); } #[test] fn pool_http1_http2_tls_reuse_close_and_scope_ownership() { for protocol in [false, true] { for secure in [false, true] { let escaped = runtime() .run(async |scope| { let listener = net::listen("127.0.0.1", 0, scope).await.unwrap(); let port = listener.port().unwrap(); let limits = Limits { connections: 1, http2: protocol.then(Default::default), ..Limits::default() }; let incoming = listener.clone(); let server_limits = limits.clone(); let server = scope .spawn(move |child| async move { if secure { let config = tls::server(super::tls::CERTIFICATE, super::tls::KEY).unwrap(); http::serve_tls::<_, _, false>( &incoming, &server_limits, &child, &config, route, ) .await } else { http::serve::<_, _, false>(&incoming, &server_limits, &child, route) .await } }) .unwrap(); let origin = format!( "{}://localhost:{port}", if secure { "https" } else { "http" } ); let config = tls::client(super::tls::ROOT).unwrap(); let client = http::client(&origin, &config, &limits, &PoolLimits::default(), scope) .unwrap(); for _ in 0..3 { assert_eq!( client.request(message()).await.unwrap().body.as_ref(), b"pooled" ); } let mut changed = message(); changed.headers.push(Header { name: "Host".into(), value: b"other.test".to_vec().into(), }); assert!(client.request(changed).await.is_err()); client.close().await.unwrap(); client.close().await.unwrap(); assert!(client.request(message()).await.is_err()); listener.close().unwrap(); server.wait().await.unwrap().unwrap(); http::client(&origin, &config, &limits, &PoolLimits::default(), scope).unwrap() }) .unwrap(); runtime() .run(async |_| { assert!( escaped .request(message()) .await .unwrap_err() .contains("closed") ); escaped.close().await.unwrap(); }) .unwrap(); } } } #[test] fn pool_reuses_one_tcp_connection_and_closes_it_before_returning() { let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let peer = std::thread::spawn(move || { let (stream, _) = listener.accept().unwrap(); stream .set_read_timeout(Some(Duration::from_secs(5))) .unwrap(); let mut stream = BufReader::new(stream); for _ in 0..3 { let mut size = 0; loop { let mut line = String::new(); assert!(stream.read_line(&mut line).unwrap() > 0); if line == "\r\n" { break; } if let Some(length) = line.to_ascii_lowercase().strip_prefix("content-length:") { size = length.trim().parse().unwrap(); } } let mut body = vec![0; size]; stream.read_exact(&mut body).unwrap(); assert_eq!(body, b"pooled"); stream .get_mut() .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok") .unwrap(); } assert_eq!(stream.read(&mut [0]).unwrap(), 0); }); runtime() .run(async |scope| { let client = http::client( &format!("http://127.0.0.1:{port}"), &tls::system(), &Limits::default(), &PoolLimits::default(), scope, ) .unwrap(); for _ in 0..3 { assert_eq!( client.request(message()).await.unwrap().body.as_ref(), b"ok" ); } client.close().await.unwrap(); }) .unwrap(); peer.join().unwrap(); }