Fix WebSocket upgrade hanging - enable streaming for upgrade sockets

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 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-11-10 23:31:01 +00:00
parent c184b079bb
commit d493ce511f
2 changed files with 110 additions and 0 deletions

View File

@@ -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) {

View File

@@ -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<void>(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<void>((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<void>((resolve, reject) => {
server.close(err => {
if (err) reject(err);
else resolve();
});
});
});