Skip to content

fix(http): surface res.headers['set-cookie'] as an array (#5079)#5102

Merged
proggeramlug merged 4 commits into
mainfrom
fix/set-cookie-array-5079
Jun 14, 2026
Merged

fix(http): surface res.headers['set-cookie'] as an array (#5079)#5102
proggeramlug merged 4 commits into
mainfrom
fix/set-cookie-array-5079

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #5079.

Problem

IncomingMessage.headers was a HashMap<String,String> built by inserting each (name, value) pair from the response — duplicates clobbered. So res.headers['set-cookie'] returned only the last cookie string instead of the spec-mandated string array, and other repeated headers lost their earlier values.

Fix

Preserve header multiplicity and apply Node's _http_incoming.js matchKnownFields combination rules when building the combined headers view:

  • set-cookiealways a string array, even for a single cookie;
  • single-value fields (content-length, content-type, host, authorization, …) → first value wins;
  • everything else → duplicates joined with , .

Client (perry-ext-http): IncomingMessageHandle.headers is now a Vec<(String,String)> (arrival order, multiplicity preserved). The res.headers getter builds the JS object via the new build_response_headers_object, emitting a real array for set-cookie. It's the only reader of that field, so the change is contained.

Server (perry-ext-http-server): js_node_http_im_headers_json builds the combined object from raw_headers (already round-tripped for rawHeaders/headersDistinct) via combined_headers_json, so server-side req.headers['set-cookie'] is likewise an array.

Verification

Issue repro now prints:

set-cookie ["a=1","b=2"]
content-type undefined
closed

(content-type undefined matches Node — the repro server never sets it, confirming single-value passthrough is intact.)

Existing perry-ext-http / perry-ext-http-server unit tests pass (23 passed, 0 failed).

No version bump / changelog per maintainer instruction — to be folded in at merge.

Summary by CodeRabbit

  • Bug Fixes
    • Improved HTTP header handling to preserve arrival order and original header name casing.
    • Updated response headers rendering to follow Node.js-style known-field merging, including correct duplicate handling for cookie and other multi-value headers.
    • Ensured set-cookie is consistently exposed as an array, and malformed or missing header data now safely falls back.
  • Tests
    • Updated test scaffolding and GC/listener assertions to match the revised incoming header representation.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Perry's HTTP header representation is updated to preserve multiplicity by changing IncomingMessageHandle.headers from a HashMap to an ordered Vec<(String, String)>, with both client and server sides implementing Node.js matchKnownFields rules: set-cookie is always accumulated as an array, single-value headers keep the first occurrence, and other duplicate headers are joined with ", ".

Changes

HTTP Header Multiplicity and Node.js Rules

Layer / File(s) Summary
Core data structure and client-side header merging logic
crates/perry-ext-http/src/lib.rs, crates/perry-ext-http/src/response_headers.rs
IncomingMessageHandle.headers changes from HashMap<String, String> to Vec<(String, String)> to preserve order and multiplicity. New module response_headers provides is_single_value_header helper (classifying Node matchKnownFields single-value headers) and build_response_headers_object function implementing Node-style merging: set-cookie always accumulates as an array, known single-value headers keep first value, cookie duplicates join with "; ", others join with ", ".
Client event handlers and response headers accessor
crates/perry-ext-http/src/client_events.rs, crates/perry-ext-http/src/lib.rs
Client events handle_response_event and handle_response_head_event pass raw headers: Vec<(String, String)> directly without intermediate HashMap construction. js_http_response_headers accessor uses the new build_response_headers_object builder.
Server request header JSON serialization with Node rules
crates/perry-ext-http-server/src/request.rs
New is_single_value_header and combined_headers_json helpers apply the same Node-style merging for server request headers. js_node_http_im_headers_json serializes via combined_headers_json instead of the original header map, with "{}" fallback on serialization failure.
Test struct initialization
crates/perry-ext-http/src/tests.rs
Update IncomingMessageHandle header field initialization in gc_mutable_scanner_rewrites_request_response_listener_roots from HashMap::new() to Vec::new().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Headers dance in ordered pairs,
No more HashMap, just vectors fair!
Set-cookie blooms in arrays bright,
Node's matchKnownFields done just right! 🍪

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(http): surface res.headers[\'set-cookie\'] as an array (#5079)' directly and clearly describes the main change—making set-cookie headers appear as an array.
Description check ✅ Passed The PR description clearly outlines the problem, fix, and verification, though some test plan checkboxes are unchecked and screenshots/output section appears absent.
Linked Issues check ✅ Passed All code changes implement the requirements from issue #5079: set-cookie is now always an array, single-value headers use first value, duplicates are joined with commas, and both client and server paths apply these rules consistently.
Out of Scope Changes check ✅ Passed All changes directly support issue #5079's objectives. Changes to IncomingMessageHandle, response_headers.rs, and related client/server implementations are all within scope of preserving header multiplicity and implementing Node's combination rules.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/set-cookie-array-5079

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-ext-http-server/src/request.rs`:
- Around line 290-321: combined_headers_json currently joins duplicate
non-set-cookie headers with ", " and doesn't treat Cookie specially; update the
function combined_headers_json to handle key == "cookie" like set-cookie is
handled but by concatenating cookie values with "; " instead of ", ": when
encountering key == "cookie", if map.get_mut(&key) is
Some(Value::String(existing)) append "; " + value, otherwise insert
Value::String(value.clone()); keep the existing set-cookie array behavior and
existing is_single_value_header logic for other headers unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b966bfc4-66c8-45c3-ab08-79559dc18cb7

📥 Commits

Reviewing files that changed from the base of the PR and between 2cfa8ca and 01f4bf9.

📒 Files selected for processing (4)
  • crates/perry-ext-http-server/src/request.rs
  • crates/perry-ext-http/src/client_events.rs
  • crates/perry-ext-http/src/lib.rs
  • crates/perry-ext-http/src/tests.rs

Comment thread crates/perry-ext-http-server/src/request.rs
@proggeramlug proggeramlug force-pushed the fix/set-cookie-array-5079 branch from c7ff397 to 33f7a29 Compare June 14, 2026 06:06
Ralph Küpper added 4 commits June 14, 2026 09:32
`IncomingMessage.headers` collapsed duplicate response headers into a
single `HashMap<String,String>` value, so `res.headers['set-cookie']`
returned only the last cookie and any repeated header lost its earlier
values.

Preserve header multiplicity and apply Node's `_http_incoming.js`
`matchKnownFields` combination rules when building the combined
`headers` view:

- `set-cookie` -> **always** a string array, even for a single cookie;
- single-value fields (content-length, content-type, host, ...) ->
  first value wins;
- everything else -> duplicates joined with `, `.

Client side (perry-ext-http): `IncomingMessageHandle.headers` becomes a
`Vec<(String,String)>` (arrival order, multiplicity preserved); the
`res.headers` getter builds the JS object via the new
`build_response_headers_object`, emitting a real array for set-cookie.

Server side (perry-ext-http-server): `js_node_http_im_headers_json`
now builds the combined object from `raw_headers` (which already
round-trips for rawHeaders/headersDistinct) via `combined_headers_json`,
so server-side `req.headers['set-cookie']` is likewise an array.

Verified against the issue repro (now prints `["a=1","b=2"]`); existing
ext-http/ext-http-server unit tests pass.
@proggeramlug proggeramlug force-pushed the fix/set-cookie-array-5079 branch from 33f7a29 to 3ffbb56 Compare June 14, 2026 07:33
@proggeramlug proggeramlug merged commit 46b732b into main Jun 14, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/set-cookie-array-5079 branch June 14, 2026 08:44
proggeramlug pushed a commit that referenced this pull request Jun 14, 2026
Rolls up the issue-fix batch merged on top of 0.5.1165 (#5102, #5103,
#5105, #5106, #5107, #5108, #5109, #5110, #5112, #5117). See CHANGELOG
for the per-PR breakdown.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant