mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 14:22:01 +00:00
fix(node:http): fall back upgrade requests to 'request' event when no 'upgrade' listener
When a WebSocket/upgrade request is sent to an `http.createServer()` server
that has no 'upgrade' event listener, Node.js falls back to emitting a
'request' event. Bun was unconditionally emitting the 'upgrade' event, which
was silently dropped when no listener existed.
Check `server.listenerCount("upgrade")` before deciding to emit the upgrade
event, and fall through to normal request handling when no listeners exist.
Closes #26924
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
111
test/regression/issue/26924.test.ts
Normal file
111
test/regression/issue/26924.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user