Skip to content

node:http — res.headers['set-cookie'] collapses to a single value instead of an array #5079

Description

@proggeramlug

Summary

A response's set-cookie header must surface as a string array on IncomingMessage.headers, even for a single cookie. Perry collapses duplicate response headers into one value, so res.headers['set-cookie'] returns only the last cookie string.

Repro

import http from "node:http";

const server = http.createServer((_req, res) => {
  res.setHeader("Set-Cookie", ["a=1", "b=2"]);
  res.end("ok");
});
server.listen(0, () => {
  const port = (server.address() as any).port;
  http.get({ port }, (res: any) => {
    console.log("set-cookie", JSON.stringify(res.headers["set-cookie"]));
    res.on("data", () => {});
    res.on("end", () => server.close(() => console.log("closed")));
  });
});
setTimeout(() => {}, 1000);
# node v26
set-cookie ["a=1","b=2"]
closed

# perry
set-cookie "b=2"
closed

Root cause

IncomingMessageHandle.headers (crates/perry-ext-http/src/lib.rs) is a HashMap<String, String>, built by inserting each (k, v) pair from the reqwest response — duplicates clobber. Node's rule (per RFC 7230 §3.2.2 / the _http_incoming.js matchKnownFields logic):

  • set-cookiealways accumulated into an array;
  • a small set of single-value headers (content-length, content-type, host, authorization, …) → first value wins;
  • everything else → duplicates joined with , .

Fix sketch

Change IncomingMessageHandle to preserve header multiplicity (e.g. Vec<(String, String)> or a small multimap) and apply Node's combination rules in the res.headers getter (js_http_response_headers). headersDistinct / rawHeaders already round-trip the raw pairs, so the underlying data is available; this is about the combined-headers view. Touches the pub struct and its build sites across perry-ext-http (client) and the perry-ext-http-server server-side IncomingMessage, so both paths should be updated together.

Found while fixing client transport errors (#5078); deferred there to keep that PR focused.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions