This is an automated email from the ASF dual-hosted git repository.
xiaoyu 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 29f9d5ff75 fix: enhance logging and improve request handling in HTTP
client plugins (#6288)
29f9d5ff75 is described below
commit 29f9d5ff75b42a316ce7968845fab467ab62748c
Author: aias00 <[email protected]>
AuthorDate: Wed Feb 4 16:51:10 2026 +0800
fix: enhance logging and improve request handling in HTTP client plugins
(#6288)
* fix: enhance logging and improve request handling in HTTP client plugins
* Update
shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/NettyHttpClientPlugin.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
Co-authored-by: Copilot <[email protected]>
* Update
shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
Co-authored-by: Copilot <[email protected]>
* fix: improve null check for request body in ShenyuToolCallback
---------
Co-authored-by: Copilot <[email protected]>
---
.../httpclient/AbstractHttpClientPlugin.java | 5 +++-
.../plugin/httpclient/NettyHttpClientPlugin.java | 19 +++++++++++-
.../mcp/server/callback/ShenyuToolCallback.java | 34 ++++++++++++++++++----
.../NonCommittingMcpResponseDecorator.java | 10 +++++++
4 files changed, 61 insertions(+), 7 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/AbstractHttpClientPlugin.java
b/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/AbstractHttpClientPlugin.java
index 845585407d..c79816ab58 100644
---
a/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/AbstractHttpClientPlugin.java
+++
b/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/AbstractHttpClientPlugin.java
@@ -74,7 +74,10 @@ public abstract class AbstractHttpClientPlugin<R> implements
ShenyuPlugin {
final int retryTimes = (int)
Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
final String retryStrategy = (String)
Optional.ofNullable(exchange.getAttribute(Constants.RETRY_STRATEGY)).orElseGet(RetryEnum.CURRENT::getName);
LogUtils.debug(LOG, () -> String.format("The request urlPath is: %s,
retryTimes is : %s, retryStrategy is : %s", uri, retryTimes, retryStrategy));
- final Mono<R> response = doRequest(exchange,
exchange.getRequest().getMethod().name(), uri, exchange.getRequest().getBody())
+ final Mono<R> response = doRequest(exchange,
+ Objects.nonNull(exchange.getRequest().getMethod()) ?
exchange.getRequest().getMethod().name() : "UNKNOWN",
+ uri,
+ exchange.getRequest().getBody())
.timeout(duration, Mono.error(() -> new
TimeoutException("Response took longer than timeout: " + duration)))
.doOnError(e -> LOG.error(e.getMessage(), e));
RetryStrategy<R> strategy;
diff --git
a/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/NettyHttpClientPlugin.java
b/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/NettyHttpClientPlugin.java
index 0724329946..83224d6a3b 100644
---
a/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/NettyHttpClientPlugin.java
+++
b/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/NettyHttpClientPlugin.java
@@ -23,6 +23,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.common.constant.Constants;
import org.apache.shenyu.common.enums.PluginEnum;
import org.apache.shenyu.common.enums.UniqueHeaderEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.http.HttpHeaders;
@@ -44,6 +46,8 @@ import java.util.Objects;
*/
public class NettyHttpClientPlugin extends
AbstractHttpClientPlugin<HttpClientResponse> {
+ private static final Logger LOG =
LoggerFactory.getLogger(NettyHttpClientPlugin.class);
+
private final HttpClient httpClient;
/**
@@ -61,6 +65,9 @@ public class NettyHttpClientPlugin extends
AbstractHttpClientPlugin<HttpClientRe
ServerHttpRequest request = exchange.getRequest();
final HttpHeaders httpHeaders = new HttpHeaders(request.getHeaders());
this.duplicateHeaders(exchange, httpHeaders,
UniqueHeaderEnum.REQ_UNIQUE_HEADER);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("NettyHttpClient request: method={}, uri={}",
httpMethod, uri);
+ }
return Mono.from(httpClient.headers(headers -> {
httpHeaders.forEach(headers::set);
headers.remove(HttpHeaders.HOST);
@@ -71,6 +78,9 @@ public class NettyHttpClientPlugin extends
AbstractHttpClientPlugin<HttpClientRe
}).request(HttpMethod.valueOf(httpMethod)).uri(uri.toASCIIString())
.send((req, nettyOutbound) ->
nettyOutbound.send(body.map(dataBuffer -> ((NettyDataBuffer)
dataBuffer).getNativeBuffer())))
.responseConnection((res, connection) -> {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("NettyHttpClient response: status={}",
res.status().code());
+ }
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_CONN_ATTR, connection);
final ServerHttpResponse response = exchange.getResponse();
@@ -89,7 +99,14 @@ public class NettyHttpClientPlugin extends
AbstractHttpClientPlugin<HttpClientRe
} else {
throw new IllegalStateException("Unable to set status
code on response: " + res.status().code() + ", " + response.getClass());
}
- response.getHeaders().putAll(headers);
+ try {
+ response.getHeaders().putAll(headers);
+ } catch (UnsupportedOperationException ex) {
+ LOG.warn("Failed to set response headers because they
are read-only. "
+ + "This may indicate unexpected response
decorator usage. "
+ + "responseClass={}, statusCode={},
message={}",
+ response.getClass().getName(),
res.status().code(), ex.getMessage());
+ }
return Mono.just(res);
}));
}
diff --git
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
index e77649d809..9cf25383e8 100644
---
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
+++
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/callback/ShenyuToolCallback.java
@@ -215,11 +215,17 @@ public class ShenyuToolCallback implements ToolCallback {
final String configStr,
final String input) {
+ final RequestConfigHelper configHelper = new
RequestConfigHelper(configStr);
+ final String toolMethod = configHelper.getMethod();
+ final String toolUrl = configHelper.getUrlTemplate();
+ final JsonObject requestTemplate = configHelper.getRequestTemplate();
+ final String toolTimeout = requestTemplate.has("timeout") ?
requestTemplate.get("timeout").getAsString() : "default";
final CompletableFuture<String> responseFuture = new
CompletableFuture<>();
final ServerWebExchange decoratedExchange = buildDecoratedExchange(
originExchange, responseFuture, sessionId, configStr, input);
- LOG.debug("Executing plugin chain for session: {}", sessionId);
+ LOG.debug("Executing plugin chain for session: {} (method: {}, url:
{}, timeout: {})",
+ sessionId, toolMethod, toolUrl, toolTimeout);
// Check if this is a temporary session that needs cleanup
final boolean isTemporarySession = sessionId.startsWith("temp_");
@@ -233,7 +239,12 @@ public class ShenyuToolCallback implements ToolCallback {
responseFuture.completeExceptionally(e);
}
})
- .doOnSuccess(v -> LOG.debug("Plugin chain completed
successfully for session: {}", sessionId))
+ .doOnSuccess(v -> {
+ LOG.debug("Plugin chain completed successfully for
session: {}", sessionId);
+ if (!responseFuture.isDone()) {
+ responseFuture.complete("");
+ }
+ })
.doOnCancel(() -> {
LOG.warn("Plugin chain execution cancelled for session:
{}", sessionId);
if (!responseFuture.isDone()) {
@@ -352,7 +363,15 @@ public class ShenyuToolCallback implements ToolCallback {
final String path = RequestConfigHelper.buildPath(urlTemplate,
argsPosition, inputJson);
// Build body with parameter formatting (only format body parameters
that need it)
- final JsonObject bodyJson = buildFormattedBodyJson(argsToJsonBody,
argsPosition, inputJson);
+ JsonObject bodyJson = buildFormattedBodyJson(argsToJsonBody,
argsPosition, inputJson);
+ // Fallback: if argsToJsonBody is false but input has content for body
methods, use the raw input as body
+ if (!argsToJsonBody && bodyJson.size() == 0 &&
isRequestBodyMethod(method) && inputJson.size() > 0) {
+ LOG.warn("Using fallback body mapping: argsToJsonBody=false and no
body-mapped args, "
+ + "but method {} expects a request body and inputJson has
content. "
+ + "Using full inputJson as request body. Check tool
configuration (urlTemplate={}, argsPosition={}).",
+ method, urlTemplate, argsPosition);
+ bodyJson = inputJson.deepCopy();
+ }
return new RequestConfig(method, path, bodyJson, requestTemplate,
argsToJsonBody, inputJson);
}
@@ -603,9 +622,14 @@ public class ShenyuToolCallback implements ToolCallback {
*/
private ServerWebExchange handleRequestBody(final ServerWebExchange
decoratedExchange,
final RequestConfig
requestConfig) {
- if (isRequestBodyMethod(requestConfig.getMethod()) &&
requestConfig.getBodyJson().size() > 0) {
- return new BodyWriterExchange(decoratedExchange,
requestConfig.getBodyJson().toString());
+ if (isRequestBodyMethod(requestConfig.getMethod())
+ && Objects.nonNull(requestConfig.getBodyJson())
+ && requestConfig.getBodyJson().size() > 0) {
+ String body = requestConfig.getBodyJson().toString();
+ return new BodyWriterExchange(decoratedExchange, body);
}
+ // For methods without a request body or when no body is configured,
+ // return the original exchange to avoid sending an unintended "{}"
body.
return decoratedExchange;
}
diff --git
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/response/NonCommittingMcpResponseDecorator.java
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/response/NonCommittingMcpResponseDecorator.java
index de39a2ab9e..233f07e77a 100644
---
a/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/response/NonCommittingMcpResponseDecorator.java
+++
b/shenyu-plugin/shenyu-plugin-mcp-server/src/main/java/org/apache/shenyu/plugin/mcp/server/response/NonCommittingMcpResponseDecorator.java
@@ -102,6 +102,16 @@ public class NonCommittingMcpResponseDecorator extends
ServerHttpResponseDecorat
.doOnError(error -> handleProcessingError("writeAndFlushWith",
error));
}
+ @Override
+ public Mono<Void> setComplete() {
+ LOG.debug("Completing response for session: {}", sessionId);
+ if (!responseFuture.isDone()) {
+ responseFuture.complete(processResponse(""));
+ }
+ // Non-committing: do not write to the underlying response.
+ return Mono.empty();
+ }
+
/**
* Processes the collected response data buffers and completes the
response future.
*