[PR #5394] [MERGED] fix(relay): multiple Set-Cookie headers in resp #5181

Closed
opened 2026-03-17 02:39:17 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/hoppscotch/hoppscotch/pull/5394
Author: @CuriousCorrelation
Created: 9/21/2025
Status: Merged
Merged: 9/23/2025
Merged by: @jamesgeorge007

Base: nextHead: relay-fix-multi-value-set-cookie-headers


📝 Commits (4)

  • e139b76 fix(relay): multiple Set-Cookie headers in resp
  • 1b84e78 fix: typos
  • 1f12609 fix: doc for defensive handling in resp transform
  • bd0a2df chore: cleanup

📊 Changes

8 files changed (+76 additions, -15 deletions)

View changed files

📝 packages/hoppscotch-agent/src-tauri/Cargo.lock (+1 -1)
📝 packages/hoppscotch-common/src/helpers/kernel/rest/response.ts (+49 -3)
📝 packages/hoppscotch-desktop/plugin-workspace/relay/src/transfer.rs (+16 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock (+1 -1)
📝 packages/hoppscotch-desktop/src-tauri/Cargo.lock (+2 -2)
📝 packages/hoppscotch-desktop/src-tauri/Cargo.toml (+1 -1)
📝 packages/hoppscotch-kernel/package.json (+1 -1)
📝 pnpm-lock.yaml (+5 -5)

📄 Description

This is an experimental way to fix multiple Set-Cookie headers being
lost during HTTP response pre/post-processing by concat-ing them with
\n in the networking layer, so all cookies to be displayed in the
response headers panel instead of only the first one being visible.

Before After
image image

NOTE: This is an experimental workaround for the time being.
\n is used here as a separator because HTTP headers are inherently
line-oriented (\r\n on the wire). This makes it trivial to split back
into distinct Set-Cookie headers without violating RFC rules. The
next idea would be using a custom delimiter but it also would be
arbitrary and risk breaking if that delimiter ever appeared in a cookie
value. The main trade-off is that this is a fragile workaround,
unusual edge cases (e.g. malformed cookies containing newlines, or
components that normalize headers differently) may fail. A full fix
would require changing the header storage structure to support multiple
values per key (the rough idea around that is explained in FE-1008).

Closes FE-1008 (phase-1)
Closes FE-309
Closes #1546 (multiple Set-Cookie headers missing in response display)
Closes #5344 (ignoring one Set-Cookie when response has 2 values)
Closes #5375 (only shows first Set-Cookie when multiple cookies present)
Partially addresses #3532 (fixes header display, automatic cookie
storage will be handled separately).

When servers returned multiple Set-Cookie headers, only the first
header was preserved while subsequent ones were discarded.

The issue occurres because HashMap<String, String> data structure in
TransferHandler could only store one value per header name (by the
very nature of hashmaps or records for instance).

What's changed

Relay networking layer

The header processing in transfer.rs now includes special handling
just for set-cookie headers (case-insensitive accumulator). When
multiple Set-Cookie headers are encountered, they are concatenated with
newlines (why is explained above).

FE processing

Added processHeaders function in response.ts that
splits concatenated Set-Cookie headers back into separate header
entries.

Dependencies

Updated relay and tauri-plugin-relay to commits that include the
Set-Cookie concatenation fix across all target packages.

Testing

Test with any endpoints that set multiple cookies:

curl -i http://localhost:3000/multiple-cookies

or perhaps use httpbin:

curl -i -G \
--data-urlencode "Set-Cookie=sessionid=abc123; Path=/" \
--data-urlencode "Set-Cookie=csrftoken=xyz789; Path=/" \
--data-urlencode "Set-Cookie=userid=user456; Path=/" \
"https://httpbin.org/response-headers"

or even a custom server script:

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/multiple-cookies') {
    res.setHeader('Set-Cookie', [
      'sessionid=abc123; Path=/',
      'csrftoken=xyz789; Path=/',
      'userid=user456; Path=/'
    ]);
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ message: 'Multiple cookies set' }));
  }
});

Expected response headers:

Set-Cookie: sessionid=abc123; Path=/
Set-Cookie: csrftoken=xyz789; Path=/
Set-Cookie: userid=user456; Path=/

In desktop app response headers panel, all three cookies now appear as
separate header entries instead of only the first one being
visible.

Special Note On Backwards Compat

This preserves the existing HashMap<String, String> structure without
requiring changes to the RelayResponse interface or frontend header
processing pipeline since this will have a rather large ripple effect
all over the codebase. A complete fix is described in FE-1008.

Implementation notes

The comment in processHeaders should explain the rationale:

A complete solution would involve swapping Record/HashMap with a
flat array and propagating the refactor throughout the codebase, from
the underlying networking to the frontend. The problem with that
approach is you lose that O(1) lookup. A simpler approach is
HashMap<String, Vec<String>> but even that doesn't substantially
reduce the refactor surface area.

The newline concatenation also preserves O(1) header lookup
performance.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/hoppscotch/hoppscotch/pull/5394 **Author:** [@CuriousCorrelation](https://github.com/CuriousCorrelation) **Created:** 9/21/2025 **Status:** ✅ Merged **Merged:** 9/23/2025 **Merged by:** [@jamesgeorge007](https://github.com/jamesgeorge007) **Base:** `next` ← **Head:** `relay-fix-multi-value-set-cookie-headers` --- ### 📝 Commits (4) - [`e139b76`](https://github.com/hoppscotch/hoppscotch/commit/e139b76ccaa19aff05e6dcfa91036aa512db6205) fix(relay): multiple Set-Cookie headers in resp - [`1b84e78`](https://github.com/hoppscotch/hoppscotch/commit/1b84e7841570f85d94588a818fad5ed83b315e84) fix: typos - [`1f12609`](https://github.com/hoppscotch/hoppscotch/commit/1f12609511dcc7f7650850a55ef83e7c80482064) fix: doc for defensive handling in resp transform - [`bd0a2df`](https://github.com/hoppscotch/hoppscotch/commit/bd0a2df8db30ade9de3b86c9d418b2066cb50541) chore: cleanup ### 📊 Changes **8 files changed** (+76 additions, -15 deletions) <details> <summary>View changed files</summary> 📝 `packages/hoppscotch-agent/src-tauri/Cargo.lock` (+1 -1) 📝 `packages/hoppscotch-common/src/helpers/kernel/rest/response.ts` (+49 -3) 📝 `packages/hoppscotch-desktop/plugin-workspace/relay/src/transfer.rs` (+16 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock` (+1 -1) 📝 `packages/hoppscotch-desktop/src-tauri/Cargo.lock` (+2 -2) 📝 `packages/hoppscotch-desktop/src-tauri/Cargo.toml` (+1 -1) 📝 `packages/hoppscotch-kernel/package.json` (+1 -1) 📝 `pnpm-lock.yaml` (+5 -5) </details> ### 📄 Description This is an experimental way to fix multiple `Set-Cookie` headers being lost during HTTP response pre/post-processing by concat-ing them with `\n` in the networking layer, so all cookies to be displayed in the response headers panel instead of only the first one being visible. | Before | After | | --- | --- | | <img width="1328" height="888" alt="image" src="https://github.com/user-attachments/assets/0897cd7d-c718-4ddb-ac3c-3a2578956642" /> | <img width="1196" height="887" alt="image" src="https://github.com/user-attachments/assets/fd2d6e10-8e4e-4ea5-bb53-86b92e4f1f23" /> | NOTE: This is an experimental workaround for the time being. `\n` is used here as a separator because HTTP headers are inherently line-oriented (`\r\n` on the wire). This makes it trivial to split back into distinct `Set-Cookie` headers without violating RFC rules. The next idea would be using a custom delimiter but it also would be arbitrary and risk breaking if that delimiter ever appeared in a cookie value. The main trade-off is that this is a fragile workaround, unusual edge cases (e.g. malformed cookies containing newlines, or components that normalize headers differently) may fail. A full fix would require changing the header storage structure to support multiple values per key (the rough idea around that is explained in FE-1008). Closes FE-1008 (phase-1) Closes FE-309 Closes #1546 (multiple Set-Cookie headers missing in response display) Closes #5344 (ignoring one Set-Cookie when response has 2 values) Closes #5375 (only shows first Set-Cookie when multiple cookies present) Partially addresses #3532 (fixes header display, automatic cookie storage will be handled separately). When servers returned multiple `Set-Cookie` headers, only the first header was preserved while subsequent ones were discarded. The issue occurres because `HashMap<String, String>` data structure in `TransferHandler` could only store one value per header name (by the very nature of hashmaps or records for instance). ### What's changed #### Relay networking layer The header processing in `transfer.rs` now includes special handling just for `set-cookie` headers (case-insensitive accumulator). When multiple Set-Cookie headers are encountered, they are concatenated with newlines (why is explained above). #### FE processing Added `processHeaders` function in `response.ts` that splits concatenated `Set-Cookie` headers back into separate header entries. #### Dependencies Updated `relay` and `tauri-plugin-relay` to commits that include the `Set-Cookie` concatenation fix across all target packages. ### Testing Test with any endpoints that set multiple cookies: ```bash curl -i http://localhost:3000/multiple-cookies ``` or perhaps use `httpbin`: ```bash curl -i -G \ --data-urlencode "Set-Cookie=sessionid=abc123; Path=/" \ --data-urlencode "Set-Cookie=csrftoken=xyz789; Path=/" \ --data-urlencode "Set-Cookie=userid=user456; Path=/" \ "https://httpbin.org/response-headers" ``` or even a custom server script: ```javascript const http = require('http'); const server = http.createServer((req, res) => { if (req.url === '/multiple-cookies') { res.setHeader('Set-Cookie', [ 'sessionid=abc123; Path=/', 'csrftoken=xyz789; Path=/', 'userid=user456; Path=/' ]); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ message: 'Multiple cookies set' })); } }); ``` #### Expected response headers: ``` Set-Cookie: sessionid=abc123; Path=/ Set-Cookie: csrftoken=xyz789; Path=/ Set-Cookie: userid=user456; Path=/ ``` In desktop app response headers panel, all three cookies now appear as separate header entries **instead of only the first one** being visible. ### Special Note On Backwards Compat This preserves the existing `HashMap<String, String>` structure without requiring changes to the `RelayResponse` interface or frontend header processing pipeline since this will have a rather large ripple effect all over the codebase. A complete fix is described in FE-1008. ### Implementation notes The comment in `processHeaders` should explain the rationale: > A complete solution would involve swapping `Record`/`HashMap` with a > flat array and propagating the refactor throughout the codebase, from > the underlying networking to the frontend. The problem with that > approach is you lose that O(1) lookup. A simpler approach is > `HashMap<String, Vec<String>>` but even that doesn't substantially > reduce the refactor surface area. The newline concatenation also preserves O(1) header lookup performance. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-17 02:39:17 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hoppscotch#5181
No description provided.