This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 03953af73f fix(plugin): rename header keys instead of overwriting
values in modify-response (#7418)
03953af73f is described below
commit 03953af73f20ef1b76121505bf63d5ed72efa500
Author: Sean-Walker0 <[email protected]>
AuthorDate: Thu Oct 8 14:49:05 2026 +0800
fix(plugin): rename header keys instead of overwriting values in
modify-response (#7418)
* fix(plugin): rename header keys instead of overwriting values in
modify-response
ModifyResponsePlugin applied the replaceHeaderKeys map with
HttpHeaders.replace(key, singletonList(value)), which keeps the old
header name and overwrites its values: a rule {"X-Old": "X-New"} on
an upstream response X-Old: abc produced X-Old: X-New instead of
renaming the header to X-New: abc. This contradicts the documented
contract of ModifyResponseRuleHandle.replaceHeaderKeys (key:
oldHeaderKey, value: newHeaderKey) and the sibling
RequestPlugin#replaceHeaderKey, which renames the key and preserves
the values.
Rename the header key and carry the original values over, mirroring
the request-side implementation. Regression tests cover the rename
(multiple values preserved, old key dropped) and the missing
source key no-op.
Fixes #6510
* fix(plugin): make same-key header renames a no-op
Review follow-up: when a replaceHeaderKeys entry maps a header to
itself (equal or case-only-different key, and HttpHeaders is
case-insensitive), the rename appended the values to the same header
and the trailing remove then dropped the header entirely. Skip the
rename when the target key equals the source key ignoring case.
Adds exact-case and case-only regression tests (both red on the
previous commit: expected [abc] but was null).
---------
Co-authored-by: Sean-Walker0
<[email protected]>
---
.../modify/response/ModifyResponsePlugin.java | 12 ++++-
.../modify/response/ModifyResponsePluginTest.java | 62 ++++++++++++++++++++++
2 files changed, 72 insertions(+), 2 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java
b/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java
index a32210b9fa..df92371bba 100644
---
a/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java
+++
b/shenyu-plugin/shenyu-plugin-modify-response/src/main/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePlugin.java
@@ -47,7 +47,7 @@ import reactor.core.publisher.Mono;
import reactor.util.annotation.NonNull;
import java.nio.charset.StandardCharsets;
-import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -144,7 +144,15 @@ public class ModifyResponsePlugin extends
AbstractShenyuPlugin {
// replace headers
if (MapUtils.isNotEmpty(this.ruleHandle.getReplaceHeaderKeys())) {
Map<String, String> replaceHeaderMap =
this.ruleHandle.getReplaceHeaderKeys();
- replaceHeaderMap.forEach((key, value) ->
httpHeaders.replace(key, Collections.singletonList(value)));
+ replaceHeaderMap.forEach((key, value) -> {
+ List<String> values = httpHeaders.get(key);
+ // HttpHeaders is case-insensitive, so an equal or
case-only-different
+ // target key would make the trailing remove drop the
header entirely
+ if (Objects.nonNull(values) &&
!key.equalsIgnoreCase(value)) {
+ httpHeaders.addAll(value, values);
+ httpHeaders.remove(key);
+ }
+ });
}
// remove headers
diff --git
a/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java
b/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java
index d879a3e6a1..65162fb86a 100644
---
a/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java
+++
b/shenyu-plugin/shenyu-plugin-modify-response/src/test/java/org/apache/shenyu/plugin/modify/response/ModifyResponsePluginTest.java
@@ -48,6 +48,7 @@ import reactor.test.StepVerifier;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -159,6 +160,67 @@ public final class ModifyResponsePluginTest {
.verifyComplete();
}
+ @Test
+ public void testReplaceHeaderKeysRenamesKeyAndKeepsValues() {
+ final ModifyResponseRuleHandle responseRuleHandle = new
ModifyResponseRuleHandle();
+
responseRuleHandle.setReplaceHeaderKeys(Collections.singletonMap("X-Old",
"X-New"));
+ final ModifyResponsePlugin.ModifyResponseDecorator decorator =
+ new ModifyResponsePlugin.ModifyResponseDecorator(exchange,
responseRuleHandle);
+ final MockServerHttpResponse response = (MockServerHttpResponse)
exchange.getResponse();
+ response.getHeaders().add("X-Old", "abc");
+ response.getHeaders().add("X-Old", "def");
+ final DataBuffer dataBuffer =
response.bufferFactory().wrap("{}".getBytes(StandardCharsets.UTF_8));
+
+
StepVerifier.create(decorator.writeWith(Mono.just(dataBuffer))).verifyComplete();
+
+ assertFalse(response.getHeaders().containsKey("X-Old"));
+ assertEquals(Arrays.asList("abc", "def"),
response.getHeaders().get("X-New"));
+ }
+
+ @Test
+ public void testReplaceHeaderKeysLeavesMissingSourceKeyUntouched() {
+ final ModifyResponseRuleHandle responseRuleHandle = new
ModifyResponseRuleHandle();
+
responseRuleHandle.setReplaceHeaderKeys(Collections.singletonMap("X-Missing",
"X-New"));
+ final ModifyResponsePlugin.ModifyResponseDecorator decorator =
+ new ModifyResponsePlugin.ModifyResponseDecorator(exchange,
responseRuleHandle);
+ final MockServerHttpResponse response = (MockServerHttpResponse)
exchange.getResponse();
+ final DataBuffer dataBuffer =
response.bufferFactory().wrap("{}".getBytes(StandardCharsets.UTF_8));
+
+
StepVerifier.create(decorator.writeWith(Mono.just(dataBuffer))).verifyComplete();
+
+ assertFalse(response.getHeaders().containsKey("X-New"));
+ }
+
+ @Test
+ public void testReplaceHeaderKeysSameKeyIsNoOp() {
+ final ModifyResponseRuleHandle responseRuleHandle = new
ModifyResponseRuleHandle();
+
responseRuleHandle.setReplaceHeaderKeys(Collections.singletonMap("X-Keep",
"X-Keep"));
+ final ModifyResponsePlugin.ModifyResponseDecorator decorator =
+ new ModifyResponsePlugin.ModifyResponseDecorator(exchange,
responseRuleHandle);
+ final MockServerHttpResponse response = (MockServerHttpResponse)
exchange.getResponse();
+ response.getHeaders().add("X-Keep", "abc");
+ final DataBuffer dataBuffer =
response.bufferFactory().wrap("{}".getBytes(StandardCharsets.UTF_8));
+
+
StepVerifier.create(decorator.writeWith(Mono.just(dataBuffer))).verifyComplete();
+
+ assertEquals(Collections.singletonList("abc"),
response.getHeaders().get("X-Keep"));
+ }
+
+ @Test
+ public void testReplaceHeaderKeysCaseOnlyRenameIsNoOp() {
+ final ModifyResponseRuleHandle responseRuleHandle = new
ModifyResponseRuleHandle();
+
responseRuleHandle.setReplaceHeaderKeys(Collections.singletonMap("X-Keep",
"x-keep"));
+ final ModifyResponsePlugin.ModifyResponseDecorator decorator =
+ new ModifyResponsePlugin.ModifyResponseDecorator(exchange,
responseRuleHandle);
+ final MockServerHttpResponse response = (MockServerHttpResponse)
exchange.getResponse();
+ response.getHeaders().add("X-Keep", "abc");
+ final DataBuffer dataBuffer =
response.bufferFactory().wrap("{}".getBytes(StandardCharsets.UTF_8));
+
+
StepVerifier.create(decorator.writeWith(Mono.just(dataBuffer))).verifyComplete();
+
+ assertEquals(Collections.singletonList("abc"),
response.getHeaders().get("X-Keep"));
+ }
+
@Test
public void testWriteWithSkipsBodyRulesForNonJsonResponse() {
final ModifyResponseRuleHandle responseRuleHandle = new
ModifyResponseRuleHandle();