Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
19031dd214 fix(server): materialize Request properties before WebSocket upgrade
When a Request object is passed as websocket data via server.upgrade(),
its lazily-loaded properties (url, headers) were lost because the
underlying uws request reference was severed without materializing them
first. Now ensureURL() and setFetchHeaders() are called before nullifying
request_context, matching the behavior of the normal async request path.

Closes #27927

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 16:31:25 +00:00
2 changed files with 67 additions and 0 deletions

View File

@@ -992,6 +992,19 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d
}
}
// Materialize any lazily-loaded Request properties before we
// sever the link to the underlying uws request, so that the
// Request object remains usable after the upgrade (e.g. when
// stored in websocket data). See #27927.
request.ensureURL() catch {
request.url = bun.String.empty;
};
if (!request.hasFetchHeaders()) {
if (upgrader.req) |req| {
request.setFetchHeaders(.createFromUWS(req));
}
}
// --- After this point, do not throw an exception
// See https://github.com/oven-sh/bun/issues/1339

View File

@@ -0,0 +1,54 @@
import { expect, test } from "bun:test";
// https://github.com/oven-sh/bun/issues/27927
// Request passed as websocket data should retain all properties
// even if they were not accessed before server.upgrade().
test("Request passed as websocket data retains url and headers without prior access", async () => {
type WebSocketData = { req: Request };
let wsDataResolve: (value: { url: string; method: string; headerKeys: string[] }) => void;
const wsDataPromise = new Promise<{ url: string; method: string; headerKeys: string[] }>(resolve => {
wsDataResolve = resolve;
});
using server = Bun.serve({
port: 0,
fetch(req, server) {
// Intentionally do NOT access req.url or req.headers before upgrade
server.upgrade(req, {
data: { req },
});
return undefined;
},
websocket: {
data: {} as WebSocketData,
message(ws) {
const req = ws.data.req;
wsDataResolve({
url: req.url,
method: req.method,
headerKeys: [...req.headers.keys()],
});
ws.close();
},
},
});
const ws = new WebSocket(`ws://localhost:${server.port}/test-path`);
await new Promise<void>((resolve, reject) => {
ws.onopen = () => {
ws.send("hello");
};
ws.onclose = () => resolve();
ws.onerror = err => reject(err);
});
const data = await wsDataPromise;
expect(data.url).toBe(`http://localhost:${server.port}/test-path`);
expect(data.method).toBe("GET");
expect(data.headerKeys).toContain("host");
expect(data.headerKeys).toContain("upgrade");
expect(data.headerKeys).toContain("sec-websocket-key");
});