Skip to content

feat(proxy-rewrite): support multiple same-name headers in headers.add/set - #13597

Merged
AlinsRan merged 6 commits into
apache:masterfrom
AlinsRan:feat/13163-multi-value-headers
Jul 22, 2026
Merged

feat(proxy-rewrite): support multiple same-name headers in headers.add/set#13597
AlinsRan merged 6 commits into
apache:masterfrom
AlinsRan:feat/13163-multi-value-headers

Conversation

@AlinsRan

@AlinsRan AlinsRan commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Description

Allow headers.set and headers.add values in proxy-rewrite to be an array, producing multiple headers with the same name:

"proxy-rewrite": {
  "headers": {
    "set": { "X-Api-Version": ["v1", "v2"] }
  }
}
  • set with an array replaces any incoming header with the listed values.
  • add with an array appends the listed values to the existing ones.
  • remove is unchanged (it already takes an array of names).

Why

proxy-rewrite models headers.add/set as a JSON object (map), which structurally cannot express same-name multi-value headers — a JSON object can't hold two values for one key. This is a real gap vs. other gateways that model header transforms as an ordered list of (name, value) pairs (Kong request-transformer append.headers, Envoy request_headers_to_add, HAProxy add-header). The array form closes it. Genuine use cases: gRPC metadata (repeated keys → repeated HTTP/2 headers) and backends/auth proxies that read repeated custom headers.

Fully backward compatible: existing string/number values keep working; the array is an additive schema option. Each element still supports NGINX variables and regex_uri captures.

This also gives the clean answer to the remove + add ordering confusion in #11587 — multi-value is a data-model feature, not an operation-order one.

Which issue(s) this PR fixes

Closes #11587
Closes #13163

Changes

  • apisix/plugins/proxy-rewrite.lua: schema allows array<string|number> for add/set values; apply loop resolves each element (set → one set_header with a table; add → one add_header per element).
  • t/plugin/proxy-rewrite.t: tests for set (replace with multiple) and add (append multiple); t/lib/server.lua: echo helper joining same-name headers.
  • docs/{en,zh}/.../proxy-rewrite.md: attribute docs + example.

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (if not, please discuss on the APISIX mailing list first)

AlinsRan added 2 commits June 23, 2026 12:02
Allow headers.set and headers.add values to be an array (e.g.
"foo": ["v1", "v2"]), producing multiple headers with the same name:
- set replaces any incoming header with the listed values;
- add appends the listed values to the existing ones.

The map-based headers schema previously could not express same-name
multi-value headers (a JSON object can't hold two values for one key);
the array form closes that gap. remove is unchanged (it already takes an
array of names). Backward compatible: string/number values keep working.

Closes apache#13163
The echo helper joined same-name headers with ", ", so the assertions
("x-multi: val1, val2") could not tell a genuine multi-header result apart
from a single comma-joined value. Emit one line per header occurrence and
assert the repeated "x-multi: ..." lines, so the test actually proves that
set/add with an array produces multiple headers with the same name.
@AlinsRan AlinsRan changed the title feat(proxy-rewrite): support multiple values for set/add headers feat(proxy-rewrite): support multiple same-name headers in headers.add/set Jun 24, 2026
AlinsRan added 2 commits June 26, 2026 09:01
…y comment

- t/plugin/proxy-rewrite.t: add cases where each array element of
  headers.set/add mixes $1 (regex_uri capture) and $http_x_src (nginx
  variable), since the docs promise per-element variable/capture support.
- proxy-rewrite.lua: clarify the add_header guard comment — it filters nil
  (which throws); an empty string is a valid value and is intentionally kept.
@AlinsRan
AlinsRan marked this pull request as ready for review June 26, 2026 07:32
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jun 26, 2026
Comment thread apisix/plugins/proxy-rewrite.lua
membphis
membphis previously approved these changes Jun 26, 2026

@membphis membphis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

nic-6443
nic-6443 previously approved these changes Jun 26, 2026

@shreemaan-abhishek shreemaan-abhishek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think both "multiple values for same header" and "same header with multiple values - (comma separated or not)" are the same.

So even without this PR, set can already set multiple values for a header by specifying comma separated values.

The add feature in this PR is nice to have.

@AlinsRan

AlinsRan commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

I think both "multiple values for same header" and "same header with multiple values - (comma separated or not)" are the same.

I don't think they're fully interchangeable. A headers.set object can't express same-name multi-value headers at all (a JSON object can't hold two values for one key), and emitting repeated lines is a first-class, intentional behavior — both HTTP and the underlying OpenResty support it (ngx.req.set_header(name, {...})). The array closes that gap.

Real headers that rely on multiple same-name fields rather than a comma list:

  • Set-Cookie — non-combinable by spec.
  • Cookie over HTTP/2 — carried as multiple field lines, recombined with "; " not ",".
  • gRPC metadata — a multimap; in practice grpc-go/grpc-java neither fold duplicates nor split on commas, so ["a","b"] reaches the app as two entries and "a,b" as one.

…lue-headers

# Conflicts:
#	t/plugin/proxy-rewrite.t
@AlinsRan
AlinsRan dismissed stale reviews from shreemaan-abhishek, nic-6443, and membphis via 0305936 July 17, 2026 05:32
The response_body_like assertion is a substring match, so it passed even
if 'set' had left the incoming 'origin' value in place. Add a
response_body_unlike assertion so the replace semantics are actually
verified.

@membphis membphis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@AlinsRan
AlinsRan merged commit 82fcf54 into apache:master Jul 22, 2026
19 checks passed
@AlinsRan
AlinsRan deleted the feat/13163-multi-value-headers branch July 22, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: proxy-rewrite silently drops duplicate keys in headers.add due to JSON object limitation bug: proxy-rewrite header priority wrong

4 participants