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();

Reply via email to