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);
         }
     }
 

Reply via email to