diff options
-rw-r--r-- | ext/websocket/01_websocket.js | 12 | ||||
-rw-r--r-- | ext/websocket/lib.rs | 8 | ||||
-rw-r--r-- | tests/unit/websocket_test.ts | 44 |
3 files changed, 62 insertions, 2 deletions
diff --git a/ext/websocket/01_websocket.js b/ext/websocket/01_websocket.js index afe543da5..60580a56c 100644 --- a/ext/websocket/01_websocket.js +++ b/ext/websocket/01_websocket.js @@ -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: { diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index 87503120b..5c3e8d7b1 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -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())); diff --git a/tests/unit/websocket_test.ts b/tests/unit/websocket_test.ts index 74e85052e..362957b2d 100644 --- a/tests/unit/websocket_test.ts +++ b/tests/unit/websocket_test.ts @@ -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(); +}); |