Files
core/test/dever-tests/tests/live_network.rs
T

768 lines
26 KiB
Rust

mod support;
#[path = "support/wire_peer.rs"]
mod wire_peer;
use dever_runtime::{bytes::Bytes, http, net, sse, task, websocket as ws};
use std::future::Future;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::time::Duration;
use wire_peer::Peer;
fn limits() -> http::Limits {
http::Limits {
header_bytes: 8192,
body_bytes: 1024,
timeout_ms: 500,
connections: 4,
http2: None,
}
}
fn live() -> http::LiveLimits {
http::LiveLimits {
chunk_bytes: 256,
idle_ms: 1000,
heartbeat_ms: 30,
}
}
fn ws_limits() -> ws::Limits {
ws::Limits {
message_bytes: 1024,
idle_ms: 1000,
write_ms: 500,
}
}
fn run(test: impl Future<Output = Result<(), String>>) {
task::run_entry_with(
task::RuntimeConfig {
worker_threads: 1,
max_blocking_threads: 2,
task_capacity: 64,
},
async {
tokio::time::timeout(Duration::from_secs(8), test)
.await
.map_err(|_| "live test deadline".to_owned())?
},
)
.unwrap();
}
async fn server<F, Fut>(
route: F,
limits: http::Limits,
live: http::LiveLimits,
) -> Result<(task::Task<()>, net::Listener, i64), String>
where
F: Fn(http::Request, http::HttpReply) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
let listener = net::listen("127.0.0.1", 0).await?;
let port = net::port(&listener)?;
let owner = listener.clone();
let server =
task::run(async move { http::serve_live(route, owner, limits, live).await }).await?;
Ok((server, listener, port))
}
struct Dropped(Arc<AtomicUsize>);
impl Drop for Dropped {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
struct Finished(Arc<tokio::sync::Semaphore>);
impl Drop for Finished {
fn drop(&mut self) {
self.0.add_permits(1);
}
}
#[test]
fn sse_wire_preserves_empty_ids_multiline_data_and_exact_size_limit() {
let event = sse::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!(
sse::encode(event.clone(), expected.len()).unwrap().values(),
expected.as_bytes()
);
assert!(sse::encode(event, expected.len() - 1).is_err());
for event in [
sse::Event {
event: "bad\rname".into(),
data: "".into(),
id: None,
retry_ms: None,
},
sse::Event {
event: "".into(),
data: "".into(),
id: Some("bad\0id".into()),
retry_ms: None,
},
sse::Event {
event: "".into(),
data: "".into(),
id: None,
retry_ms: Some(-1),
},
] {
assert!(sse::encode(event, 1024).is_err());
}
assert_eq!(
sse::encode(
sse::Event {
event: "".into(),
data: "".into(),
id: None,
retry_ms: None
},
8
)
.unwrap()
.values(),
b"data: \n\n"
);
}
#[test]
fn streaming_first_chunk_arrives_before_handler_finishes() {
run(async {
let gate = Arc::new(tokio::sync::Semaphore::new(0));
let handler_gate = gate.clone();
let (server, listener, port) = server(
move |_, reply| {
let gate = handler_gate.clone();
async move {
http::start(&reply, 200, vec![]).await?;
http::write(&reply, Bytes::from_text("first")).await?;
let _permit = gate.acquire().await.unwrap();
http::write(&reply, Bytes::from_text("second")).await?;
http::finish(&reply).await
}
},
limits(),
live(),
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"GET / HTTP/1.1\r\nHost: test\r\n\r\n").await?;
assert!(
peer.head()
.await?
.to_ascii_lowercase()
.contains("transfer-encoding: chunked")
);
assert_eq!(peer.chunk().await?, b"first");
gate.add_permits(1);
assert_eq!(peer.chunk().await?, b"second");
assert!(peer.chunk().await?.is_empty());
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn sse_heartbeats_outlive_request_deadline_and_stop_drains_handler() {
run(async {
let dropped = Arc::new(AtomicUsize::new(0));
let marker = dropped.clone();
let (server, listener, port) = server(
move |request, reply| {
let marker = marker.clone();
async move {
let _guard = Dropped(marker);
assert!(
request
.headers
.iter()
.any(|header| header.name == "last-event-id"
&& header.value.values() == b"42")
);
sse::start(&reply, vec![]).await?;
sse::send(
&reply,
sse::Event {
event: "update".into(),
data: "hello".into(),
id: Some("43".into()),
retry_ms: None,
},
)
.await?;
std::future::pending().await
}
},
http::Limits {
timeout_ms: 80,
..limits()
},
live(),
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"GET / HTTP/1.1\r\nHost: test\r\nLast-Event-ID: 42\r\n\r\n")
.await?;
assert!(peer.head().await?.contains("text/event-stream"));
assert_eq!(
peer.chunk().await?,
b"event: update\nid: 43\ndata: hello\n\n"
);
for _ in 0..5 {
assert_eq!(peer.chunk().await?, b":\n\n");
}
task::stop(server).await?;
assert_eq!(dropped.load(Ordering::SeqCst), 1);
assert_eq!(net::read(&peer.socket, 1).await?, None);
listener.close()?;
Ok(())
});
}
#[test]
fn head_body_drop_releases_stream_and_buffered_handlers_before_next_request() {
run(async {
let dropped = Arc::new(AtomicUsize::new(0));
let marker = dropped.clone();
let (server, listener, port) = server(
move |request, reply| {
let marker = marker.clone();
async move {
let _guard = Dropped(marker);
if request.target == "/stream" {
http::start(&reply, 200, vec![]).await?;
} else {
http::respond(
&reply,
http::Response {
status: 200,
headers: vec![],
body: Bytes::from_text("ok"),
},
)
.await?;
}
std::future::pending().await
}
},
limits(),
live(),
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"HEAD /stream HTTP/1.1\r\nHost: test\r\n\r\nHEAD /full HTTP/1.1\r\nHost: test\r\n\r\nGET /full HTTP/1.1\r\nHost: test\r\n\r\n").await?;
for _ in 0..3 {
assert!(peer.head().await?.starts_with("HTTP/1.1 200"));
}
assert_eq!(peer.take(2).await?, b"ok");
task::stop(server).await?;
assert_eq!(dropped.load(Ordering::SeqCst), 3);
listener.close()?;
Ok(())
});
}
#[test]
fn handler_fault_after_headers_truncates_only_that_response() {
run(async {
let gate = Arc::new(tokio::sync::Semaphore::new(0));
let handler_gate = gate.clone();
let (server, listener, port) = server(
move |_, reply| {
let gate = handler_gate.clone();
async move {
http::start(&reply, 200, vec![]).await?;
http::write(&reply, Bytes::from_text("partial")).await?;
let _permit = gate.acquire().await.unwrap();
Err("precise handler fault".into())
}
},
limits(),
live(),
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"GET / HTTP/1.1\r\nHost: test\r\n\r\n").await?;
peer.head().await?;
assert_eq!(peer.chunk().await?, b"partial");
gate.add_permits(1);
assert!(peer.chunk().await.is_err());
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
async fn echo(_request: http::Request, reply: http::HttpReply) -> Result<(), String> {
let socket = ws::accept(&reply, ws_limits()).await?;
while let Some(message) = ws::receive(&socket).await? {
if matches!(message, ws::Message::Text(_) | ws::Message::Binary(_)) {
ws::send(&socket, message).await?;
}
}
Ok(())
}
#[test]
fn slow_stream_peer_applies_backpressure_and_stop_releases_the_writer() {
run(async {
let produced = Arc::new(AtomicUsize::new(0));
let counter = produced.clone();
let finished = Arc::new(tokio::sync::Semaphore::new(0));
let signal = finished.clone();
let (server, listener, port) = server(
move |_, reply| {
let counter = counter.clone();
let signal = signal.clone();
async move {
let _finished = Finished(signal);
http::start(&reply, 200, vec![]).await?;
let chunk = Bytes::new(vec![1; 65536]);
for _ in 0..10000 {
if http::write(&reply, chunk.clone()).await.is_err() {
break;
}
counter.fetch_add(1, Ordering::SeqCst);
}
Ok(())
}
},
http::Limits {
timeout_ms: 100,
..limits()
},
http::LiveLimits {
chunk_bytes: 65536,
..live()
},
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"GET / HTTP/1.1\r\nHost: test\r\n\r\n").await?;
peer.head().await?;
// 客户端停止读取;写入只能占用有界通道与传输缓冲,不能把全部正文排队。
let _permit = tokio::time::timeout(Duration::from_secs(2), finished.acquire())
.await
.unwrap()
.unwrap();
let count = produced.load(Ordering::SeqCst);
assert!(
count > 0 && count < 10000,
"produced {count} chunks without a consuming peer"
);
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn stream_idle_timeout_is_a_peer_failure_and_server_accepts_next_request() {
run(async {
let (server, listener, port) = server(
|request, reply| async move {
if request.target == "/idle" {
http::start(&reply, 200, vec![]).await?;
std::future::pending().await
} else {
http::respond(
&reply,
http::Response {
status: 200,
headers: vec![],
body: Bytes::from_text("ok"),
},
)
.await
}
},
limits(),
http::LiveLimits {
idle_ms: 60,
heartbeat_ms: 20,
..live()
},
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"GET /idle HTTP/1.1\r\nHost: test\r\n\r\n")
.await?;
peer.head().await?;
assert!(peer.chunk().await.is_err());
let mut peer = Peer::connect(port).await?;
peer.write(b"GET /ok HTTP/1.1\r\nHost: test\r\n\r\n")
.await?;
assert!(peer.head().await?.starts_with("HTTP/1.1 200"));
assert_eq!(peer.take(2).await?, b"ok");
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn websocket_rejects_invalid_mask_utf8_and_fragmented_message_size() {
run(async {
let failures = dever_runtime::channel::Channel::new(4)?;
let sent = failures.clone();
let (server, listener, port) = server(
move |_, reply| {
let failures = sent.clone();
async move {
let socket = ws::accept(
&reply,
ws::Limits {
message_bytes: 4,
..ws_limits()
},
)
.await?;
let error = ws::receive(&socket).await.unwrap_err();
failures.send(error).await
}
},
limits(),
live(),
)
.await?;
for case in 0..4 {
let mut peer = Peer::connect(port).await?;
peer.upgrade().await?;
match case {
0 => peer.send_frame(0x81, false, b"bad").await?,
1 => peer.send_frame(0x81, true, &[255]).await?,
2 => peer.send_frame(0x82, true, b"large").await?,
_ => {
peer.send_frame(0x01, true, b"abc").await?;
peer.send_frame(0x80, true, b"def").await?;
}
}
let error = failures.receive().await?.unwrap();
assert!(!error.is_empty());
assert_eq!(net::read(&peer.socket, 1).await?, None);
}
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn upgraded_sessions_hold_connection_capacity_and_parent_stop_closes_them() {
run(async {
let (server, listener, port) = server(
echo,
http::Limits {
connections: 1,
..limits()
},
live(),
)
.await?;
let first = ws::connect("127.0.0.1", port, "/ws", ws_limits()).await?;
let second = ws::connect("127.0.0.1", port, "/ws", ws_limits());
tokio::pin!(second);
assert!(
tokio::time::timeout(Duration::from_millis(30), &mut second)
.await
.is_err()
);
ws::close(&first, 1000, "next").await?;
let second = second.await?;
let stream = ws::messages(second.clone());
ws::send(&second, ws::Message::Binary(Bytes::from_text("next"))).await?;
assert!(
matches!(stream.pull().await.unwrap()?, ws::Message::Binary(bytes) if bytes.values() == b"next")
);
task::stop(server).await?;
assert!(stream.pull().await.unwrap().is_err());
assert!(stream.pull().await.is_none());
listener.close()?;
Ok(())
});
}
#[test]
fn websocket_client_rejects_wrong_handshake_accept() {
run(async {
let listener = net::listen("127.0.0.1", 0).await?;
let port = net::port(&listener)?;
let owner = listener.clone();
let peer = task::run(async move {
let mut peer = Peer::new(net::accept(&owner).await?)?;
let head = peer.head().await?;
assert!(head.starts_with("GET /chat HTTP/1.1"));
assert!(head.to_ascii_lowercase().contains("sec-websocket-key:"));
peer.write(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: wrong\r\n\r\n").await?;
assert_eq!(net::read(&peer.socket, 1).await?, None);
Ok(())
}).await?;
assert!(
ws::connect("127.0.0.1", port, "/chat", ws_limits())
.await
.is_err()
);
task::wait(peer).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn client_frames_are_masked_and_server_upgrade_retains_preread_frame_bytes() {
run(async {
let listener = net::listen("127.0.0.1", 0).await?;
let port = net::port(&listener)?;
let owner = listener.clone();
let peer = task::run(async move {
let mut peer = Peer::new(net::accept(&owner).await?)?;
let head = peer.head().await?;
let key = head.lines().find_map(|line| {
let (name, value) = line.split_once(':')?;
name.eq_ignore_ascii_case("sec-websocket-key").then(|| value.trim())
}).unwrap();
// 只复用握手摘要;帧头、掩码及负载由独立观察器核验。
let accept = tokio_tungstenite::tungstenite::handshake::derive_accept_key(key.as_bytes());
peer.write(format!("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Accept: {accept}\r\n\r\n").as_bytes()).await?;
assert_eq!(peer.frame().await?, (0x81, true, b"masked".to_vec()));
peer.send_frame(0x88, false, &[3, 232]).await?;
assert_eq!(peer.frame().await?, (0x88, true, vec![3, 232]));
Ok(())
}).await?;
let socket = ws::connect("127.0.0.1", port, "/", ws_limits()).await?;
ws::send(&socket, ws::Message::Text("masked".into())).await?;
assert!(ws::receive(&socket).await?.is_none());
task::wait(peer).await?;
listener.close()?;
let (server, listener, port) = server(echo, limits(), live()).await?;
let mut peer = Peer::connect(port).await?;
let mut request = b"GET /ws HTTP/1.1\r\nHost: test\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n".to_vec();
request.extend_from_slice(&[0x81, 0x82, 0, 0, 0, 0, b'o', b'k']);
peer.write(&request).await?;
assert!(peer.head().await?.starts_with("HTTP/1.1 101"));
assert_eq!(peer.frame().await?, (0x81, false, b"ok".to_vec()));
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn websocket_cancel_waiting_writer_keeps_active_frame_and_cancel_active_writer_closes() {
run(async {
let sockets = dever_runtime::channel::Channel::new(1)?;
let sender = sockets.clone();
let (server, listener, port) = server(
move |_, reply| {
let sender = sender.clone();
async move {
let socket = ws::accept(
&reply,
ws::Limits {
message_bytes: 8 * 1024 * 1024,
write_ms: 3000,
..ws_limits()
},
)
.await?;
sender.send(socket).await?;
std::future::pending().await
}
},
limits(),
live(),
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.upgrade().await?;
let socket = sockets.receive().await?.unwrap();
let first = ws::send(
&socket,
ws::Message::Binary(Bytes::new(vec![42; 8 * 1024 * 1024])),
);
tokio::pin!(first);
assert!(
tokio::time::timeout(Duration::from_millis(20), &mut first)
.await
.is_err()
);
let waiting = socket.clone();
let waiting = task::run(async move {
ws::send(&waiting, ws::Message::Text("must not appear".into())).await
})
.await?;
tokio::task::yield_now().await;
task::stop(waiting).await?;
let reader = async { peer.frame().await };
let (sent, frame) = tokio::join!(first, reader);
sent?;
let frame = frame?;
assert_eq!(
(frame.0, frame.1, frame.2.len()),
(0x82, false, 8 * 1024 * 1024)
);
assert!(frame.2.iter().all(|byte| *byte == 42));
let writing = socket.clone();
let writing = task::run(async move {
ws::send(
&writing,
ws::Message::Binary(Bytes::new(vec![17; 8 * 1024 * 1024])),
)
.await
})
.await?;
tokio::time::sleep(Duration::from_millis(30)).await;
task::stop(writing).await?;
assert!(
ws::send(&socket, ws::Message::Text("closed".into()))
.await
.is_err()
);
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn stopping_live_service_drains_started_blocking_descendants() {
run(async {
let started = Arc::new(tokio::sync::Semaphore::new(0));
let completed = Arc::new(AtomicUsize::new(0));
let signal = started.clone();
let count = completed.clone();
let (server, listener, port) = server(
move |_, reply| {
let signal = signal.clone();
let count = count.clone();
async move {
sse::start(&reply, vec![]).await?;
let child = task::run(async move {
task::blocking(move || {
signal.add_permits(1);
std::thread::sleep(Duration::from_millis(80));
count.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.await
})
.await?;
task::wait(child).await
}
},
limits(),
live(),
)
.await?;
let peer = Peer::connect(port).await?;
peer.write(b"GET / HTTP/1.1\r\nHost: test\r\n\r\n").await?;
let _permit = started.acquire().await.unwrap();
task::stop(server).await?;
assert_eq!(completed.load(Ordering::SeqCst), 1);
listener.close()?;
Ok(())
});
}
#[test]
fn rejected_reply_operations_leave_the_committed_stream_open() {
run(async {
let (server, listener, port) = server(
|_, reply| async move {
assert!(
http::write(&reply, Bytes::from_text("too early"))
.await
.is_err()
);
assert!(http::start(&reply, 204, vec![]).await.is_err());
http::start(&reply, 200, vec![]).await?;
assert!(http::start(&reply, 201, vec![]).await.is_err());
assert!(
http::respond(
&reply,
http::Response {
status: 200,
headers: vec![],
body: Bytes::from_text("duplicate")
}
)
.await
.is_err()
);
assert!(sse::start(&reply, vec![]).await.is_err());
assert!(ws::accept(&reply, ws_limits()).await.is_err());
assert!(http::write(&reply, Bytes::new(vec![0; 257])).await.is_err());
tokio::task::yield_now().await;
http::write(&reply, Bytes::from_text("still-open")).await?;
http::finish(&reply).await
},
limits(),
live(),
)
.await?;
let mut peer = Peer::connect(port).await?;
peer.write(b"GET / HTTP/1.1\r\nHost: test\r\n\r\n").await?;
assert!(peer.head().await?.starts_with("HTTP/1.1 200"));
assert_eq!(peer.chunk().await?, b"still-open");
assert!(peer.chunk().await?.is_empty());
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn websocket_independent_frames_cover_fragmentation_ping_binary_and_close() {
run(async {
let (server, listener, port) = server(echo, limits(), live()).await?;
let mut peer = Peer::connect(port).await?;
peer.upgrade().await?;
peer.send_frame(0x01, true, &[0xe4]).await?;
peer.send_frame(0x89, true, b"alive").await?;
assert_eq!(peer.frame().await?, (0x8a, false, b"alive".to_vec()));
peer.send_frame(0x80, true, &[0xbd, 0xa0]).await?;
assert_eq!(peer.frame().await?, (0x81, false, "你".as_bytes().to_vec()));
peer.send_frame(0x82, true, &[0, 255, 1]).await?;
assert_eq!(peer.frame().await?, (0x82, false, vec![0, 255, 1]));
peer.send_frame(0x88, true, &[3, 232]).await?;
assert_eq!(peer.frame().await?, (0x88, false, vec![3, 232]));
assert_eq!(net::read(&peer.socket, 1).await?, None);
task::stop(server).await?;
listener.close()?;
Ok(())
});
}
#[test]
fn websocket_client_is_full_duplex_and_close_wakes_pending_receiver() {
run(async {
let (server, listener, port) = server(echo, limits(), live()).await?;
let socket = ws::connect("127.0.0.1", port, "/ws", ws_limits()).await?;
let reader = socket.clone();
let waiting = task::run(async move { ws::receive(&reader).await }).await?;
ws::send(&socket, ws::Message::Text("duplex".into())).await?;
assert!(
matches!(task::wait(waiting).await?, Some(ws::Message::Text(text)) if text == "duplex")
);
let reader = socket.clone();
let waiting = task::run(async move { Ok(ws::receive(&reader).await.is_err()) }).await?;
ws::close(&socket, 1000, "done").await?;
assert!(task::wait(waiting).await?);
assert!(
ws::send(&socket, ws::Message::Text("closed".into()))
.await
.is_err()
);
task::stop(server).await?;
listener.close()?;
Ok(())
});
}