From d493ce511f5987af4974f6c4387327fd4e151bb6 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Mon, 10 Nov 2025 23:31:01 +0000 Subject: [PATCH] Fix WebSocket upgrade hanging - enable streaming for upgrade sockets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the WebSocket hanging issue where clients would never receive the upgrade response from the server. The problem was that upgrade sockets weren't configured for streaming, so writes to the socket wouldn't actually send data. Changes: - Added socket[kEnableStreaming](true) before emitting "upgrade" event - This enables handle.ondrain which allows socket writes to work properly - Previously, socket.write() would return true but bytesWritten stayed 0 The fix matches the pattern used for CONNECT method (line 548) where streaming is already enabled. Added comprehensive test in http-websocket-upgrade.test.ts that: - Creates an HTTP server with upgrade handler - Sends WebSocket upgrade request - Verifies client receives "101 Switching Protocols" response - Test passes with fix, times out without fix This resolves the code-server WebSocket hanging issue where the WebSocket would connect but never send/receive messages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/js/node/_http_server.ts | 2 + .../node/http/http-websocket-upgrade.test.ts | 108 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 test/js/node/http/http-websocket-upgrade.test.ts diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 5327cb01cc..e7f60c5450 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -611,6 +611,8 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort http_res.end(); socket.destroy(); } else if (is_upgrade) { + // Enable streaming for WebSocket/upgrade connections + socket[kEnableStreaming](true); server.emit("upgrade", http_req, socket, kEmptyBuffer); if (!socket._httpMessage) { if (canUseInternalAssignSocket) { diff --git a/test/js/node/http/http-websocket-upgrade.test.ts b/test/js/node/http/http-websocket-upgrade.test.ts new file mode 100644 index 0000000000..289d185035 --- /dev/null +++ b/test/js/node/http/http-websocket-upgrade.test.ts @@ -0,0 +1,108 @@ +import { expect, test } from "bun:test"; +import { createHash } from "node:crypto"; +import { createServer } from "node:http"; +import { Socket } from "node:net"; + +test("http.Server WebSocket upgrade sends response correctly", async () => { + const server = createServer((req, res) => { + res.writeHead(200); + res.end("OK"); + }); + + let upgradeReceived = false; + let clientReceivedUpgrade = false; + + server.on("upgrade", (request, socket, head) => { + upgradeReceived = true; + + const key = request.headers["sec-websocket-key"]; + expect(key).toBeTruthy(); + + if (!key) { + socket.destroy(); + return; + } + + // Generate WebSocket accept key + const acceptKey = createHash("sha1") + .update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + .digest("base64"); + + // Send WebSocket upgrade response + const headers = [ + "HTTP/1.1 101 Switching Protocols", + "Upgrade: websocket", + "Connection: Upgrade", + `Sec-WebSocket-Accept: ${acceptKey}`, + "", + "", + ].join("\r\n"); + + socket.write(headers); + }); + + await new Promise(resolve => { + server.listen(0, () => { + resolve(); + }); + }); + + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("Server address is not valid"); + } + + // Create WebSocket client + const client = new Socket(); + + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Test timeout - client never received upgrade response")); + }, 2000); + + client.connect(address.port, "127.0.0.1", () => { + // Send WebSocket handshake + const key = Buffer.from("testkey123456789").toString("base64"); + const handshake = [ + "GET /test HTTP/1.1", + "Host: localhost", + "Upgrade: websocket", + "Connection: Upgrade", + `Sec-WebSocket-Key: ${key}`, + "Sec-WebSocket-Version: 13", + "", + "", + ].join("\r\n"); + + client.write(handshake); + }); + + client.on("data", data => { + const response = data.toString(); + + if (response.includes("101 Switching Protocols")) { + clientReceivedUpgrade = true; + clearTimeout(timeout); + client.end(); + resolve(); + } + }); + + client.on("error", err => { + clearTimeout(timeout); + reject(err); + }); + }); + + // Verify both server and client processed the upgrade + expect(upgradeReceived).toBe(true); + expect(clientReceivedUpgrade).toBe(true); + + // Clean up + await new Promise((resolve, reject) => { + server.close(err => { + if (err) reject(err); + else resolve(); + }); + }); +});