Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,325 @@
|
||||
use super::*;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[test]
|
||||
fn websocket_client_scope_ends_all_aliases_and_timeout_stream_fails_once() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, server) = server(scope).await;
|
||||
let port = listener.port().unwrap();
|
||||
let owner = scope
|
||||
.spawn(move |child| async move {
|
||||
ws::connect("127.0.0.1", port, "/", &limits(), &child)
|
||||
.await
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let escaped = owner.wait().await.unwrap();
|
||||
assert!(escaped.send(Message::Text("closed".into())).await.is_err());
|
||||
let socket = ws::connect(
|
||||
"127.0.0.1",
|
||||
port,
|
||||
"/",
|
||||
&ws::Limits {
|
||||
idle_ms: 25,
|
||||
..limits()
|
||||
},
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let stream = socket.messages(|message| message);
|
||||
let alias = stream.clone();
|
||||
let failure = stream.next().await.unwrap().unwrap().unwrap_err();
|
||||
assert!(failure.contains("timed out"));
|
||||
assert!(alias.next().await.unwrap().is_none());
|
||||
assert!(socket.send(Message::Text("closed".into())).await.is_err());
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_listener_drain_keeps_session_and_parent_stop_cleans_handler() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, mut server) = server(scope).await;
|
||||
let socket = ws::connect("127.0.0.1", listener.port().unwrap(), "/", &limits(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
listener.close().unwrap();
|
||||
tokio::select! {
|
||||
_ = &mut server => panic!("server must retain its upgraded session"),
|
||||
() = tokio::time::sleep(Duration::from_millis(25)) => (),
|
||||
}
|
||||
socket
|
||||
.send(Message::Text("after listener close".into()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Text("after listener close".into()))
|
||||
);
|
||||
socket.close(1000, "").await.unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
|
||||
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 (sender, receiver) = oneshot::channel::<WebSocket>();
|
||||
let sender = Arc::new(std::sync::Mutex::new(Some(sender)));
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&Limits::default(),
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
move |_, reply, scope| {
|
||||
let sender = sender.clone();
|
||||
let running = running.clone();
|
||||
async move {
|
||||
let socket = ws::accept(&reply, &limits(), &scope).await.unwrap();
|
||||
running.fetch_add(1, Ordering::SeqCst);
|
||||
let _running = Running(running);
|
||||
let _ = sender.lock().unwrap().take().unwrap().send(socket.clone());
|
||||
let _ = socket.receive().await;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let socket = ws::connect("127.0.0.1", listener.port().unwrap(), "/", &limits(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let escaped = receiver.await.unwrap();
|
||||
server.stop().await.unwrap();
|
||||
assert_eq!(active.load(Ordering::SeqCst), 0);
|
||||
assert!(escaped.send(Message::Text("gone".into())).await.is_err());
|
||||
assert!(socket.receive().await.is_err());
|
||||
listener.close().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct Running(Arc<AtomicUsize>);
|
||||
impl Drop for Running {
|
||||
fn drop(&mut self) {
|
||||
self.0.fetch_sub(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_owner_stop_wakes_receive_in_another_task() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, server) = server(scope).await;
|
||||
let port = listener.port().unwrap();
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let owner = scope
|
||||
.spawn(move |child| async move {
|
||||
let socket = ws::connect("127.0.0.1", port, "/", &limits(), &child)
|
||||
.await
|
||||
.unwrap();
|
||||
sender.send(socket).unwrap();
|
||||
std::future::pending::<()>().await;
|
||||
})
|
||||
.unwrap();
|
||||
let socket = receiver.await.unwrap();
|
||||
let (started, ready) = oneshot::channel();
|
||||
let reader = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let receiving = socket.receive();
|
||||
tokio::pin!(receiving);
|
||||
assert!(futures_util::poll!(&mut receiving).is_pending());
|
||||
started.send(()).unwrap();
|
||||
tokio::time::timeout(Duration::from_millis(200), receiving)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap_err()
|
||||
})
|
||||
.unwrap();
|
||||
ready.await.unwrap();
|
||||
owner.stop().await.unwrap();
|
||||
assert!(reader.wait().await.unwrap().contains("WebSocket is closed"));
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_closing_message_stream_preserves_independent_socket_alias() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, server) = server(scope).await;
|
||||
let socket = ws::connect("127.0.0.1", listener.port().unwrap(), "/", &limits(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let stream = socket.messages(|message| message);
|
||||
stream.close();
|
||||
assert!(stream.next().await.unwrap().is_none());
|
||||
socket
|
||||
.send(Message::Binary(vec![1, 2].into()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Binary(vec![1, 2].into()))
|
||||
);
|
||||
socket.close(1000, "").await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_cancelled_writer_closes_only_after_it_owns_the_direction() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let (started, receiving) = oneshot::channel();
|
||||
let (release, released) = oneshot::channel();
|
||||
let peer = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let wire = tokio_tungstenite::accept_async(stream).await.unwrap();
|
||||
wire.get_ref().readable().await.unwrap();
|
||||
let _ = started.send(());
|
||||
let _ = released.await;
|
||||
})
|
||||
.unwrap();
|
||||
let socket = ws::connect(
|
||||
"127.0.0.1",
|
||||
i64::from(port),
|
||||
"/",
|
||||
&ws::Limits {
|
||||
message_bytes: 16 * 1024 * 1024,
|
||||
write_ms: 1000,
|
||||
..limits()
|
||||
},
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let sender = socket.clone();
|
||||
let mut writing = scope
|
||||
.spawn_leaf(move || async move {
|
||||
sender
|
||||
.send(Message::Binary(vec![1; 16 * 1024 * 1024].into()))
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
receiving.await.unwrap();
|
||||
let waiting = socket.clone();
|
||||
let waiter = scope
|
||||
.spawn_leaf(
|
||||
move || async move { waiting.send(Message::Text("queued".into())).await },
|
||||
)
|
||||
.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
waiter.stop().await.unwrap();
|
||||
assert!(
|
||||
tokio::time::timeout(Duration::from_millis(10), &mut writing)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
writing.stop().await.unwrap();
|
||||
assert!(socket.send(Message::Text("closed".into())).await.is_err());
|
||||
release.send(()).unwrap();
|
||||
peer.wait().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_close_keeps_the_handshake_when_a_reader_is_waiting() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let (replied, reply) = oneshot::channel();
|
||||
let peer = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let mut wire = tokio_tungstenite::accept_async(stream).await.unwrap();
|
||||
assert!(matches!(
|
||||
wire.next().await,
|
||||
Some(Ok(tokio_tungstenite::tungstenite::Message::Close(_)))
|
||||
));
|
||||
wire.flush().await.unwrap();
|
||||
replied.send(()).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
let socket = ws::connect("127.0.0.1", i64::from(port), "/", &limits(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let receiving = socket.receive();
|
||||
tokio::pin!(receiving);
|
||||
assert!(futures_util::poll!(&mut receiving).is_pending());
|
||||
let closing = socket.close(1000, "");
|
||||
tokio::pin!(closing);
|
||||
assert!(futures_util::poll!(&mut closing).is_pending());
|
||||
reply.await.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
let received = receiving.await;
|
||||
closing.await.unwrap();
|
||||
assert!(received.unwrap_err().contains("WebSocket is closing"));
|
||||
peer.wait().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_close_deadline_releases_all_aliases() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let (release, released) = oneshot::channel();
|
||||
let peer = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let _wire = tokio_tungstenite::accept_async(stream).await.unwrap();
|
||||
let _ = released.await;
|
||||
})
|
||||
.unwrap();
|
||||
let socket = ws::connect(
|
||||
"127.0.0.1",
|
||||
i64::from(port),
|
||||
"/",
|
||||
&ws::Limits {
|
||||
write_ms: 50,
|
||||
..limits()
|
||||
},
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let alias = socket.clone();
|
||||
let receiving = alias.receive();
|
||||
tokio::pin!(receiving);
|
||||
assert!(futures_util::poll!(&mut receiving).is_pending());
|
||||
let (closed, received) = tokio::join!(socket.close(1000, ""), receiving);
|
||||
assert!(closed.unwrap_err().contains("timed out"));
|
||||
assert!(received.unwrap_err().contains("WebSocket is closing"));
|
||||
assert!(
|
||||
alias
|
||||
.send(Message::Text("closed".into()))
|
||||
.await
|
||||
.unwrap_err()
|
||||
.contains("WebSocket is closed")
|
||||
);
|
||||
release.send(()).unwrap();
|
||||
peer.wait().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
use super::*;
|
||||
use dever_runtime::http::websocket::{self as ws, Message, WebSocket};
|
||||
|
||||
mod lifecycle;
|
||||
mod protocol;
|
||||
mod shared;
|
||||
#[path = "../../../performance/websocket/shared/mod.rs"]
|
||||
mod shared_reference;
|
||||
|
||||
fn limits() -> ws::Limits {
|
||||
ws::Limits {
|
||||
idle_ms: 1000,
|
||||
write_ms: 1000,
|
||||
..ws::Limits::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn echo(
|
||||
_: Request,
|
||||
reply: http::HttpReply,
|
||||
scope: Arc<Scope<'static>>,
|
||||
) -> Result<(), String> {
|
||||
let socket = ws::accept(&reply, &limits(), &scope)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
while let Ok(Some(message)) = socket.receive().await {
|
||||
if matches!(message, Message::Text(_) | Message::Binary(_)) {
|
||||
socket
|
||||
.send(message)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn server(
|
||||
scope: &Scope<'_>,
|
||||
) -> (
|
||||
net::Listener,
|
||||
dever_runtime::task::Task<Result<(), http::ServeError>>,
|
||||
) {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let incoming = listener.clone();
|
||||
let task = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&Limits::default(),
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
echo,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
(listener, task)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_live_upgrade_echo_control_frames_and_close() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, server) = server(scope).await;
|
||||
let socket = ws::connect(
|
||||
"127.0.0.1",
|
||||
listener.port().unwrap(),
|
||||
"/echo?key=value",
|
||||
&limits(),
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
for message in [
|
||||
Message::Text("你好".into()),
|
||||
Message::Binary(vec![0, 255, 17].into()),
|
||||
] {
|
||||
socket.send(message.clone()).await.unwrap();
|
||||
assert_eq!(socket.receive().await.unwrap(), Some(message));
|
||||
}
|
||||
socket
|
||||
.send(Message::Ping(b"ping".to_vec().into()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Pong(b"ping".to_vec().into()))
|
||||
);
|
||||
assert!(
|
||||
socket
|
||||
.send(Message::Ping(vec![0; 126].into()))
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(socket.close(1005, "").await.is_err());
|
||||
assert!(socket.close(1000, &"界".repeat(42)).await.is_err());
|
||||
socket
|
||||
.send(Message::Text("still open".into()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Text("still open".into()))
|
||||
);
|
||||
socket.close(1000, "结束").await.unwrap();
|
||||
assert!(socket.receive().await.unwrap().is_none());
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_aliases_send_whole_messages_while_receiving() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, server) = server(scope).await;
|
||||
let socket = ws::connect("127.0.0.1", listener.port().unwrap(), "/", &limits(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let writers = futures_util::future::join_all((0u8..4).map(|writer| {
|
||||
let socket = &socket;
|
||||
async move {
|
||||
for sequence in 0u8..16 {
|
||||
let mut bytes = vec![writer; 65536];
|
||||
bytes[1] = sequence;
|
||||
socket.send(Message::Binary(bytes.into())).await.unwrap();
|
||||
}
|
||||
}
|
||||
}));
|
||||
let receiving = async {
|
||||
let mut next = [0u8; 4];
|
||||
for _ in 0..64 {
|
||||
let Some(Message::Binary(bytes)) = socket.receive().await.unwrap() else {
|
||||
panic!("expected a complete binary message");
|
||||
};
|
||||
assert_eq!(bytes.len(), 65536);
|
||||
let writer = bytes[0];
|
||||
assert_eq!(bytes[1], next[usize::from(writer)]);
|
||||
assert!(bytes[2..].iter().all(|byte| *byte == writer));
|
||||
next[usize::from(writer)] += 1;
|
||||
}
|
||||
assert_eq!(next, [16; 4]);
|
||||
};
|
||||
tokio::join!(writers, receiving);
|
||||
socket.close(1000, "").await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_wss_uses_supplied_ca_and_dns_with_no_insecure_fallback() {
|
||||
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 server = scope
|
||||
.spawn(move |child| async move {
|
||||
let tls = dever_runtime::tls::server(tls::CERTIFICATE, tls::KEY).unwrap();
|
||||
http::serve_live_tls(
|
||||
&incoming,
|
||||
&Limits::default(),
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
&tls,
|
||||
echo,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let url = format!("wss://localhost:{port}/echo");
|
||||
assert!(
|
||||
ws::open(&url, &dever_runtime::tls::system(), &limits(), scope)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
let trusted = dever_runtime::tls::client(tls::ROOT).unwrap();
|
||||
let socket = ws::open(&url, &trusted, &limits(), scope).await.unwrap();
|
||||
socket
|
||||
.send(Message::Text("verified TLS".into()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Text("verified TLS".into()))
|
||||
);
|
||||
socket.close(1000, "").await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_invalid_inputs_fail_before_connecting() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for limit in [
|
||||
ws::Limits {
|
||||
message_bytes: 0,
|
||||
..limits()
|
||||
},
|
||||
ws::Limits {
|
||||
idle_ms: 0,
|
||||
..limits()
|
||||
},
|
||||
ws::Limits {
|
||||
write_ms: -1,
|
||||
..limits()
|
||||
},
|
||||
] {
|
||||
assert!(
|
||||
ws::connect("127.0.0.1", 0, "/", &limit, scope)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
for url in [
|
||||
"http://localhost/",
|
||||
"ws://user:password@localhost/",
|
||||
"ws://localhost/#fragment",
|
||||
"ws://localhost:99999/",
|
||||
] {
|
||||
assert!(
|
||||
ws::open(url, &dever_runtime::tls::system(), &limits(), scope)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
for target in ["http://localhost/", "*", "/#fragment"] {
|
||||
assert!(
|
||||
ws::connect("127.0.0.1", 0, target, &limits(), scope)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
use super::*;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use tokio_tungstenite::tungstenite::{
|
||||
Message as Frame,
|
||||
protocol::frame::{
|
||||
Frame as RawFrame,
|
||||
coding::{Data, OpCode},
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release WebSocket handshake performance probe"]
|
||||
fn websocket_upgrade_cost() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let (listener, serving) = server(scope).await;
|
||||
let port = listener.port().unwrap();
|
||||
println!("\ncase,sample,iterations,elapsed_ns");
|
||||
for sample in 0..16 {
|
||||
let started = std::time::Instant::now();
|
||||
for _ in 0..20 {
|
||||
let socket = ws::connect("127.0.0.1", port, "/echo", &limits(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
socket.send(Message::Text("check".into())).await.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Text("check".into()))
|
||||
);
|
||||
socket.close(1000, "").await.unwrap();
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
if sample != 0 {
|
||||
println!("websocket_upgrade,{},20,{elapsed}", sample - 1);
|
||||
}
|
||||
}
|
||||
listener.close().unwrap();
|
||||
serving.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_client_limits_handshake_without_limiting_later_frames() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for header_bytes in [60000, 66000] {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let peer = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let handshake = tokio_tungstenite::accept_hdr_async(
|
||||
stream,
|
||||
#[expect(clippy::result_large_err, reason = "Tungstenite 的握手回调要求直接返回 HTTP 错误响应")]
|
||||
move |_: &tokio_tungstenite::tungstenite::handshake::server::Request,
|
||||
mut response: tokio_tungstenite::tungstenite::handshake::server::Response| {
|
||||
response.headers_mut().insert(
|
||||
"x-padding",
|
||||
"x".repeat(header_bytes).parse().unwrap(),
|
||||
);
|
||||
Ok(response)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
if header_bytes == 60000 {
|
||||
let mut wire = handshake.unwrap();
|
||||
wire.send(Frame::Binary(vec![17; 70000].into())).await.unwrap();
|
||||
assert!(matches!(wire.next().await, Some(Ok(Frame::Close(_)))));
|
||||
wire.flush().await.unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let connection = ws::connect("127.0.0.1", i64::from(port), "/", &limits(), scope).await;
|
||||
if header_bytes == 60000 {
|
||||
let socket = connection.unwrap();
|
||||
assert_eq!(socket.receive().await.unwrap(), Some(Message::Binary(vec![17; 70000].into())));
|
||||
socket.close(1000, "").await.unwrap();
|
||||
} else {
|
||||
assert!(connection.is_err());
|
||||
}
|
||||
peer.wait().await.unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_client_reassembles_fragments_and_rejects_cumulative_limit_and_utf8() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for invalid in [false, true] {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let peer = scope
|
||||
.spawn_leaf(move || async move {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
let mut wire = tokio_tungstenite::accept_async(stream).await.unwrap();
|
||||
wire.send(Frame::Frame(RawFrame::message(
|
||||
vec![0xe4, 0xb8],
|
||||
OpCode::Data(Data::Text),
|
||||
false,
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
wire.send(Frame::Frame(RawFrame::message(
|
||||
vec![0xad],
|
||||
OpCode::Data(Data::Continue),
|
||||
true,
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
if invalid {
|
||||
wire.send(Frame::Frame(RawFrame::message(
|
||||
vec![0xff],
|
||||
OpCode::Data(Data::Text),
|
||||
true,
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
wire.send(Frame::Frame(RawFrame::message(
|
||||
b"12345".to_vec(),
|
||||
OpCode::Data(Data::Text),
|
||||
false,
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
wire.send(Frame::Frame(RawFrame::message(
|
||||
b"6789".to_vec(),
|
||||
OpCode::Data(Data::Continue),
|
||||
true,
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
let _ = wire.next().await;
|
||||
})
|
||||
.unwrap();
|
||||
let socket = ws::connect(
|
||||
"127.0.0.1",
|
||||
i64::from(port),
|
||||
"/",
|
||||
&ws::Limits {
|
||||
message_bytes: 8,
|
||||
..limits()
|
||||
},
|
||||
scope,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
socket.receive().await.unwrap(),
|
||||
Some(Message::Text("中".into()))
|
||||
);
|
||||
assert!(socket.receive().await.is_err());
|
||||
assert!(socket.send(Message::Text("closed".into())).await.is_err());
|
||||
peer.wait().await.unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_accept_failure_can_send_http_error_and_http2_cannot_upgrade() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for http2 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 limits = Limits {
|
||||
http2: http2.then(http::Http2Limits::default),
|
||||
..Limits::default()
|
||||
};
|
||||
let server_limits = limits.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&server_limits,
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
|_, reply, scope| async move {
|
||||
assert!(
|
||||
ws::accept(&reply, &super::limits(), &scope).await.is_err()
|
||||
);
|
||||
reply
|
||||
.respond(Response {
|
||||
status: 400,
|
||||
headers: vec![],
|
||||
body: b"not a websocket".to_vec().into(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let response = http::send("127.0.0.1", port, request("/", &[]), &limits)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status, 400);
|
||||
assert_eq!(response.body.as_ref(), b"not a websocket");
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_upgrade_preserves_prefetched_frames_and_rejects_unmasked_clients() {
|
||||
runtime().run(async |scope| {
|
||||
let (listener, server) = server(scope).await;
|
||||
let port = listener.port().unwrap();
|
||||
scope.blocking(move || {
|
||||
let mut peer = TcpStream::connect(("127.0.0.1", port as u16)).unwrap();
|
||||
peer.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
|
||||
peer.set_write_timeout(Some(Duration::from_secs(2))).unwrap();
|
||||
let mut request = b"GET /echo HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n".to_vec();
|
||||
request.extend([0x81, 0x81, 1, 2, 3, 4, b'x' ^ 1]);
|
||||
peer.write_all(&request).unwrap();
|
||||
let mut peer = BufReader::new(peer);
|
||||
let mut line = String::new();
|
||||
peer.read_line(&mut line).unwrap();
|
||||
assert!(line.starts_with("HTTP/1.1 101"), "{line}");
|
||||
loop {
|
||||
line.clear();
|
||||
assert!(peer.read_line(&mut line).unwrap() > 0);
|
||||
if line == "\r\n" { break; }
|
||||
}
|
||||
let mut echo = [0; 3];
|
||||
peer.read_exact(&mut echo).unwrap();
|
||||
assert_eq!(echo, [0x81, 1, b'x']);
|
||||
peer.get_mut().write_all(&[0x81, 1, b'y']).unwrap();
|
||||
assert_eq!(peer.read(&mut echo).unwrap(), 0);
|
||||
}).await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
}).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
use super::shared_reference::{Limits, Owner, Socket};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::time::Duration;
|
||||
use tokio::{
|
||||
net::{TcpListener, TcpStream},
|
||||
sync::oneshot,
|
||||
};
|
||||
use tokio_tungstenite::{
|
||||
WebSocketStream,
|
||||
tungstenite::{Message, protocol::WebSocketConfig},
|
||||
};
|
||||
|
||||
fn limits() -> Limits {
|
||||
Limits {
|
||||
message: 16 * 1024 * 1024,
|
||||
idle: Duration::from_secs(2),
|
||||
write: Duration::from_secs(2),
|
||||
}
|
||||
}
|
||||
|
||||
async fn pair(limits: Limits) -> (Socket, Owner, WebSocketStream<TcpStream>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let config = WebSocketConfig::default()
|
||||
.max_message_size(Some(limits.message))
|
||||
.max_frame_size(Some(limits.message))
|
||||
.write_buffer_size(0);
|
||||
let client = async {
|
||||
let stream = TcpStream::connect(address).await.unwrap();
|
||||
stream.set_nodelay(true).unwrap();
|
||||
tokio_tungstenite::client_async_with_config(
|
||||
format!("ws://{address}/"),
|
||||
stream,
|
||||
Some(config),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
};
|
||||
let server = async {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
stream.set_nodelay(true).unwrap();
|
||||
tokio_tungstenite::accept_async_with_config(stream, Some(config))
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
let (client, server) = tokio::join!(client, server);
|
||||
let (socket, owner) = Socket::new(client, limits);
|
||||
(socket, owner, server)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_rust_reference_owner_exit_wakes_reader_and_releases_tcp() {
|
||||
let (socket, owner, mut peer) = pair(limits()).await;
|
||||
let alias = socket.clone();
|
||||
let reading = alias.receive();
|
||||
tokio::pin!(reading);
|
||||
assert!(futures_util::poll!(&mut reading).is_pending());
|
||||
drop(owner);
|
||||
assert!(
|
||||
tokio::time::timeout(Duration::from_millis(100), reading)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_err()
|
||||
);
|
||||
assert!(socket.send(Message::Text("closed".into())).await.is_err());
|
||||
assert!(
|
||||
tokio::time::timeout(Duration::from_millis(100), peer.next())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_rust_reference_full_duplex_aliases_preserve_messages() {
|
||||
let (socket, _owner, mut peer) = pair(limits()).await;
|
||||
let server = tokio::spawn(async move {
|
||||
while let Some(message) = peer.next().await {
|
||||
let message = message.unwrap();
|
||||
if message.is_close() {
|
||||
peer.flush().await.unwrap();
|
||||
break;
|
||||
}
|
||||
peer.send(message).await.unwrap();
|
||||
}
|
||||
});
|
||||
let sender = |writer: u8| {
|
||||
let socket = socket.clone();
|
||||
async move {
|
||||
for sequence in 0..16 {
|
||||
let mut bytes = vec![writer; 65536];
|
||||
bytes[1] = sequence;
|
||||
socket.send(Message::Binary(bytes.into())).await.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
let receiver = async {
|
||||
let mut sequences = [0_u8; 2];
|
||||
for _ in 0..32 {
|
||||
let Message::Binary(bytes) = socket.receive().await.unwrap().unwrap() else {
|
||||
panic!("binary expected")
|
||||
};
|
||||
let writer = usize::from(bytes[0]);
|
||||
assert_eq!(bytes[1], sequences[writer]);
|
||||
assert_eq!(bytes.len(), 65536);
|
||||
assert!(bytes[2..].iter().all(|byte| *byte == writer as u8));
|
||||
sequences[writer] += 1;
|
||||
}
|
||||
assert_eq!(sequences, [16, 16]);
|
||||
};
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
tokio::join!(sender(0), sender(1), receiver);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
socket.close(1000, "").await.unwrap();
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_rust_reference_close_has_priority_over_readable_reply() {
|
||||
let (socket, _owner, mut peer) = pair(limits()).await;
|
||||
let (sent, ready) = oneshot::channel();
|
||||
let server = tokio::spawn(async move {
|
||||
assert!(peer.next().await.unwrap().unwrap().is_close());
|
||||
peer.flush().await.unwrap();
|
||||
sent.send(()).unwrap();
|
||||
});
|
||||
let reading = socket.receive();
|
||||
tokio::pin!(reading);
|
||||
assert!(futures_util::poll!(&mut reading).is_pending());
|
||||
let closing = socket.close(1000, "");
|
||||
tokio::pin!(closing);
|
||||
assert!(futures_util::poll!(&mut closing).is_pending());
|
||||
ready.await.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
assert!(reading.await.is_err());
|
||||
closing.await.unwrap();
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_rust_reference_cancelled_writer_only_closes_after_lock() {
|
||||
let (socket, _owner, _peer) = pair(limits()).await;
|
||||
let mut writing = Box::pin(socket.send(Message::Binary(vec![1; limits().message].into())));
|
||||
assert!(futures_util::poll!(&mut writing).is_pending());
|
||||
let mut queued = Box::pin(socket.send(Message::Text("queued".into())));
|
||||
assert!(futures_util::poll!(&mut queued).is_pending());
|
||||
drop(queued);
|
||||
assert!(futures_util::poll!(&mut writing).is_pending());
|
||||
drop(writing);
|
||||
assert!(socket.send(Message::Text("closed".into())).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_rust_reference_limits_ping_and_idle_close() {
|
||||
let (socket, _owner, mut peer) = pair(Limits {
|
||||
message: 128,
|
||||
idle: Duration::from_millis(25),
|
||||
..limits()
|
||||
})
|
||||
.await;
|
||||
assert!(
|
||||
socket
|
||||
.send(Message::Binary(vec![0; 129].into()))
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
socket
|
||||
.send(Message::Ping(vec![0; 126].into()))
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
peer.send(Message::Ping(vec![7].into())).await.unwrap();
|
||||
assert!(socket.receive().await.unwrap().unwrap().is_ping());
|
||||
assert_eq!(
|
||||
peer.next().await.unwrap().unwrap(),
|
||||
Message::Pong(vec![7].into())
|
||||
);
|
||||
assert!(socket.receive().await.unwrap_err().contains("timed out"));
|
||||
assert!(socket.send(Message::Text("closed".into())).await.is_err());
|
||||
}
|
||||
Reference in new issue
Block a user