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