265 lines
10 KiB
Rust
265 lines
10 KiB
Rust
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();
|
|
}
|