diff --git a/src/js/node/_http_outgoing.ts b/src/js/node/_http_outgoing.ts index 8a695ce873..c297412dbd 100644 --- a/src/js/node/_http_outgoing.ts +++ b/src/js/node/_http_outgoing.ts @@ -271,7 +271,7 @@ const OutgoingMessagePrototype = { return this; }, setHeaders(headers) { - if (this._header || this[headerStateSymbol] !== NodeHTTPHeaderState.none) { + if ((this._header !== undefined && this._header !== null) || this[headerStateSymbol] == NodeHTTPHeaderState.sent) { throw $ERR_HTTP_HEADERS_SENT("set"); } diff --git a/test/regression/issue/27049.test.ts b/test/regression/issue/27049.test.ts new file mode 100644 index 0000000000..209db5df21 --- /dev/null +++ b/test/regression/issue/27049.test.ts @@ -0,0 +1,85 @@ +import { expect, test } from "bun:test"; +import http from "node:http"; + +test("ClientRequest.setHeaders should not throw ERR_HTTP_HEADERS_SENT on new request", async () => { + await using server = Bun.serve({ + port: 0, + fetch(req) { + return new Response(req.headers.get("x-test") ?? "missing"); + }, + }); + + const { resolve, reject, promise } = Promise.withResolvers(); + + const req = http.request(`http://localhost:${server.port}/test`, { method: "GET" }, res => { + let data = ""; + res.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + res.on("end", () => resolve(data)); + }); + + req.on("error", reject); + + // This should not throw - headers haven't been sent yet + req.setHeaders(new Headers({ "x-test": "value" })); + + req.end(); + + const body = await promise; + expect(body).toBe("value"); +}); + +test("ClientRequest.setHeaders works with Map", async () => { + await using server = Bun.serve({ + port: 0, + fetch(req) { + return new Response(req.headers.get("x-map-test") ?? "missing"); + }, + }); + + const { resolve, reject, promise } = Promise.withResolvers(); + + const req = http.request(`http://localhost:${server.port}/test`, { method: "GET" }, res => { + let data = ""; + res.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + res.on("end", () => resolve(data)); + }); + + req.on("error", reject); + + req.setHeaders(new Map([["x-map-test", "map-value"]])); + + req.end(); + + const body = await promise; + expect(body).toBe("map-value"); +}); + +test("ServerResponse.setHeaders should not throw before headers are sent", async () => { + const { resolve, reject, promise } = Promise.withResolvers(); + + const server = http.createServer((req, res) => { + // This should not throw - headers haven't been sent yet + res.setHeaders(new Headers({ "x-custom": "server-value" })); + res.writeHead(200); + res.end("ok"); + }); + + server.listen(0, () => { + const port = (server.address() as any).port; + const req = http.request(`http://localhost:${port}/test`, res => { + resolve(res.headers["x-custom"] as string); + server.close(); + }); + req.on("error", e => { + server.close(); + reject(e); + }); + req.end(); + }); + + expect(await promise).toBe("server-value"); +});