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 5231b06588af..26b4543d89e5 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 static boolean isByteType(final MediaType mediaType) { //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 a26d6e42e833..3e34067a43be 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>> doRequest(final ServerWebExchan 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 0af86ea258f3..7c06712862cb 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.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 void setup() { 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 void testExecuted() { 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 monoNoPathTest = webClientPluginNoPathTest.execute(exchangeNoPathTest, chainNoPathTest); StepVerifier.create(monoNoPathTest).expectSubscription().verifyComplete(); @@ -100,19 +110,19 @@ public void testExecuted() { .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 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 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 monoErrorTest = webClientPluginErrorTest.execute(generateServerWebExchange(), chainErrorTest); StepVerifier.create(monoErrorTest).expectSubscription().verifyError(); } @@ -147,6 +157,30 @@ public void testNamed() { 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 cbd6b837650d..6ebc42ec5cf0 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 @@ static class WebClientConfiguration { public ShenyuPlugin webClientPlugin( final HttpClientProperties properties, final ObjectProvider 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); } }