From a5a2571a81275d68c81312e5af8dc6b765582d33 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Fri, 20 Feb 2026 04:41:53 +0000 Subject: [PATCH] fix(node:http): socket.write not sending data in upgrade event handler Enable streaming on the socket before emitting the "upgrade" event, matching the existing behavior for CONNECT requests. Without this, handle.ondrain was undefined so _write() silently skipped calling handle.write(). Also skip socket.cork() for upgrade connections since the socket is handed off to user code and data should flow immediately. Closes #9882 Co-Authored-By: Claude --- src/js/node/_http_server.ts | 5 +- test/regression/issue/09882.test.ts | 162 ++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 test/regression/issue/09882.test.ts diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index ae646dcd418..3a336c0fb8c 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -602,6 +602,7 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort http_res.end(); socket.destroy(); } else if (is_upgrade) { + socket[kEnableStreaming](true); server.emit("upgrade", http_req, socket, kEmptyBuffer); if (!socket._httpMessage) { if (canUseInternalAssignSocket) { @@ -629,7 +630,9 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort server.emit("request", http_req, http_res); } - socket.cork(); + if (!is_upgrade) { + socket.cork(); + } if (handle.finished || didFinish) { handle = undefined; diff --git a/test/regression/issue/09882.test.ts b/test/regression/issue/09882.test.ts new file mode 100644 index 00000000000..0ed1d211661 --- /dev/null +++ b/test/regression/issue/09882.test.ts @@ -0,0 +1,162 @@ +import { expect, test } from "bun:test"; +import http from "node:http"; +import net from "node:net"; + +test("socket.write sends data in http upgrade event handler", async () => { + const { promise, resolve, reject } = Promise.withResolvers(); + + const server = http.createServer(); + server.on("upgrade", (_req, socket) => { + socket.write("x", () => { + // After the write completes, close the socket + socket.end(); + }); + }); + + server.listen(0, "127.0.0.1", () => { + const addr = server.address() as net.AddressInfo; + + const client = net.createConnection(addr.port, "127.0.0.1", () => { + client.write( + "GET / HTTP/1.1\r\n" + + "Host: 127.0.0.1\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + + "Sec-WebSocket-Version: 13\r\n" + + "\r\n", + ); + }); + + let received = ""; + client.on("data", (data: Buffer) => { + received += data.toString(); + }); + + client.on("end", () => { + try { + expect(received).toBe("x"); + resolve(); + } catch (e) { + reject(e); + } finally { + server.close(); + } + }); + + client.on("error", (err: Error) => { + server.close(); + reject(err); + }); + }); + + await promise; +}); + +test("socket.write with 101 handshake in http upgrade event handler", async () => { + const { promise, resolve, reject } = Promise.withResolvers(); + + const server = http.createServer(); + server.on("upgrade", (_req, socket) => { + socket.write( + "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\nhello from upgrade", + () => { + socket.end(); + }, + ); + }); + + server.listen(0, "127.0.0.1", () => { + const addr = server.address() as net.AddressInfo; + + const client = net.createConnection(addr.port, "127.0.0.1", () => { + client.write( + "GET / HTTP/1.1\r\n" + + "Host: 127.0.0.1\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + + "Sec-WebSocket-Version: 13\r\n" + + "\r\n", + ); + }); + + let received = ""; + client.on("data", (data: Buffer) => { + received += data.toString(); + }); + + client.on("end", () => { + try { + expect(received).toContain("HTTP/1.1 101 Switching Protocols"); + expect(received).toContain("hello from upgrade"); + resolve(); + } catch (e) { + reject(e); + } finally { + server.close(); + } + }); + + client.on("error", (err: Error) => { + server.close(); + reject(err); + }); + }); + + await promise; +}); + +test("multiple socket.write calls in http upgrade event handler", async () => { + const { promise, resolve, reject } = Promise.withResolvers(); + + const server = http.createServer(); + server.on("upgrade", (_req, socket) => { + socket.write("first"); + socket.write("second"); + socket.write("third", () => { + socket.end(); + }); + }); + + server.listen(0, "127.0.0.1", () => { + const addr = server.address() as net.AddressInfo; + + const client = net.createConnection(addr.port, "127.0.0.1", () => { + client.write( + "GET / HTTP/1.1\r\n" + + "Host: 127.0.0.1\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + + "Sec-WebSocket-Version: 13\r\n" + + "\r\n", + ); + }); + + let received = ""; + client.on("data", (data: Buffer) => { + received += data.toString(); + }); + + client.on("end", () => { + try { + expect(received).toContain("first"); + expect(received).toContain("second"); + expect(received).toContain("third"); + resolve(); + } catch (e) { + reject(e); + } finally { + server.close(); + } + }); + + client.on("error", (err: Error) => { + server.close(); + reject(err); + }); + }); + + await promise; +});