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 e47d1ad9a3 fix(ai): synchronize routing context after request path
rewrite (#7535)
e47d1ad9a3 is described below
commit e47d1ad9a39d6e34810e18afb79ae4fd0017b848
Author: lymerin <[email protected]>
AuthorDate: Sun Oct 11 22:45:21 2026 +0800
fix(ai): synchronize routing context after request path rewrite (#7535)
---
.../request/AiRequestTransformerPlugin.java | 12 +++++
.../request/AiRequestTransformerPluginTest.java | 56 ++++++++++++++++++++++
2 files changed, 68 insertions(+)
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPlugin.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPlugin.java
index dbaa551d73..1c3dfaa6d8 100644
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPlugin.java
+++
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPlugin.java
@@ -18,6 +18,7 @@
package org.apache.shenyu.plugin.ai.transformer.request;
import com.google.gson.JsonElement;
+import org.apache.shenyu.common.constant.Constants;
import org.apache.shenyu.common.dto.RuleData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.common.dto.convert.plugin.AiRequestTransformerConfig;
@@ -31,6 +32,7 @@ import
org.apache.shenyu.plugin.ai.transformer.request.cache.ChatClientCache;
import
org.apache.shenyu.plugin.ai.transformer.request.handler.AiRequestTransformerPluginHandler;
import
org.apache.shenyu.plugin.ai.transformer.request.template.AiRequestTransformerTemplate;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.context.ShenyuContext;
import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
import org.apache.shenyu.plugin.base.utils.ServerWebExchangeUtils;
@@ -292,6 +294,16 @@ public class AiRequestTransformerPlugin extends
AbstractShenyuPlugin {
.request(newRequest)
.build();
+ ShenyuContext context = newExchange.getAttribute(Constants.CONTEXT);
+ if (Objects.nonNull(context)) {
+ String rewrittenPath = newUri.getRawPath();
+ // Preserve an already transformed forwarding path.
+ if (Objects.equals(context.getRealUrl(), context.getPath())) {
+ context.setRealUrl(rewrittenPath);
+ }
+ context.setPath(rewrittenPath);
+ }
+
return Mono.just(newExchange);
}
diff --git
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPluginTest.java
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPluginTest.java
index 3258960e9f..f7b367d9fb 100644
---
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPluginTest.java
+++
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-request-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/request/AiRequestTransformerPluginTest.java
@@ -18,6 +18,7 @@
package org.apache.shenyu.plugin.ai.transformer.request;
import com.google.gson.JsonSyntaxException;
+import org.apache.shenyu.common.constant.Constants;
import org.apache.shenyu.common.dto.RuleData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.common.dto.convert.rule.AiRequestTransformerHandle;
@@ -25,10 +26,13 @@ import
org.apache.shenyu.plugin.ai.common.spring.ai.registry.AiModelFactoryRegis
import org.apache.shenyu.plugin.ai.transformer.request.cache.ChatClientCache;
import
org.apache.shenyu.plugin.ai.transformer.request.handler.AiRequestTransformerPluginHandler;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.context.ShenyuContext;
+import org.apache.shenyu.plugin.api.utils.RequestUrlUtils;
import org.apache.shenyu.plugin.base.utils.CacheKeyUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.MockedStatic;
import org.springframework.ai.chat.client.ChatClient;
@@ -46,6 +50,7 @@ import reactor.test.StepVerifier;
import java.net.URI;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -143,6 +148,57 @@ class AiRequestTransformerPluginTest {
assertThrows(JsonSyntaxException.class, () ->
AiRequestTransformerPlugin.convertBodyJson(aiResponse));
}
+ @ParameterizedTest
+ @CsvSource({
+ ", http://upstream.example/rewritten",
+ "/forced, http://upstream.example/forced"
+ })
+ void testRewriteRequestRouting(final String rewriteUri, final String
expectedOutboundUri) {
+ AiRequestTransformerHandle handle = new AiRequestTransformerHandle();
+ handle.setProvider("TEST_PROVIDER");
+ handle.setBaseUrl("http://test.com");
+ handle.setApiKey("test-api-key");
+ handle.setModel("test-model");
+
+ ChatClient mockClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
+ String aiResponse = "POST /rewritten HTTP/1.1\nContent-Type:
application/json\n\n{}";
+
when(mockClient.prompt().user(anyString()).stream().content()).thenReturn(Flux.just(aiResponse));
+ MockServerWebExchange exchange =
MockServerWebExchange.from(MockServerHttpRequest.post("http://localhost/original")
+ .header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE)
+ .body("{}"));
+ ShenyuContext context = new ShenyuContext();
+ context.setPath("/original");
+ context.setRealUrl("/original");
+ exchange.getAttributes().put(Constants.CONTEXT, context);
+ if (Objects.nonNull(rewriteUri)) {
+ exchange.getAttributes().put(Constants.REWRITE_URI, rewriteUri);
+ }
+ RuleData rule = new RuleData();
+ rule.setId("test-routing-rule-id");
+ rule.setSelectorId("test-selector-id");
+ when(chatClientCache.getClient(rule.getId())).thenReturn(mockClient);
+ AtomicReference<ServerWebExchange> downstreamExchange = new
AtomicReference<>();
+
when(chain.execute(any(ServerWebExchange.class))).thenAnswer(invocation -> {
+ downstreamExchange.set(invocation.getArgument(0));
+ return Mono.empty();
+ });
+
+ String cacheKey = CacheKeyUtils.INST.getKey(rule);
+
AiRequestTransformerPluginHandler.CACHED_HANDLE.get().cachedHandle(cacheKey,
handle);
+ try (MockedStatic<ChatClientCache> mockedCache =
mockStatic(ChatClientCache.class)) {
+
mockedCache.when(ChatClientCache::getInstance).thenReturn(chatClientCache);
+ StepVerifier.create(plugin.doExecute(exchange, chain, new
SelectorData(), rule)).verifyComplete();
+
+ ServerWebExchange downstream = downstreamExchange.get();
+ assertEquals(URI.create("http://localhost/rewritten"),
downstream.getRequest().getURI());
+ assertEquals(URI.create(expectedOutboundUri),
RequestUrlUtils.buildRequestUri(downstream, "http://upstream.example"));
+ assertEquals("/rewritten", context.getPath());
+ assertEquals("/rewritten", context.getRealUrl());
+ } finally {
+
AiRequestTransformerPluginHandler.CACHED_HANDLE.get().removeHandle(cacheKey);
+ }
+ }
+
@Test
void testExtractHeadersFromAiResponse() {