-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
What version of Bun is running?
1.3.6-canary.49+604c83c8a
What platform is your computer?
Darwin 24.6.0 arm64 arm
What steps can reproduce the bug?
Option 1: Single file reproduction (Bun client)
Create a file repro.ts:
const server = Bun.serve({
port: 3456,
fetch(req, server) {
const protocols =
req.headers
.get("Sec-WebSocket-Protocol")
?.split(",")
.map((p) => p.trim()) || [];
server.upgrade(req, {
headers: { "Sec-WebSocket-Protocol": protocols[1] || protocols[0] },
});
},
websocket: {
open(ws) {
console.log("Server: opened");
},
close(ws) {
console.log("Server: closed");
},
},
});
await Bun.sleep(100);
const ws = new WebSocket("ws://localhost:3456", ["ocpp1.6", "ocpp2.0.1"]);
ws.onopen = () => {
console.log("Client: opened, protocol:", ws.protocol);
ws.close();
server.stop();
};
ws.onclose = (e) => {
console.log("Client: closed, code:", e.code, "reason:", e.reason);
server.stop();
};Run with:
bun run repro.tsOption 2: Node.js client test
You need Node for this, I installed it. Eww.
Anyway, this confirms that the bug is in Bun.serve and not the WebSocket client new WebSocket(...)
Create server.ts:
const server = Bun.serve({
port: 3456,
fetch(req, server) {
const protocols =
req.headers
.get("Sec-WebSocket-Protocol")
?.split(",")
.map((p) => p.trim()) || [];
const selected = protocols[1] || protocols[0];
server.upgrade(req, {
headers: { "Sec-WebSocket-Protocol": selected },
});
},
websocket: {
open(ws) {
console.log("Server: opened");
},
close(ws) {
console.log("Server: closed");
},
},
});
console.log("Listening on ws://localhost:3456");Create node-client.js:
const WebSocket = require("ws");
const ws = new WebSocket("ws://localhost:3456", ["ocpp1.6", "ocpp2.0.1"]);
ws.on("open", () => {
console.log("Node client: opened, protocol:", ws.protocol);
ws.close();
});
ws.on("error", (err) => {
console.log("Node client: error:", err.message);
});Run:
bub server.ts
node node-client.js What is the expected behavior?
According to RFC 6455 Section 4.2.2, the server should be able to select any subprotocol from the client's offered list by setting the Sec-WebSocket-Protocol response header.
Expected output:
Server: opened
Client: opened, protocol: ocpp2.0.1
Server: closed
The WebSocket connection should successfully establish with the negotiated protocol (ocpp2.0.1).
What do you see instead?
Server: opened
Client: closed, code: 1002 reason: Mismatch client protocol
The connection fails immediately after the server-side open handler fires. The client receives error code 1002 with reason "Mismatch client protocol", even though the server selected a valid protocol from the client's offered list.
Note: The issue occurs even when selecting the first protocol. The bug appears whenever the Sec-WebSocket-Protocol header is manually set in server.upgrade().
Workaround: If server.upgrade(req) is called without setting the header, Bun automatically selects the first protocol and the connection succeeds. This however prevents the server from being able to select the most appropriate protocol offered by the client.
Tested with Node.js client: A Node.js client using the 'ws' package also fails with error "Server sent an invalid subprotocol", confirming the bug is in Bun's server implementation, not the client.
Additional information
Tested on Bun version: v1.3.6-canary.42+603bbd18a, 1.3.6-canary.49+604c83c8a, v1.3.5 and v1.0.0.
Bun's server is not properly setting the Sec-WebSocket-Protocol response header during the upgrade, despite the code explicitly requesting it. Both Bun's WebSocket client and Node.js 'ws' client reject the connection, indicating the server's response is not RFC 6455 compliant. The server likely either:
- Not sending the header at all in the 101 Switching Protocols response, OR
- Sending a malformed/incorrect protocol value