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 c09f98953b fix(request): preserve values in identity key replacements
(#7905)
c09f98953b is described below
commit c09f98953b969a66e0bd685b4299d8779f8e6b92
Author: cloudbase-ai <[email protected]>
AuthorDate: Wed Oct 21 14:04:41 2026 +0800
fix(request): preserve values in identity key replacements (#7905)
Signed-off-by: cloudbase-ai <[email protected]>
Co-authored-by: cloudbase-ai
<[email protected]>
---
.../shenyu/plugin/request/RequestPlugin.java | 6 +-
.../shenyu/plugin/request/RequestPluginTest.java | 77 ++++++++++++++++++++++
2 files changed, 80 insertions(+), 3 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-request/src/main/java/org/apache/shenyu/plugin/request/RequestPlugin.java
b/shenyu-plugin/shenyu-plugin-request/src/main/java/org/apache/shenyu/plugin/request/RequestPlugin.java
index 43edfb3348..0a2c091239 100644
---
a/shenyu-plugin/shenyu-plugin-request/src/main/java/org/apache/shenyu/plugin/request/RequestPlugin.java
+++
b/shenyu-plugin/shenyu-plugin-request/src/main/java/org/apache/shenyu/plugin/request/RequestPlugin.java
@@ -217,7 +217,7 @@ public class RequestPlugin extends AbstractShenyuPlugin {
private void replaceParameterKey(final Map.Entry<String, String>
shenyuParam, final MultiValueMap<String, String> queryParams) {
List<String> values = queryParams.get(shenyuParam.getKey());
- if (Objects.nonNull(values)) {
+ if (Objects.nonNull(values) && !Objects.equals(shenyuParam.getKey(),
shenyuParam.getValue())) {
queryParams.addAll(shenyuParam.getValue(), values);
queryParams.remove(shenyuParam.getKey());
}
@@ -250,9 +250,9 @@ public class RequestPlugin extends AbstractShenyuPlugin {
private void replaceHeaderKey(final Map.Entry<String, String>
shenyuHeader, final HttpHeaders headers) {
List<String> values = headers.get(shenyuHeader.getKey());
- if (Objects.nonNull(values)) {
- headers.addAll(shenyuHeader.getValue(), values);
+ if (Objects.nonNull(values) && !Objects.equals(shenyuHeader.getKey(),
shenyuHeader.getValue())) {
headers.remove(shenyuHeader.getKey());
+ headers.addAll(shenyuHeader.getValue(), values);
}
}
diff --git
a/shenyu-plugin/shenyu-plugin-request/src/test/java/org/apache/shenyu/plugin/request/RequestPluginTest.java
b/shenyu-plugin/shenyu-plugin-request/src/test/java/org/apache/shenyu/plugin/request/RequestPluginTest.java
index fc5092573e..07990f028f 100644
---
a/shenyu-plugin/shenyu-plugin-request/src/test/java/org/apache/shenyu/plugin/request/RequestPluginTest.java
+++
b/shenyu-plugin/shenyu-plugin-request/src/test/java/org/apache/shenyu/plugin/request/RequestPluginTest.java
@@ -39,6 +39,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -331,6 +332,82 @@ public class RequestPluginTest {
assertEquals("new=1", requestUri.getRawQuery());
}
+ @ParameterizedTest
+ @CsvSource({"X-Id,X-Id", "X-Id,x-id", "x-id,X-ID"})
+ public void testHeaderRenamePreservesValuesForSameName(final String
source, final String target) {
+ ruleData.setHandle("{\"header\":{\"replaceHeaderKeys\":{\"" + source +
"\":\"" + target + "\"}}}");
+ new RequestPluginHandler().handlerRule(ruleData);
+ ServerWebExchange requestExchange =
MockServerWebExchange.from(MockServerHttpRequest.get("/test")
+ .header("X-Id", "first", "second").build());
+ when(chain.execute(any())).thenReturn(Mono.empty());
+
+ StepVerifier.create(requestPlugin.doExecute(requestExchange, chain,
new SelectorData(), ruleData)).verifyComplete();
+
+ ArgumentCaptor<ServerWebExchange> captor =
ArgumentCaptor.forClass(ServerWebExchange.class);
+ Mockito.verify(chain).execute(captor.capture());
+ assertEquals(Arrays.asList("first", "second"),
captor.getValue().getRequest().getHeaders().get(target));
+ assertEquals(Arrays.asList("first", "second"),
requestExchange.getRequest().getHeaders().get("X-Id"));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"token=first&token=second", "token&token=&token=second"})
+ public void testParameterIdentityRenamePreservesValuesAndOrder(final
String query) {
+
ruleData.setHandle("{\"parameter\":{\"replaceParameterKeys\":{\"token\":\"token\"}}}");
+ new RequestPluginHandler().handlerRule(ruleData);
+ ServerWebExchange requestExchange =
MockServerWebExchange.from(MockServerHttpRequest
+ .method(HttpMethod.GET,
URI.create("http://localhost/files/a%2Fb?" + query + "&keep=1")).build());
+ when(chain.execute(any())).thenReturn(Mono.empty());
+
+ StepVerifier.create(requestPlugin.doExecute(requestExchange, chain,
new SelectorData(), ruleData)).verifyComplete();
+
+ ArgumentCaptor<ServerWebExchange> captor =
ArgumentCaptor.forClass(ServerWebExchange.class);
+ Mockito.verify(chain).execute(captor.capture());
+ URI result = captor.getValue().getRequest().getURI();
+ assertEquals(query + "&keep=1", result.getRawQuery());
+ assertEquals("/files/a%2Fb", result.getRawPath());
+ }
+
+ @ParameterizedTest
+ @CsvSource({"header,Old,New", "parameter,old,new",
"parameter,Token,token"})
+ public void testRenameStillMergesIntoExistingTarget(final String type,
final String source, final String target) {
+ String property = "header".equals(type) ? "replaceHeaderKeys" :
"replaceParameterKeys";
+ ruleData.setHandle("{\"" + type + "\":{\"" + property + "\":{\"" +
source + "\":\"" + target + "\"}}}");
+ new RequestPluginHandler().handlerRule(ruleData);
+ ServerWebExchange requestExchange =
MockServerWebExchange.from(MockServerHttpRequest
+ .method(HttpMethod.GET, URI.create("http://localhost/test?" +
source + "=first&" + source + "=second&" + target + "=existing"))
+ .header(source, "first", "second").header(target,
"existing").build());
+ when(chain.execute(any())).thenReturn(Mono.empty());
+
+ StepVerifier.create(requestPlugin.doExecute(requestExchange, chain,
new SelectorData(), ruleData)).verifyComplete();
+
+ ArgumentCaptor<ServerWebExchange> captor =
ArgumentCaptor.forClass(ServerWebExchange.class);
+ Mockito.verify(chain).execute(captor.capture());
+ ServerHttpRequest result = captor.getValue().getRequest();
+ MultiValueMap<String, String> values = "header".equals(type) ?
result.getHeaders() : result.getQueryParams();
+ assertEquals(Arrays.asList("existing", "first", "second"),
values.get(target));
+ assertFalse(values.containsKey(source));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"header", "parameter"})
+ public void testMissingRenameSourcePreservesTarget(final String type) {
+ String property = "header".equals(type) ? "replaceHeaderKeys" :
"replaceParameterKeys";
+ ruleData.setHandle("{\"" + type + "\":{\"" + property +
"\":{\"missing\":\"target\"}}}");
+ new RequestPluginHandler().handlerRule(ruleData);
+ ServerWebExchange requestExchange =
MockServerWebExchange.from(MockServerHttpRequest
+ .method(HttpMethod.GET,
URI.create("http://localhost/test?target=kept"))
+ .header("target", "kept").build());
+ when(chain.execute(any())).thenReturn(Mono.empty());
+
+ StepVerifier.create(requestPlugin.doExecute(requestExchange, chain,
new SelectorData(), ruleData)).verifyComplete();
+
+ ArgumentCaptor<ServerWebExchange> captor =
ArgumentCaptor.forClass(ServerWebExchange.class);
+ Mockito.verify(chain).execute(captor.capture());
+ ServerHttpRequest result = captor.getValue().getRequest();
+ assertEquals("kept", result.getHeaders().getFirst("target"));
+ assertEquals("kept", result.getQueryParams().getFirst("target"));
+ }
+
@Test
public void testGetOrder() {
assertEquals(this.requestPlugin.getOrder(),
PluginEnum.REQUEST.getCode());