fix(http): fix setHeaders throwing ERR_HTTP_HEADERS_SENT on new requests

The `setHeaders` method used `this[headerStateSymbol] !== NodeHTTPHeaderState.none`
which threw when `headerStateSymbol` was undefined (ClientRequest never calls the
OutgoingMessage constructor) and was also stricter than Node.js. Align the check
with `setHeader` (singular) by only throwing when headers have actually been sent.

Closes #27049

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-02-15 16:18:40 +00:00
parent 38f41dccdf
commit 34ef6c9d29
2 changed files with 86 additions and 1 deletions

View File

@@ -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");
}

View File

@@ -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<string>();
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<string>();
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<string>();
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");
});