Skip to content

Commit

Permalink
fix(ext/websocket): drop connection when close frame not ack (denolan…
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy authored and sbmsr committed Jul 2, 2024
1 parent 032f8d0 commit c82ea62
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
12 changes: 12 additions & 0 deletions ext/websocket/01_websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,18 @@ class WebSocket extends EventTarget {
const rid = this[_rid];
while (this[_readyState] !== CLOSED) {
const kind = await op_ws_next_event(rid);
/* close the connection if read was cancelled, and we didn't get a close frame */
if (
(this[_readyState] == CLOSING) &&
kind <= 3 && this[_role] !== CLIENT
) {
this[_readyState] = CLOSED;

const event = new CloseEvent("close");
this.dispatchEvent(event);
core.tryClose(rid);
break;
}

switch (kind) {
case 0: {
Expand Down
8 changes: 6 additions & 2 deletions ext/websocket/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,10 +699,14 @@ pub async fn op_ws_close(
#[smi] code: Option<u16>,
#[string] reason: Option<String>,
) -> Result<(), AnyError> {
let resource = state
let Ok(resource) = state
.borrow_mut()
.resource_table
.get::<ServerWebSocket>(rid)?;
.get::<ServerWebSocket>(rid)
else {
return Ok(());
};

let frame = reason
.map(|reason| Frame::close(code.unwrap_or(1005), reason.as_bytes()))
.unwrap_or_else(|| Frame::close_raw(vec![].into()));
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/websocket_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,3 +761,47 @@ Deno.test("Close without frame", async () => {
};
await promise;
});

Deno.test("Close connection", async () => {
const ac = new AbortController();
const listeningDeferred = Promise.withResolvers<void>();

const server = Deno.serve({
handler: (req) => {
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onmessage = function (e) {
socket.close(1008);
assertEquals(e.data, "Hello");
};
socket.onclose = () => {
ac.abort();
};
socket.onerror = () => fail();
return response;
},
signal: ac.signal,
onListen: () => listeningDeferred.resolve(),
hostname: "localhost",
port: servePort,
});

await listeningDeferred.promise;

const conn = await Deno.connect({ port: servePort, hostname: "localhost" });
await conn.write(
new TextEncoder().encode(
"GET / HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n",
),
);

// Write a 2 text frame saying "Hello"
await conn.write(new Uint8Array([0x81, 0x05]));
await conn.write(new TextEncoder().encode("Hello"));

// We are a bad client so we won't acknowledge the close frame
await conn.write(new Uint8Array([0x81, 0x05]));
await conn.write(new TextEncoder().encode("Hello"));

await server.finished;
conn.close();
});

0 comments on commit c82ea62

Please sign in to comment.