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

Reply via email to