mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
fix(ext/websocket): cancel in-flight handshake on close() (#28598)
Fixes https://github.com/denoland/deno/issues/25126 --------- Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
This commit is contained in:
parent
f42b39d2cf
commit
66e03a39a3
5 changed files with 92 additions and 8 deletions
|
@ -116,6 +116,7 @@ const _binaryType = Symbol("[[binaryType]]");
|
|||
const _eventLoop = Symbol("[[eventLoop]]");
|
||||
const _sendQueue = Symbol("[[sendQueue]]");
|
||||
const _queueSend = Symbol("[[queueSend]]");
|
||||
const _cancelHandle = Symbol("[[cancelHandle]]");
|
||||
|
||||
const _server = Symbol("[[server]]");
|
||||
const _idleTimeoutDuration = Symbol("[[idleTimeout]]");
|
||||
|
@ -136,6 +137,7 @@ class WebSocket extends EventTarget {
|
|||
this[_idleTimeoutDuration] = 0;
|
||||
this[_idleTimeoutTimeout] = undefined;
|
||||
this[_sendQueue] = [];
|
||||
this[_cancelHandle] = undefined;
|
||||
|
||||
const prefix = "Failed to construct 'WebSocket'";
|
||||
webidl.requiredArguments(arguments.length, 1, prefix);
|
||||
|
@ -177,12 +179,6 @@ class WebSocket extends EventTarget {
|
|||
this[_url] = wsURL.href;
|
||||
this[_role] = CLIENT;
|
||||
|
||||
op_ws_check_permission_and_cancel_handle(
|
||||
"WebSocket.abort()",
|
||||
this[_url],
|
||||
false,
|
||||
);
|
||||
|
||||
if (typeof protocols === "string") {
|
||||
protocols = [protocols];
|
||||
}
|
||||
|
@ -214,11 +210,20 @@ class WebSocket extends EventTarget {
|
|||
);
|
||||
}
|
||||
|
||||
const cancelRid = op_ws_check_permission_and_cancel_handle(
|
||||
"WebSocket.abort()",
|
||||
this[_url],
|
||||
true,
|
||||
);
|
||||
|
||||
this[_cancelHandle] = cancelRid;
|
||||
|
||||
PromisePrototypeThen(
|
||||
op_ws_create(
|
||||
"new WebSocket()",
|
||||
wsURL.href,
|
||||
ArrayPrototypeJoin(protocols, ", "),
|
||||
cancelRid,
|
||||
),
|
||||
(create) => {
|
||||
this[_rid] = create.rid;
|
||||
|
@ -256,6 +261,12 @@ class WebSocket extends EventTarget {
|
|||
);
|
||||
this.dispatchEvent(errorEv);
|
||||
|
||||
if (this[_cancelHandle]) {
|
||||
core.tryClose(this[_cancelHandle]);
|
||||
|
||||
this[_cancelHandle] = undefined;
|
||||
}
|
||||
|
||||
const closeEv = new CloseEvent("close");
|
||||
this.dispatchEvent(closeEv);
|
||||
},
|
||||
|
@ -397,6 +408,13 @@ class WebSocket extends EventTarget {
|
|||
);
|
||||
}
|
||||
|
||||
if (this[_cancelHandle]) {
|
||||
// Cancel ongoing handshake.
|
||||
core.tryClose(this[_cancelHandle]);
|
||||
|
||||
this[_cancelHandle] = undefined;
|
||||
}
|
||||
|
||||
if (this[_readyState] === CONNECTING) {
|
||||
this[_readyState] = CLOSING;
|
||||
} else if (this[_readyState] === OPEN) {
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
import { assert, assertEquals, assertThrows, fail } from "./test_util.ts";
|
||||
import {
|
||||
assert,
|
||||
assertEquals,
|
||||
assertThrows,
|
||||
delay,
|
||||
fail,
|
||||
} from "./test_util.ts";
|
||||
|
||||
const servePort = 4248;
|
||||
const serveUrl = `ws://localhost:${servePort}/`;
|
||||
|
@ -822,3 +828,42 @@ Deno.test("send to a closed socket", async () => {
|
|||
};
|
||||
await promise;
|
||||
});
|
||||
|
||||
// https://github.com/denoland/deno/issues/25126
|
||||
Deno.test("websocket close ongoing handshake", async () => {
|
||||
// First try to close without any delay
|
||||
{
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
let gotError1 = false;
|
||||
const ws = new WebSocket("ws://localhost:4264");
|
||||
ws.onopen = () => fail();
|
||||
ws.onerror = (e) => {
|
||||
assertEquals((e as ErrorEvent).error.code, "EINTR");
|
||||
gotError1 = true;
|
||||
};
|
||||
ws.onclose = () => resolve();
|
||||
ws.close();
|
||||
await promise;
|
||||
assert(gotError1);
|
||||
}
|
||||
|
||||
await delay(50); // Wait a bit before trying again.
|
||||
|
||||
{
|
||||
const { promise: promise2, resolve: resolve2 } = Promise.withResolvers<
|
||||
void
|
||||
>();
|
||||
const ws2 = new WebSocket("ws://localhost:4264");
|
||||
ws2.onopen = () => fail();
|
||||
let gotError2 = false;
|
||||
ws2.onerror = (e) => {
|
||||
assertEquals((e as ErrorEvent).error.code, "EINTR");
|
||||
gotError2 = true;
|
||||
};
|
||||
ws2.onclose = () => resolve2();
|
||||
await delay(50); // wait a bit this time before calling close
|
||||
ws2.close();
|
||||
await promise2;
|
||||
assert(gotError2);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -323,7 +323,7 @@ async fn get_tcp_listener_stream(
|
|||
futures::stream::select_all(listeners)
|
||||
}
|
||||
|
||||
pub const TEST_SERVERS_COUNT: usize = 33;
|
||||
pub const TEST_SERVERS_COUNT: usize = 34;
|
||||
|
||||
#[derive(Default)]
|
||||
struct HttpServerCount {
|
||||
|
|
|
@ -84,6 +84,7 @@ const WS_PORT: u16 = 4242;
|
|||
const WSS_PORT: u16 = 4243;
|
||||
const WSS2_PORT: u16 = 4249;
|
||||
const WS_CLOSE_PORT: u16 = 4244;
|
||||
const WS_HANG_PORT: u16 = 4264;
|
||||
const WS_PING_PORT: u16 = 4245;
|
||||
const H2_GRPC_PORT: u16 = 4246;
|
||||
const H2S_GRPC_PORT: u16 = 4247;
|
||||
|
@ -120,6 +121,7 @@ pub async fn run_all_servers() {
|
|||
let ws_ping_server_fut = ws::run_ws_ping_server(WS_PING_PORT);
|
||||
let wss_server_fut = ws::run_wss_server(WSS_PORT);
|
||||
let ws_close_server_fut = ws::run_ws_close_server(WS_CLOSE_PORT);
|
||||
let ws_hang_server_fut = ws::run_ws_hang_handshake(WS_HANG_PORT);
|
||||
let wss2_server_fut = ws::run_wss2_server(WSS2_PORT);
|
||||
|
||||
let tls_server_fut = run_tls_server(TLS_PORT);
|
||||
|
@ -162,6 +164,7 @@ pub async fn run_all_servers() {
|
|||
tls_server_fut.boxed_local(),
|
||||
tls_client_auth_server_fut.boxed_local(),
|
||||
ws_close_server_fut.boxed_local(),
|
||||
ws_hang_server_fut.boxed_local(),
|
||||
another_redirect_server_fut.boxed_local(),
|
||||
auth_redirect_server_fut.boxed_local(),
|
||||
basic_auth_redirect_server_fut.boxed_local(),
|
||||
|
|
|
@ -62,6 +62,24 @@ pub async fn run_ws_close_server(port: u16) {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn run_ws_hang_handshake(port: u16) {
|
||||
let mut tcp = get_tcp_listener_stream("ws (hang handshake)", port).await;
|
||||
while let Some(Ok(mut stream)) = tcp.next().await {
|
||||
loop {
|
||||
let mut buf = [0; 1024];
|
||||
let n = stream.read(&mut buf).await;
|
||||
|
||||
if n.is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
if n.unwrap() == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_wss2_server(port: u16) {
|
||||
let mut tls = get_tls_listener_stream(
|
||||
"wss2 (tls)",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue