mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 19:08:50 +00:00
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:
@@ -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) {
|
||||
|
||||
108
test/js/node/http/http-websocket-upgrade.test.ts
Normal file
108
test/js/node/http/http-websocket-upgrade.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user