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