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 5ebaa340d9 fix(sdk): give each invocation independently owned header
values (#7771) (#7786)
5ebaa340d9 is described below
commit 5ebaa340d9c0dc83c10a6fb30cc80b017e80454f
Author: konlue <[email protected]>
AuthorDate: Mon Oct 19 09:33:00 2026 +0800
fix(sdk): give each invocation independently owned header values (#7771)
(#7786)
---
.../shenyu/sdk/core/common/RequestTemplate.java | 9 +++++++-
.../sdk/core/common/RequestTemplateTest.java | 9 ++++++++
.../annotation/CookieValueParameterProcessor.java | 17 ++++++---------
.../RequestHeaderParameterProcessor.java | 12 +++++------
.../CookieValueParameterProcessorTest.java | 25 ++++++++++++++++++++++
.../RequestHeaderParameterProcessorTest.java | 22 +++++++++++++++++++
6 files changed, 75 insertions(+), 19 deletions(-)
diff --git
a/shenyu-sdk/shenyu-sdk-core/src/main/java/org/apache/shenyu/sdk/core/common/RequestTemplate.java
b/shenyu-sdk/shenyu-sdk-core/src/main/java/org/apache/shenyu/sdk/core/common/RequestTemplate.java
index 7b88bbe31f..3a3683b2e2 100644
---
a/shenyu-sdk/shenyu-sdk-core/src/main/java/org/apache/shenyu/sdk/core/common/RequestTemplate.java
+++
b/shenyu-sdk/shenyu-sdk-core/src/main/java/org/apache/shenyu/sdk/core/common/RequestTemplate.java
@@ -21,6 +21,7 @@ import org.apache.shenyu.sdk.core.ShenyuRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -84,11 +85,17 @@ public final class RequestTemplate {
/**
* Create a Request Template from an existing Request Template.
+ * <p>
+ * Header value collections are copied per entry so that per-invocation
+ * mutations (dynamic headers, cookies) never reach the template the copy
was made from.
+ * </p>
*
* @param requestTemplate to copy from.
* @return a new Request Template.
*/
public static RequestTemplate from(final RequestTemplate requestTemplate) {
+ final Map<String, Collection<String>> headers = new HashMap<>();
+ requestTemplate.getHeaders().forEach((key, value) -> headers.put(key,
new ArrayList<>(value)));
return new RequestTemplate(
requestTemplate.getReturnType(),
requestTemplate.getMethod(),
@@ -98,7 +105,7 @@ public final class RequestTemplate {
requestTemplate.getPath(),
requestTemplate.getHttpMethod(),
requestTemplate.getParamMetadataList(),
- requestTemplate.getHeaders(),
+ headers,
requestTemplate.getBody());
}
diff --git
a/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/common/RequestTemplateTest.java
b/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/common/RequestTemplateTest.java
index 0deddeac4c..015ff2bcdc 100644
---
a/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/common/RequestTemplateTest.java
+++
b/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/common/RequestTemplateTest.java
@@ -78,6 +78,15 @@ public class RequestTemplateTest {
assertNotNull(newRequestTemplate);
}
+ @Test
+ public void testFromCopiesHeaderValues() {
+ RequestTemplate copy = RequestTemplate.from(requestTemplate);
+ copy.getHeaders().get("header").add("header3");
+
+ Assert.assertEquals(Arrays.asList("header1", "header2"),
requestTemplate.getHeaders().get("header"));
+ Assert.assertTrue(copy.getHeaders().get("header").contains("header3"));
+ }
+
@Test
public void testRequest() {
assertNotNull(requestTemplate);
diff --git
a/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessor.java
b/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessor.java
index e5e8831e18..a05417b3b3 100644
---
a/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessor.java
+++
b/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessor.java
@@ -17,8 +17,6 @@
package org.apache.shenyu.sdk.spring.annotation;
-import com.google.common.collect.Lists;
-import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.sdk.core.ShenyuRequest;
import org.apache.shenyu.sdk.core.common.RequestTemplate;
@@ -27,7 +25,9 @@ import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.CookieValue;
import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Map;
import static org.apache.shenyu.sdk.core.util.Util.checkState;
@@ -51,16 +51,11 @@ public class CookieValueParameterProcessor implements
AnnotatedParameterProcesso
CookieValue cookie = ANNOTATION.cast(annotation);
String name = StringUtils.defaultIfBlank(cookie.value(),
StringUtils.defaultIfBlank(cookie.name(), StringUtils.EMPTY)).trim();
checkState(StringUtils.isNotBlank(name), "Cookie.name() was empty on
parameter %s", requestTemplate.getMethod());
- Collection<String> cookieExpression =
requestTemplate.getHeaders().getOrDefault(HttpHeaders.COOKIE,
Lists.newArrayList());
- cookieExpression.add(String.format("%s=%s", name, arg));
+ // operate on the per-invocation header map only: never mutate
collections owned by the shared request template
Map<String, Collection<String>> headers = shenyuRequest.getHeaders();
- headers.compute(HttpHeaders.COOKIE, (key, old) -> {
- if (CollectionUtils.isEmpty(old)) {
- return cookieExpression;
- }
- CollectionUtils.addAll(old, cookieExpression);
- return old;
- });
+ Collection<String> cookieExpression = new
ArrayList<>(headers.getOrDefault(HttpHeaders.COOKIE, Collections.emptyList()));
+ cookieExpression.add(String.format("%s=%s", name, arg));
+ headers.put(HttpHeaders.COOKIE, cookieExpression);
shenyuRequest.setHeaders(headers);
return true;
}
diff --git
a/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessor.java
b/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessor.java
index 344635e2dc..cdfed41f5f 100644
---
a/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessor.java
+++
b/shenyu-sdk/shenyu-sdk-spring/src/main/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessor.java
@@ -18,10 +18,10 @@
package org.apache.shenyu.sdk.spring.annotation;
import static com.google.common.base.Strings.emptyToNull;
-import com.google.common.collect.Lists;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -58,17 +58,15 @@ public class RequestHeaderParameterProcessor implements
AnnotatedParameterProces
shenyuRequest.setHeaders(headers);
} else if (key instanceof String && value instanceof String) {
headers.compute((String) key, (header, old) -> {
- if (CollectionUtils.isEmpty(old)) {
- return Lists.newArrayList((String) value);
- }
- old.add((String) value);
- return old;
+ Collection<String> merged = new
ArrayList<>(CollectionUtils.isEmpty(old) ? Collections.emptyList() : old);
+ merged.add((String) value);
+ return merged;
});
shenyuRequest.setHeaders(headers);
}
});
} else if (arg instanceof String) {
- Collection<String> headerColl =
Optional.ofNullable(headers.get(name)).orElseGet(ArrayList::new);
+ Collection<String> headerColl = new
ArrayList<>(Optional.ofNullable(headers.get(name)).orElseGet(ArrayList::new));
headerColl.add((String) arg);
headers.put(name, headerColl);
shenyuRequest.setHeaders(headers);
diff --git
a/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessorTest.java
b/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessorTest.java
index b012b7be4a..08de864db3 100644
---
a/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessorTest.java
+++
b/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/CookieValueParameterProcessorTest.java
@@ -19,7 +19,10 @@ package org.apache.shenyu.sdk.spring.annotation;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.shenyu.sdk.core.ShenyuRequest;
@@ -27,6 +30,7 @@ import org.apache.shenyu.sdk.core.common.RequestTemplate;
import org.apache.shenyu.sdk.spring.factory.AnnotatedParameterProcessor;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -113,6 +117,27 @@ public class CookieValueParameterProcessorTest {
assertTrue(request.getHeaders().get(HttpHeaders.COOKIE).contains("two=twoValue"),
"cookie value resolve error");
}
+ @Test
+ public void processArgumentDoesNotAccumulateAcrossCalls() {
+ final RequestTemplate template = new RequestTemplate();
+ template.setHttpMethod(ShenyuRequest.HttpMethod.GET);
+ template.getHeaders().put(HttpHeaders.COOKIE,
Collections.singletonList("static=static"));
+
+ final CookieValue cookie = spy(CookieValue.class);
+ when(cookie.value()).thenReturn("session");
+
+ final ShenyuRequest first = RequestTemplate.from(template).request();
+ processor.processArgument(first, cookie, "one");
+ assertEquals(Arrays.asList("static=static", "session=one"), new
ArrayList<>(first.getHeaders().get(HttpHeaders.COOKIE)));
+
+ final ShenyuRequest second = RequestTemplate.from(template).request();
+ processor.processArgument(second, cookie, "two");
+ assertEquals(Arrays.asList("static=static", "session=two"), new
ArrayList<>(second.getHeaders().get(HttpHeaders.COOKIE)));
+
+ // the shared template was never touched
+ assertEquals(Collections.singletonList("static=static"),
template.getHeaders().get(HttpHeaders.COOKIE));
+ }
+
@AfterEach
public void clear() {
headers.clear();
diff --git
a/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessorTest.java
b/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessorTest.java
index c8fa466d6e..3273c99b2e 100644
---
a/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessorTest.java
+++
b/shenyu-sdk/shenyu-sdk-spring/src/test/java/org/apache/shenyu/sdk/spring/annotation/RequestHeaderParameterProcessorTest.java
@@ -19,13 +19,16 @@ package org.apache.shenyu.sdk.spring.annotation;
import com.google.common.collect.Maps;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.shenyu.sdk.core.ShenyuRequest;
import org.apache.shenyu.sdk.core.common.RequestTemplate;
import org.apache.shenyu.sdk.spring.factory.AnnotatedParameterProcessor;
import org.junit.jupiter.api.AfterEach;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
@@ -93,6 +96,25 @@ public class RequestHeaderParameterProcessorTest {
}
+ @Test
+ public void processArgumentOnStaticSingletonHeaderTest() throws
NoSuchMethodException {
+ final RequestTemplate template = new RequestTemplate();
+ template.setHttpMethod(ShenyuRequest.HttpMethod.GET);
+
template.setMethod(getClass().getDeclaredMethod("processArgumentOnStaticSingletonHeaderTest"));
+ template.getHeaders().put("X-Mode", Collections.singleton("static"));
+
+ final ShenyuRequest perCall = RequestTemplate.from(template).request();
+ final RequestHeader header = spy(RequestHeader.class);
+ when(header.value()).thenReturn("X-Mode");
+
+ processor.processArgument(perCall, header, "dynamic");
+
+
assertTrue(perCall.getHeaders().get("X-Mode").containsAll(Arrays.asList("static",
"dynamic")),
+ "dynamic header value must merge with the static one");
+ assertEquals(Collections.singleton("static"),
template.getHeaders().get("X-Mode"),
+ "the shared template must not be mutated");
+ }
+
@Test
public void processArgumentMapTest() {
final String headerName = "header";