diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 95a8fda0..bdde6211 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -775,6 +775,32 @@ spring: NOTE: At this time a URI using the `forward` protocol does not support using the retry filter. +=== RequestSize GatewayFilter Factory +The RequestSize GatewayFilter Factory can restrict a request from reaching the downstream service , when the request size is greater than the permissible limit. The filter takes `RequestSize` as parameter which is the permissible size limit of the request defined in bytes. + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: request_size_route + uri: http://localhost:8080/upload + predicates: + - Path=/upload + filters: + - name: RequestSize + args: + maxSize: 5000000 +---- + +The RequestSize GatewayFilter Factory set the response status as `413 Payload Too Large` with a additional header `errorMessage` when the Request is rejected due to size. Following is an example of such an `errorMessage` . + +`errorMessage` : `Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB` + +NOTE: The default Request size will be set to 5 MB if not provided as filter argument in route definition. + == Global Filters The `GlobalFilter` interface has the same signature as `GatewayFilter`. These are special filters that are conditionally applied to all routes. (This interface and usage are subject to change in future milestones). diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 33f23add..47cbdf65 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -64,6 +64,7 @@ import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatew import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory; @@ -574,6 +575,11 @@ public class GatewayAutoConfiguration { public RequestHeaderToRequestUriGatewayFilterFactory requestHeaderToRequestUriGatewayFilterFactory() { return new RequestHeaderToRequestUriGatewayFilterFactory(); } + + @Bean + public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() { + return new RequestSizeGatewayFilterFactory(); + } @Configuration @ConditionalOnClass(Health.class) diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java new file mode 100644 index 00000000..add5844b --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.gateway.filter.factory; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * This filter blocks the request, if the request size is more than the permissible size.The default request size is 5 MB. + * @author Arpan + */ +public class RequestSizeGatewayFilterFactory + extends AbstractGatewayFilterFactory { + + private static String PREFIX = "kMGTPE"; + private static String ERROR = "Request size is larger than permissible limit." + + " Request size is %s where permissible limit is %s"; + + public RequestSizeGatewayFilterFactory() { + super(RequestSizeGatewayFilterFactory.RequestSizeConfig.class); + } + + @Override + public GatewayFilter apply(RequestSizeGatewayFilterFactory.RequestSizeConfig requestSizeConfig) { + requestSizeConfig.validate(); + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + String contentLength = request.getHeaders().getFirst("content-length"); + if (!StringUtils.isEmpty(contentLength)) { + Long currentRequestSize = Long.valueOf(contentLength); + if (currentRequestSize > requestSizeConfig.getMaxSize()) { + exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE); + exchange.getResponse().getHeaders().add("errorMessage", + getErrorMessage(currentRequestSize, requestSizeConfig.getMaxSize())); + return exchange.getResponse().setComplete(); + } + } + return chain.filter(exchange); + }; + } + + public static class RequestSizeConfig { + + private Long maxSize = 5000000L; + + public RequestSizeGatewayFilterFactory.RequestSizeConfig setMaxSize(Long maxSize) { + this.maxSize = maxSize; + return this; + } + + public Long getMaxSize() { + return maxSize; + } + + public void validate() { + Assert.isTrue(this.maxSize != null && this.maxSize > 0, "maxSize must be greater than 0"); + Assert.isInstanceOf(Long.class, maxSize, "maxSize must be a number"); + } + } + + private static String getErrorMessage(Long currentRequestSize, Long maxSize) { + return String.format(ERROR, getReadableByteCount(currentRequestSize), getReadableByteCount(maxSize)); + } + + private static String getReadableByteCount(long bytes) { + int unit = 1000; + if (bytes < unit) + return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = Character.toString(PREFIX.charAt(exp - 1)); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } +} \ No newline at end of file diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java index cfa4cc7e..aeacf39b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java @@ -47,6 +47,7 @@ import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatew import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory; @@ -555,6 +556,16 @@ public class GatewayFilterSpec extends UriSpec { }.apply(c -> { })); } + + + /** + * A filter that sets the maximum permissible size of a Request. + * @param size the maximum size of a request + * @return a {@link GatewayFilterSpec} that can be used to apply additional filters + */ + public GatewayFilterSpec setRequestSize(Long size) { + return filter(getBean(RequestSizeGatewayFilterFactory.class).apply(c -> c.setMaxSize(size))); + } private String routeId() { return routeBuilder.getId(); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java new file mode 100644 index 00000000..f11a2201 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java @@ -0,0 +1,54 @@ +package org.springframework.cloud.gateway.filter.factory; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Arpan Das + */ + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +@DirtiesContext +public class RequestSizeGatewayFilterFactoryTest extends BaseWebClientTests { + + private static final String responseMesssage = "Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB"; + + @Test + public void setRequestSizeFilterWorks() { + testClient.get().uri("/headers") + .header("Host", "www.setrequestsize.org") + .header("content-length", "6000000") + .exchange().expectStatus().isEqualTo(HttpStatus.PAYLOAD_TOO_LARGE) + .expectHeader().valueMatches("errorMessage", responseMesssage); + } + + @EnableAutoConfiguration + @SpringBootConfiguration + @Import(DefaultTestConfig.class) + public static class TestConfig { + @Value("${test.uri}") + String uri; + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes().route("test_request_size", + r -> r.order(-1).host("**.setrequestsize.org").filters(f -> f.setRequestSize(5000000L)).uri(uri)) + .build(); + } + } +} \ No newline at end of file