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 88c655e98f fix(response): deduplicate final WebClient response headers 
(#7258)
88c655e98f is described below

commit 88c655e98f570dca972d31ff670436d3e03e05d5
Author: Liming Deng <[email protected]>
AuthorDate: Thu Oct 8 15:43:02 2026 +0800

    fix(response): deduplicate final WebClient response headers (#7258)
---
 .../response/strategy/WebClientMessageWriter.java  | 34 ++++++++++++++++++++++
 .../strategy/WebClientMessageWriterTest.java       | 29 ++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git 
a/shenyu-plugin/shenyu-plugin-response/src/main/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriter.java
 
b/shenyu-plugin/shenyu-plugin-response/src/main/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriter.java
index 5751eb8a6e..0f07666453 100644
--- 
a/shenyu-plugin/shenyu-plugin-response/src/main/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriter.java
+++ 
b/shenyu-plugin/shenyu-plugin-response/src/main/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriter.java
@@ -18,8 +18,11 @@
 package org.apache.shenyu.plugin.response.strategy;
 
 import com.google.common.collect.Lists;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.enums.HeaderUniqueStrategyEnum;
 import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.common.enums.UniqueHeaderEnum;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
 import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
 import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
@@ -37,6 +40,8 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -85,6 +90,7 @@ public class WebClientMessageWriter implements MessageWriter {
             }
 
             this.redrawResponseHeaders(response, fluxResponseEntity);
+            deduplicateResponseHeaders(exchange);
 
             Mono<Void> responseMono;
             if (Objects.nonNull(fluxResponseEntity.getBody())) {
@@ -129,6 +135,34 @@ public class WebClientMessageWriter implements 
MessageWriter {
         response.getHeaders().putAll(httpHeaders);
     }
 
+    private void deduplicateResponseHeaders(final ServerWebExchange exchange) {
+        String names = 
exchange.getAttribute(UniqueHeaderEnum.RESP_UNIQUE_HEADER.getName());
+        if (StringUtils.isEmpty(names)) {
+            return;
+        }
+        HttpHeaders headers = exchange.getResponse().getHeaders();
+        HeaderUniqueStrategyEnum strategy = 
exchange.getAttributeOrDefault(UniqueHeaderEnum.RESP_UNIQUE_HEADER.getStrategy(),
 HeaderUniqueStrategyEnum.RETAIN_FIRST);
+        for (String name : StringUtils.split(names, 
Constants.SEPARATOR_CHARS)) {
+            List<String> values = headers.get(name);
+            if (Objects.isNull(values) || values.size() <= 1) {
+                continue;
+            }
+            switch (strategy) {
+                case RETAIN_FIRST:
+                    headers.set(name, values.get(0));
+                    break;
+                case RETAIN_LAST:
+                    headers.set(name, values.get(values.size() - 1));
+                    break;
+                case RETAIN_UNIQUE:
+                    headers.put(name, new ArrayList<>(new 
LinkedHashSet<>(values)));
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected header 
strategy: " + strategy);
+            }
+        }
+    }
+
     private void clean(final ServerWebExchange exchange) {
         ResponseEntity<Flux<DataBuffer>> fluxResponseEntity = 
exchange.getAttribute(Constants.CLIENT_RESPONSE_ATTR);
         if (Objects.nonNull(fluxResponseEntity) && 
Objects.nonNull(fluxResponseEntity.getBody())) {
diff --git 
a/shenyu-plugin/shenyu-plugin-response/src/test/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriterTest.java
 
b/shenyu-plugin/shenyu-plugin-response/src/test/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriterTest.java
index ad20e343bd..6ee8143f57 100644
--- 
a/shenyu-plugin/shenyu-plugin-response/src/test/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriterTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-response/src/test/java/org/apache/shenyu/plugin/response/strategy/WebClientMessageWriterTest.java
@@ -19,12 +19,16 @@ package org.apache.shenyu.plugin.response.strategy;
 
 import io.netty.buffer.PooledByteBufAllocator;
 import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.enums.HeaderUniqueStrategyEnum;
+import org.apache.shenyu.common.enums.UniqueHeaderEnum;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
 import org.apache.shenyu.plugin.api.context.ShenyuContext;
 import org.apache.shenyu.plugin.api.result.ShenyuResult;
 import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.junit.jupiter.MockitoSettings;
@@ -50,6 +54,7 @@ import reactor.test.StepVerifier;
 
 import org.reactivestreams.Publisher;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -116,6 +121,30 @@ public class WebClientMessageWriterTest {
         
StepVerifier.create(monoGatewayTimeout).expectSubscription().verifyComplete();
     }
 
+    @ParameterizedTest
+    @EnumSource(HeaderUniqueStrategyEnum.class)
+    public void testFinalResponseRetainsDeduplication(final 
HeaderUniqueStrategyEnum strategy) {
+        MockServerWebExchange exchange = 
MockServerWebExchange.from(MockServerHttpRequest.get("/headers").build());
+        exchange.getResponse().getHeaders().set("X-Duplicate", 
"already-deduplicated");
+        
exchange.getResponse().getHeaders().setAccessControlAllowOrigin("https://allowed.example";);
+        HttpHeaders upstream = new HttpHeaders();
+        upstream.put("X-Duplicate", List.of("first", "last", "last"));
+        upstream.put("X-Unconfigured", List.of("one", "two"));
+        upstream.setAccessControlAllowOrigin("*");
+        ResponseEntity<Flux<DataBuffer>> response = new 
ResponseEntity<>(Flux.empty(), upstream, HttpStatus.OK);
+        exchange.getAttributes().put(Constants.CLIENT_RESPONSE_ATTR, response);
+        
exchange.getAttributes().put(UniqueHeaderEnum.RESP_UNIQUE_HEADER.getName(), 
"X-Duplicate;X-Missing");
+        
exchange.getAttributes().put(UniqueHeaderEnum.RESP_UNIQUE_HEADER.getStrategy(), 
strategy);
+        when(chain.execute(exchange)).thenReturn(Mono.empty());
+        StepVerifier.create(webClientMessageWriter.writeWith(exchange, 
chain)).verifyComplete();
+        List<String> expected = strategy == 
HeaderUniqueStrategyEnum.RETAIN_UNIQUE
+                ? List.of("first", "last") : List.of(strategy == 
HeaderUniqueStrategyEnum.RETAIN_FIRST ? "first" : "last");
+        assertEquals(expected, 
exchange.getResponse().getHeaders().get("X-Duplicate"));
+        assertEquals(List.of("one", "two"), 
exchange.getResponse().getHeaders().get("X-Unconfigured"));
+        assertEquals("https://allowed.example";, 
exchange.getResponse().getHeaders().getAccessControlAllowOrigin());
+        assertEquals(List.of("first", "last", "last"), 
response.getHeaders().get("X-Duplicate"));
+    }
+
     @Test
     public void testWriteErrorDoesNotResubscribeResponseBody() {
         RuntimeException expected = new RuntimeException("write failed");

Reply via email to