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 7e368839ea fix: limit request body size and stream multipart form data
(#6950)
7e368839ea is described below
commit 7e368839ea9a58ad6f0bcbb3b891c59d4c091a65
Author: hengyuss <[email protected]>
AuthorDate: Sat Sep 5 14:21:13 2026 +0800
fix: limit request body size and stream multipart form data (#6950)
Co-authored-by: aias00 <[email protected]>
---
.../shenyu/plugin/base/utils/MediaTypeUtils.java | 3 +-
.../shenyu/plugin/httpclient/WebClientPlugin.java | 8 +++-
.../plugin/httpclient/WebClientPluginTest.java | 44 +++++++++++++++++++---
.../httpclient/HttpClientPluginConfiguration.java | 6 ++-
4 files changed, 51 insertions(+), 10 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/MediaTypeUtils.java
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/MediaTypeUtils.java
index 5231b06588..26b4543d89 100644
---
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/MediaTypeUtils.java
+++
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/utils/MediaTypeUtils.java
@@ -37,6 +37,7 @@ public class MediaTypeUtils {
//APPLICATION_STREAM_JSON is deprecated
|| MediaType.APPLICATION_NDJSON.isCompatibleWith(mediaType)
|| MediaType.APPLICATION_PDF.isCompatibleWith(mediaType)
- ||
MediaType.APPLICATION_OCTET_STREAM.isCompatibleWith(mediaType);
+ ||
MediaType.APPLICATION_OCTET_STREAM.isCompatibleWith(mediaType)
+ || MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType);
}
}
diff --git
a/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/WebClientPlugin.java
b/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/WebClientPlugin.java
index a26d6e42e8..3e34067a43 100644
---
a/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/WebClientPlugin.java
+++
b/shenyu-plugin/shenyu-plugin-httpclient/src/main/java/org/apache/shenyu/plugin/httpclient/WebClientPlugin.java
@@ -43,13 +43,17 @@ public class WebClientPlugin extends
AbstractHttpClientPlugin<ResponseEntity<Flu
private final WebClient webClient;
+ private final int maxInMemorySize;
+
/**
* Instantiates a new Web client plugin.
*
* @param webClient the web client
+ * @param maxInMemorySize the maximum number of bytes to buffer in memory
*/
- public WebClientPlugin(final WebClient webClient) {
+ public WebClientPlugin(final WebClient webClient, final int
maxInMemorySize) {
this.webClient = webClient;
+ this.maxInMemorySize = maxInMemorySize;
}
@Override
@@ -80,7 +84,7 @@ public class WebClientPlugin extends
AbstractHttpClientPlugin<ResponseEntity<Flu
return outputMessage.writeWith(body);
}
// fix chinese garbled code
- return outputMessage.writeWith(DataBufferUtils.join(body));
+ return outputMessage.writeWith(DataBufferUtils.join(body,
maxInMemorySize));
});
}
final WebClient.ResponseSpec responseSpec = requestHeadersSpec
diff --git
a/shenyu-plugin/shenyu-plugin-httpclient/src/test/java/org/apache/shenyu/plugin/httpclient/WebClientPluginTest.java
b/shenyu-plugin/shenyu-plugin-httpclient/src/test/java/org/apache/shenyu/plugin/httpclient/WebClientPluginTest.java
index 0af86ea258..7c06712862 100644
---
a/shenyu-plugin/shenyu-plugin-httpclient/src/test/java/org/apache/shenyu/plugin/httpclient/WebClientPluginTest.java
+++
b/shenyu-plugin/shenyu-plugin-httpclient/src/test/java/org/apache/shenyu/plugin/httpclient/WebClientPluginTest.java
@@ -34,19 +34,29 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferLimitException;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.mock.http.client.reactive.MockClientHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
+import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -77,7 +87,7 @@ public final class WebClientPluginTest {
when(context.getBean(ShenyuResult.class)).thenReturn(mock(ShenyuResult.class));
WebClient webClient = mockWebClientOK();
- webClientPlugin = new WebClientPlugin(webClient);
+ webClientPlugin = new WebClientPlugin(webClient,
Constants.BYTES_PER_MB);
}
/**
@@ -90,7 +100,7 @@ public final class WebClientPluginTest {
ServerWebExchange exchangeNoPathTest = MockServerWebExchange
.from(MockServerHttpRequest.get("/test").build());
exchangeNoPathTest.getAttributes().put(Constants.CONTEXT,
mock(ShenyuContext.class));
- WebClientPlugin webClientPluginNoPathTest = new
WebClientPlugin(webClientNoPathTest);
+ WebClientPlugin webClientPluginNoPathTest = new
WebClientPlugin(webClientNoPathTest, Constants.BYTES_PER_MB);
Mono<Void> monoNoPathTest =
webClientPluginNoPathTest.execute(exchangeNoPathTest, chainNoPathTest);
StepVerifier.create(monoNoPathTest).expectSubscription().verifyComplete();
@@ -100,19 +110,19 @@ public final class WebClientPluginTest {
.from(MockServerHttpRequest.post("/test123?param=1").build());
exchangePostTest.getAttributes().put(Constants.CONTEXT,
mock(ShenyuContext.class));
exchangePostTest.getAttributes().put(Constants.HTTP_URI,
URI.create("/test123?param=1"));
- WebClientPlugin webClientPluginPostTest = new
WebClientPlugin(webClientPostTest);
+ WebClientPlugin webClientPluginPostTest = new
WebClientPlugin(webClientPostTest, Constants.BYTES_PER_MB);
Mono<Void> monoPostTest =
webClientPluginPostTest.execute(exchangePostTest, chainPostTest);
StepVerifier.create(monoPostTest).expectSubscription().verifyError();
final ShenyuPluginChain chainOkTest = mock(ShenyuPluginChain.class);
final WebClient webClientOkTest = mockWebClientOK();
- WebClientPlugin webClientPluginOkTest = new
WebClientPlugin(webClientOkTest);
+ WebClientPlugin webClientPluginOkTest = new
WebClientPlugin(webClientOkTest, Constants.BYTES_PER_MB);
Mono<Void> monoOkTest =
webClientPluginOkTest.execute(generateServerWebExchange(), chainOkTest);
StepVerifier.create(monoOkTest).expectSubscription().verifyError();
final ShenyuPluginChain chainErrorTest = mock(ShenyuPluginChain.class);
final WebClient webClientErrorTest = mockWebClientError();
- WebClientPlugin webClientPluginErrorTest = new
WebClientPlugin(webClientErrorTest);
+ WebClientPlugin webClientPluginErrorTest = new
WebClientPlugin(webClientErrorTest, Constants.BYTES_PER_MB);
Mono<Void> monoErrorTest =
webClientPluginErrorTest.execute(generateServerWebExchange(), chainErrorTest);
StepVerifier.create(monoErrorTest).expectSubscription().verifyError();
}
@@ -147,6 +157,30 @@ public final class WebClientPluginTest {
assertEquals(PluginEnum.WEB_CLIENT.getName(), webClientPlugin.named());
}
+ /**
+ * Test that a non-binary request body cannot exceed the configured
in-memory limit.
+ */
+ @Test
+ public void testRequestBodyExceedsMaxInMemorySize() {
+ final int maxInMemorySize = 4;
+ final WebClientPlugin plugin = new WebClientPlugin(mockWebClientOK(),
maxInMemorySize);
+ final DataBuffer body = DefaultDataBufferFactory.sharedInstance
+ .wrap("12345".getBytes(StandardCharsets.UTF_8));
+ final ServerWebExchange exchange =
MockServerWebExchange.from(MockServerHttpRequest.post("/test")
+ .contentType(MediaType.APPLICATION_JSON)
+ .build());
+
+ plugin.doRequest(exchange, HttpMethod.POST.name(),
URI.create("/test"), Flux.just(body)).subscribe();
+ final ClientRequest request = captor.getValue();
+ final MockClientHttpRequest outputMessage = new
MockClientHttpRequest(HttpMethod.POST, URI.create("/test"));
+ final BodyInserter.Context context = mock(BodyInserter.Context.class);
+ when(context.messageWriters()).thenReturn(Collections.emptyList());
+
+ StepVerifier.create(request.body().insert(outputMessage, context))
+ .expectError(DataBufferLimitException.class)
+ .verify();
+ }
+
private ServerWebExchange generateServerWebExchange() {
ServerWebExchange exchange =
MockServerWebExchange.from(MockServerHttpRequest.get("/test").build());
exchange.getAttributes().put(Constants.CONTEXT,
mock(ShenyuContext.class));
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-httpclient/src/main/java/org/apache/shenyu/springboot/starter/plugin/httpclient/HttpClientPluginConfiguration.java
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-httpclient/src/main/java/org/apache/shenyu/springboot/starter/plugin/httpclient/HttpClientPluginConfiguration.java
index cbd6b83765..6ebc42ec5c 100644
---
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-httpclient/src/main/java/org/apache/shenyu/springboot/starter/plugin/httpclient/HttpClientPluginConfiguration.java
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-httpclient/src/main/java/org/apache/shenyu/springboot/starter/plugin/httpclient/HttpClientPluginConfiguration.java
@@ -102,15 +102,17 @@ public class HttpClientPluginConfiguration {
public ShenyuPlugin webClientPlugin(
final HttpClientProperties properties,
final ObjectProvider<HttpClient> httpClient) {
+ int maxInMemorySize =
+ properties.getMaxInMemorySize() * Constants.BYTES_PER_MB;
WebClient webClient = WebClient.builder()
// fix Exceeded limit on max bytes to buffer
// detail see
https://stackoverflow.com/questions/59326351/configure-spring-codec-max-in-memory-size-when-using-reactiveelasticsearchclient
.exchangeStrategies(ExchangeStrategies.builder()
- .codecs(codecs ->
codecs.defaultCodecs().maxInMemorySize(properties.getMaxInMemorySize() *
Constants.BYTES_PER_MB))
+ .codecs(codecs ->
codecs.defaultCodecs().maxInMemorySize(maxInMemorySize))
.build())
.clientConnector(new
ReactorClientHttpConnector(Objects.requireNonNull(httpClient.getIfAvailable())))
.build();
- return new WebClientPlugin(webClient);
+ return new WebClientPlugin(webClient, maxInMemorySize);
}
}