Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,233 @@
|
||||
use super::*;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[test]
|
||||
fn head_and_disconnect_cancel_requests_before_reusing_the_connection() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let incoming = listener.clone();
|
||||
let active = Arc::new(AtomicUsize::new(0));
|
||||
let running = active.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&Limits::default(),
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
move |request, reply, _| {
|
||||
let active = active.clone();
|
||||
async move {
|
||||
assert_eq!(active.fetch_add(1, Ordering::SeqCst), 0);
|
||||
let _running = Running(active);
|
||||
reply.start(200, vec![]).await.unwrap();
|
||||
if request.method == "HEAD" {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
reply.write(b"data").await.unwrap();
|
||||
std::future::pending::<Result<(), String>>().await
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let (mut sender, driver) = client(listener.port().unwrap(), scope).await;
|
||||
let response = sender.send_request(message("HEAD")).await.unwrap();
|
||||
assert!(
|
||||
response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.unwrap()
|
||||
.to_bytes()
|
||||
.is_empty()
|
||||
);
|
||||
assert_eq!(running.load(Ordering::SeqCst), 0);
|
||||
let response = sender.send_request(message("GET")).await.unwrap();
|
||||
let mut body = response.into_body();
|
||||
assert_eq!(
|
||||
body.frame().await.unwrap().unwrap().into_data().unwrap(),
|
||||
b"data"[..]
|
||||
);
|
||||
drop(body);
|
||||
drop(sender);
|
||||
driver.wait().await.unwrap();
|
||||
tokio::time::timeout(Duration::from_secs(2), async {
|
||||
while running.load(Ordering::SeqCst) != 0 {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slow_peer_bounds_writer_and_parent_stop_drains_blocking_descendants() {
|
||||
for sse in [false, true] {
|
||||
slow_peer_case(sse);
|
||||
}
|
||||
}
|
||||
|
||||
fn slow_peer_case(sse: bool) {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let incoming = listener.clone();
|
||||
let count = Arc::new(AtomicUsize::new(0));
|
||||
let written = count.clone();
|
||||
let active = Arc::new(AtomicUsize::new(0));
|
||||
let running = active.clone();
|
||||
let ready = Arc::new(Notify::new());
|
||||
let started = ready.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&Limits {
|
||||
timeout_ms: 5000,
|
||||
..Limits::default()
|
||||
},
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
move |_, reply, scope| {
|
||||
let written = written.clone();
|
||||
let active = active.clone();
|
||||
let ready = ready.clone();
|
||||
async move {
|
||||
let worker = scope
|
||||
.spawn(move |child| async move {
|
||||
child
|
||||
.blocking(move || {
|
||||
active.fetch_add(1, Ordering::SeqCst);
|
||||
let _running = Running(active);
|
||||
ready.notify_one();
|
||||
std::thread::sleep(Duration::from_millis(400));
|
||||
})
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
if sse {
|
||||
reply.sse_start(vec![]).await.unwrap();
|
||||
} else {
|
||||
reply.start(200, vec![]).await.unwrap();
|
||||
}
|
||||
let bytes = vec![0; 65536];
|
||||
let event = http::Event {
|
||||
event: String::new(),
|
||||
data: "a".repeat(65528),
|
||||
id: None,
|
||||
retry_ms: None,
|
||||
};
|
||||
for _ in 0..10000 {
|
||||
let result = if sse {
|
||||
reply.sse_send(&event).await
|
||||
} else {
|
||||
reply.write(&bytes).await
|
||||
};
|
||||
result.map_err(|error| error.to_string())?;
|
||||
written.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
worker.wait().await.unwrap().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let mut socket =
|
||||
tokio::net::TcpStream::connect(("127.0.0.1", listener.port().unwrap() as u16))
|
||||
.await
|
||||
.unwrap();
|
||||
use tokio::io::AsyncWriteExt;
|
||||
socket
|
||||
.write_all(b"GET / HTTP/1.1\r\nHost: fixture.test\r\n\r\n")
|
||||
.await
|
||||
.unwrap();
|
||||
started.notified().await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
assert!(count.load(Ordering::SeqCst) < 10000);
|
||||
server.stop().await.unwrap();
|
||||
assert_eq!(running.load(Ordering::SeqCst), 0);
|
||||
listener.close().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_header_and_idle_deadlines_are_distinct_and_faults_truncate() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for mode in ["headers", "idle", "fault", "missing"] {
|
||||
let gate = Arc::new(Semaphore::new(0));
|
||||
let failed = gate.clone();
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let incoming = listener.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&Limits {
|
||||
timeout_ms: 100,
|
||||
..Limits::default()
|
||||
},
|
||||
&http::LiveLimits {
|
||||
idle_ms: 60,
|
||||
heartbeat_ms: 20,
|
||||
chunk_bytes: 64,
|
||||
},
|
||||
&child,
|
||||
move |_, reply, _| {
|
||||
let gate = failed.clone();
|
||||
async move {
|
||||
if mode == "missing" {
|
||||
return Ok(());
|
||||
}
|
||||
if mode == "headers" {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
reply.start(200, vec![]).await.unwrap();
|
||||
if mode == "idle" {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
reply.write(b"partial").await.unwrap();
|
||||
let _permit = gate.acquire().await.unwrap();
|
||||
Err("located handler failure".into())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let (mut sender, driver) = client(listener.port().unwrap(), scope).await;
|
||||
let response = sender.send_request(message("GET")).await.unwrap();
|
||||
match mode {
|
||||
"headers" => assert_eq!(response.status(), 504),
|
||||
"missing" => assert_eq!(response.status(), 500),
|
||||
_ => {
|
||||
assert_eq!(response.status(), 200);
|
||||
let mut body = response.into_body();
|
||||
if mode == "fault" {
|
||||
assert_eq!(
|
||||
body.frame().await.unwrap().unwrap().into_data().unwrap(),
|
||||
b"partial"[..]
|
||||
);
|
||||
gate.add_permits(1);
|
||||
}
|
||||
assert!(body.collect().await.is_err());
|
||||
}
|
||||
}
|
||||
drop(sender);
|
||||
driver.wait().await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
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<Empty<Bytes>>,
|
||||
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<Empty<Bytes>> {
|
||||
hyper::Request::builder()
|
||||
.method(method)
|
||||
.uri("/")
|
||||
.header("Host", "fixture.test")
|
||||
.body(Empty::new())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
struct Running(Arc<AtomicUsize>);
|
||||
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::<Result<(), String>>().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();
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
use super::*;
|
||||
use futures_util::stream;
|
||||
use http_body_util::StreamBody;
|
||||
use hyper::{body::Frame, service::service_fn};
|
||||
use std::{convert::Infallible, time::Instant};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
async fn independent_server(
|
||||
data: Arc<str>,
|
||||
iterations: usize,
|
||||
limit: usize,
|
||||
) -> (i64, tokio::task::JoinHandle<()>) {
|
||||
let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
|
||||
.await
|
||||
.unwrap();
|
||||
let port = i64::from(listener.local_addr().unwrap().port());
|
||||
let server = tokio::spawn(async move {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
socket.set_nodelay(true).unwrap();
|
||||
let service = service_fn(move |_: hyper::Request<hyper::body::Incoming>| {
|
||||
let data = data.clone();
|
||||
async move {
|
||||
let (sender, receiver) = mpsc::channel::<Bytes>(1);
|
||||
let producer = tokio::spawn(async move {
|
||||
for _ in 0..iterations {
|
||||
assert!(!data.contains(['\r', '\n']));
|
||||
assert!(data.len().checked_add(8).unwrap() <= limit);
|
||||
let encoded = format!("data: {data}\n\n");
|
||||
tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
sender.send(Bytes::from(encoded)),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
let frames = stream::unfold(
|
||||
(receiver, Some(producer)),
|
||||
|(mut receiver, mut producer)| async move {
|
||||
match receiver.recv().await {
|
||||
Some(bytes) => Some((
|
||||
Ok::<_, Infallible>(Frame::data(bytes)),
|
||||
(receiver, producer),
|
||||
)),
|
||||
None => {
|
||||
producer.take().unwrap().await.unwrap();
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
Ok::<_, Infallible>(
|
||||
hyper::Response::builder()
|
||||
.status(200)
|
||||
.header("content-type", "text/event-stream")
|
||||
.header("cache-control", "no-cache")
|
||||
.body(StreamBody::new(frames))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
});
|
||||
hyper::server::conn::http1::Builder::new()
|
||||
.serve_connection(TokioIo::new(socket), service)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
(port, server)
|
||||
}
|
||||
|
||||
async fn sample(
|
||||
sender: &mut hyper::client::conn::http1::SendRequest<Empty<Bytes>>,
|
||||
expected: &[u8],
|
||||
iterations: usize,
|
||||
) -> (u128, usize) {
|
||||
let started = Instant::now();
|
||||
let (response, chunks) = tokio::time::timeout(Duration::from_secs(10), async {
|
||||
let response = sender.send_request(message("GET")).await.unwrap();
|
||||
let (response, mut body) = response.into_parts();
|
||||
let mut chunks = Vec::with_capacity(iterations);
|
||||
while let Some(frame) = body.frame().await {
|
||||
chunks.push(frame.unwrap().into_data().unwrap());
|
||||
}
|
||||
(response, chunks)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.headers["content-type"], "text/event-stream");
|
||||
let total = chunks.iter().map(Bytes::len).sum::<usize>();
|
||||
assert_eq!(total, expected.len() * iterations);
|
||||
assert!(
|
||||
chunks
|
||||
.iter()
|
||||
.flat_map(|bytes| bytes.iter().copied())
|
||||
.eq(expected.iter().copied().cycle().take(total))
|
||||
);
|
||||
(elapsed, total)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn sse_send_cost() {
|
||||
let runtime = Runtime::new(dever_runtime::task::Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 32,
|
||||
})
|
||||
.unwrap();
|
||||
runtime
|
||||
.run(async |scope| {
|
||||
eprintln!(
|
||||
"sse_send: 15 fixed rounds; one async worker; persistent HTTP/1 loopback; \
|
||||
both implementations encode each single-line data event and use a one-chunk queue; \
|
||||
rust_independent uses format!, Bytes and Hyper/Tokio directly, without Dever's SSE encoder/send; \
|
||||
timed request through complete body includes network and bounded send; \
|
||||
setup, exact byte verification and teardown excluded; \
|
||||
successful-stream workload only, not lifecycle/heartbeat parity"
|
||||
);
|
||||
println!("\ncase,sample,implementation,iterations,elapsed_ns,bytes");
|
||||
for (size, iterations) in [(64, 4096), (65_536, 128), (1_048_576, 16)] {
|
||||
let event = Arc::new(http::Event {
|
||||
event: String::new(),
|
||||
data: "a".repeat(size),
|
||||
id: None,
|
||||
retry_ms: None,
|
||||
});
|
||||
let expected = format!("data: {}\n\n", event.data);
|
||||
let limit = expected.len();
|
||||
let encoded = event.encode(limit).unwrap();
|
||||
println!(
|
||||
"sse_capacity,{size},{},{}",
|
||||
encoded.len(),
|
||||
encoded.capacity()
|
||||
);
|
||||
assert_eq!(encoded, expected);
|
||||
drop(encoded);
|
||||
let (independent_port, independent) = independent_server(
|
||||
Arc::from(event.data.as_str()),
|
||||
iterations,
|
||||
limit,
|
||||
)
|
||||
.await;
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let incoming = listener.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&Limits {
|
||||
timeout_ms: 5000,
|
||||
..Limits::default()
|
||||
},
|
||||
&http::LiveLimits {
|
||||
chunk_bytes: limit as i64,
|
||||
heartbeat_ms: 30000,
|
||||
idle_ms: 60000,
|
||||
},
|
||||
&child,
|
||||
move |_, reply, _| {
|
||||
let event = event.clone();
|
||||
async move {
|
||||
reply.sse_start(vec![]).await.unwrap();
|
||||
for _ in 0..iterations {
|
||||
reply.sse_send(&event).await.unwrap();
|
||||
}
|
||||
reply.finish().await.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let (mut dever_client, dever_driver) =
|
||||
client(listener.port().unwrap(), scope).await;
|
||||
let (mut independent_client, independent_driver) =
|
||||
client(independent_port, scope).await;
|
||||
sample(&mut dever_client, expected.as_bytes(), iterations).await;
|
||||
sample(&mut independent_client, expected.as_bytes(), iterations).await;
|
||||
for round in 0..15 {
|
||||
let mut order = ["dever", "rust_independent", "control"];
|
||||
order.rotate_left(round % 3);
|
||||
for label in order {
|
||||
let sender = if label == "rust_independent" {
|
||||
&mut independent_client
|
||||
} else {
|
||||
&mut dever_client
|
||||
};
|
||||
let (elapsed, total) = sample(sender, expected.as_bytes(), iterations).await;
|
||||
println!("sse_send_{size},{round},{label},{iterations},{elapsed},{total}");
|
||||
}
|
||||
}
|
||||
drop(dever_client);
|
||||
drop(independent_client);
|
||||
dever_driver.wait().await.unwrap();
|
||||
independent_driver.wait().await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
independent.await.unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
use super::*;
|
||||
use hyper_util::rt::TokioExecutor;
|
||||
|
||||
#[test]
|
||||
fn http2_live_reset_isolates_sse_neighbor_and_tls_uses_same_body_contract() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for secure in [false, true] {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let port = listener.port().unwrap();
|
||||
let incoming = listener.clone();
|
||||
let running = Arc::new(AtomicUsize::new(0));
|
||||
let active = running.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
let limits = Limits {
|
||||
timeout_ms: 200,
|
||||
http2: Some(http::Http2Limits::default()),
|
||||
..Limits::default()
|
||||
};
|
||||
let live = http::LiveLimits {
|
||||
heartbeat_ms: 20,
|
||||
idle_ms: 1000,
|
||||
..http::LiveLimits::default()
|
||||
};
|
||||
let route =
|
||||
move |request: Request,
|
||||
reply: http::HttpReply,
|
||||
_: Arc<Scope<'static>>| {
|
||||
let running = active.clone();
|
||||
async move {
|
||||
if request.target == "/ordinary" {
|
||||
return reply
|
||||
.respond(Response {
|
||||
status: 200,
|
||||
headers: vec![],
|
||||
body: b"ok".to_vec().into(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string());
|
||||
}
|
||||
running.fetch_add(1, Ordering::SeqCst);
|
||||
let _running = Running(running);
|
||||
reply.sse_start(vec![]).await.unwrap();
|
||||
std::future::pending::<Result<(), String>>().await
|
||||
}
|
||||
};
|
||||
if secure {
|
||||
let identity = dever_runtime::tls::server(
|
||||
super::super::tls::CERTIFICATE,
|
||||
super::super::tls::KEY,
|
||||
)
|
||||
.unwrap();
|
||||
http::serve_live_tls(
|
||||
&incoming, &limits, &live, &child, &identity, route,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
http::serve_live(&incoming, &limits, &live, &child, route).await
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let socket = tokio::net::TcpStream::connect(("127.0.0.1", port as u16))
|
||||
.await
|
||||
.unwrap();
|
||||
let (mut sender, driver) = if secure {
|
||||
let trust = dever_runtime::tls::client(super::super::tls::ROOT).unwrap();
|
||||
let socket = trust.connect("localhost", socket, true).await.unwrap();
|
||||
let (sender, connection) =
|
||||
hyper::client::conn::http2::Builder::new(TokioExecutor::new())
|
||||
.handshake::<_, Empty<Bytes>>(TokioIo::new(socket))
|
||||
.await
|
||||
.unwrap();
|
||||
(
|
||||
sender,
|
||||
scope
|
||||
.spawn_leaf(move || async move {
|
||||
let _ = connection.await;
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let (sender, connection) =
|
||||
hyper::client::conn::http2::Builder::new(TokioExecutor::new())
|
||||
.handshake::<_, Empty<Bytes>>(TokioIo::new(socket))
|
||||
.await
|
||||
.unwrap();
|
||||
(
|
||||
sender,
|
||||
scope
|
||||
.spawn_leaf(move || async move {
|
||||
let _ = connection.await;
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
};
|
||||
let request = |path| {
|
||||
hyper::Request::builder()
|
||||
.uri(format!(
|
||||
"{}://localhost:{port}/{path}",
|
||||
if secure { "https" } else { "http" }
|
||||
))
|
||||
.body(Empty::new())
|
||||
.unwrap()
|
||||
};
|
||||
let mut first = sender
|
||||
.send_request(request("first"))
|
||||
.await
|
||||
.unwrap()
|
||||
.into_body();
|
||||
let mut second = sender
|
||||
.send_request(request("second"))
|
||||
.await
|
||||
.unwrap()
|
||||
.into_body();
|
||||
assert_eq!(
|
||||
first.frame().await.unwrap().unwrap().into_data().unwrap(),
|
||||
b":\n\n"[..]
|
||||
);
|
||||
assert_eq!(running.load(Ordering::SeqCst), 2);
|
||||
drop(first);
|
||||
tokio::time::timeout(Duration::from_secs(2), async {
|
||||
while running.load(Ordering::SeqCst) != 1 {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
second.frame().await.unwrap().unwrap().into_data().unwrap(),
|
||||
b":\n\n"[..]
|
||||
);
|
||||
// 活动 SSE 响应超过连接空闲期限仍可继续传输。
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
let ordinary = sender.send_request(request("ordinary")).await.unwrap();
|
||||
assert_eq!(
|
||||
ordinary.into_body().collect().await.unwrap().to_bytes(),
|
||||
b"ok"[..]
|
||||
);
|
||||
drop(second);
|
||||
drop(sender);
|
||||
driver.wait().await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
assert_eq!(running.load(Ordering::SeqCst), 0);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user