diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index ae646dcd41..63e4fc62d4 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -582,7 +582,8 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort socket[kRequest] = http_req; const is_upgrade = http_req.headers.upgrade; - if (!is_upgrade) { + const hasUpgradeListeners = is_upgrade && server.listenerCount("upgrade") > 0; + if (!hasUpgradeListeners) { if (canUseInternalAssignSocket) { // ~10% performance improvement in JavaScriptCore due to avoiding .once("close", ...) and removing a listener assignSocketInternal(http_res, socket); @@ -601,7 +602,7 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort http_res.writeHead(503); http_res.end(); socket.destroy(); - } else if (is_upgrade) { + } else if (hasUpgradeListeners) { server.emit("upgrade", http_req, socket, kEmptyBuffer); if (!socket._httpMessage) { if (canUseInternalAssignSocket) { diff --git a/test/regression/issue/26924.test.ts b/test/regression/issue/26924.test.ts new file mode 100644 index 0000000000..b24f1daa56 --- /dev/null +++ b/test/regression/issue/26924.test.ts @@ -0,0 +1,111 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +test("node:http server falls back upgrade request to 'request' event when no 'upgrade' listener", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` +const http = require('node:http'); + +const server = http.createServer(); +const events = []; + +server.on('request', (req, res) => { + events.push('request'); + res.end(); +}); + +// No 'upgrade' listener registered + +server.listen(0, function() { + const port = this.address().port; + // Send a request with Upgrade header using http module + const req = http.request({ + hostname: 'localhost', + port, + path: '/', + method: 'GET', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + }, + }, (res) => { + events.push('response'); + res.resume(); + res.on('end', () => { + console.log(JSON.stringify(events)); + server.close(); + }); + }); + req.end(); +}); +`, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + const events = JSON.parse(stdout.trim()); + expect(events).toEqual(["request", "response"]); + expect(exitCode).toBe(0); +}); + +test("node:http server emits 'upgrade' event when listener is registered", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + ` +const http = require('node:http'); + +const server = http.createServer(); +const events = []; + +server.on('request', (req, res) => { + events.push('request'); + res.end(); +}); + +server.on('upgrade', (req, socket) => { + events.push('upgrade'); + socket.end(); +}); + +server.listen(0, function() { + const port = this.address().port; + const req = http.request({ + hostname: 'localhost', + port, + path: '/', + method: 'GET', + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + }, + }); + req.on('error', () => {}); + req.end(); + // Give the server time to process + setTimeout(() => { + console.log(JSON.stringify(events)); + server.close(); + }, 500); +}); +`, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + const events = JSON.parse(stdout.trim()); + expect(events).toEqual(["upgrade"]); + expect(exitCode).toBe(0); +});