diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index ef2c7b4e..785de171 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -1145,8 +1145,8 @@ This will remove the `red` parameter before it is sent downstream. === `RequestHeaderSize` `GatewayFilter` Factory -The `RequestHeaderSize` `GatewayFilter` factory takes a `maxSize` parameter. -It is the maximum data size allowed of the request header (incuding key and value). +The `RequestHeaderSize` `GatewayFilter` factory takes `maxSize` and `errorHeaderName` parameters. +The `maxSize` parameter is the maximum data size allowed of the request header (incuding key and value). The `errorHeaderName` parameter sets the name of the response header containing an error message, by default it is "errorMessage". The following listing configures a `RequestHeaderSize` `GatewayFilter`: .application.yml diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java index e8edcc46..02d4c8c2 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.filter.factory; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -45,8 +46,9 @@ import static org.springframework.cloud.gateway.support.GatewayToStringStyler.fi public class RequestHeaderSizeGatewayFilterFactory extends AbstractGatewayFilterFactory { - private static String ERROR = "Request Header/s size is larger than permissible limit." - + " Request Header/s size is %s where permissible limit is %s"; + private static String ERROR_PREFIX = "Request Header/s size is larger than permissible limit (%s)."; + + private static String ERROR = " Request Header/s size for '%s' is %s."; public RequestHeaderSizeGatewayFilterFactory() { super(RequestHeaderSizeGatewayFilterFactory.Config.class); @@ -59,25 +61,30 @@ public class RequestHeaderSizeGatewayFilterFactory @Override public GatewayFilter apply(RequestHeaderSizeGatewayFilterFactory.Config config) { + String errorHeaderName = config.getErrorHeaderName() != null ? config.getErrorHeaderName() : "errorMessage"; return new GatewayFilter() { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); - Long headerSizeInBytes = 0L; + HashMap longHeaders = new HashMap<>(); for (Map.Entry> headerEntry : headers.entrySet()) { - headerSizeInBytes += (long) headerEntry.getKey().getBytes().length; + long headerSizeInBytes = 0L; + headerSizeInBytes += headerEntry.getKey().getBytes().length; List values = headerEntry.getValue(); for (String value : values) { - headerSizeInBytes += (long) value.getBytes().length; + headerSizeInBytes += value.getBytes().length; + } + if (headerSizeInBytes > config.getMaxSize().toBytes()) { + longHeaders.put(headerEntry.getKey(), headerSizeInBytes); } } - if (headerSizeInBytes > config.getMaxSize().toBytes()) { + if (!longHeaders.isEmpty()) { exchange.getResponse().setStatusCode(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE); - exchange.getResponse().getHeaders().add("errorMessage", - getErrorMessage(headerSizeInBytes, config.getMaxSize())); + exchange.getResponse().getHeaders().add(errorHeaderName, + getErrorMessage(longHeaders, config.getMaxSize())); return exchange.getResponse().setComplete(); } @@ -93,14 +100,19 @@ public class RequestHeaderSizeGatewayFilterFactory }; } - private static String getErrorMessage(Long currentRequestSize, DataSize maxSize) { - return String.format(ERROR, DataSize.of(currentRequestSize, DataUnit.BYTES), maxSize); + private static String getErrorMessage(HashMap longHeaders, DataSize maxSize) { + StringBuilder msg = new StringBuilder(String.format(ERROR_PREFIX, maxSize)); + longHeaders + .forEach((header, size) -> msg.append(String.format(ERROR, header, DataSize.of(size, DataUnit.BYTES)))); + return msg.toString(); } public static class Config { private DataSize maxSize = DataSize.ofBytes(16000L); + private String errorHeaderName; + public DataSize getMaxSize() { return maxSize; } @@ -109,6 +121,13 @@ public class RequestHeaderSizeGatewayFilterFactory this.maxSize = maxSize; } + public String getErrorHeaderName() { + return errorHeaderName; + } + + public void setErrorHeaderName(String errorHeaderName) { + this.errorHeaderName = errorHeaderName; + } } } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java index 8f56f0ea..70b23798 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java @@ -46,31 +46,40 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @DirtiesContext public class RequestHeaderSizeGatewayFilterFactoryTest extends BaseWebClientTests { - private static final String responseMesssage = "Request Header/s size is larger than " - + "permissible limit. Request Header/s size is \\d*B where permissible limit is \\d*B"; + private static final String longString = "11111111112222222222333333333344444444445555555"; @Test public void setRequestHeaderSizeFilterWorks() { + System.err.println("Here: " + longString.length() + ", " + longString.getBytes().length); testClient.get().uri("/headers").header("Host", "www.testrequestheadersizefilter.org") - .header("HeaderName", "Some Very Large Header Name").exchange().expectStatus() - .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() - .valueMatches("errorMessage", responseMesssage); + .header("HeaderName", longString).exchange().expectStatus() + .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader().value("errorMessage", + header -> assertThat(header).contains("permissible limit (46B)", "'HeaderName' is 57B")); } @Test public void setRequestHeaderSizeFilterShortcutWorks() { - testClient.get().uri("/headers").header("Host", "www.requestheadersize.org") - .header("HeaderName", "Some Very Large Header Name").exchange().expectStatus() + testClient.get().uri("/headers").header("Host", "www.requestheadersize.org").header("HeaderName", longString) + .exchange().expectStatus().isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() + .value("errorMessage", + header -> assertThat(header).contains("permissible limit (46B)", "'HeaderName' is 57B")); + } + + @Test + public void setRequestHeaderSizeFilterMultipleHeadersWorks() { + testClient.get().uri("/headers").header("Host", "www.requestheadersize.org").header("HeaderName", longString) + .header("HeaderName2", longString).exchange().expectStatus() .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() - .valueMatches("errorMessage", responseMesssage); + .value("errorMessage", header -> assertThat(header).contains("permissible limit (46B)", + "'HeaderName' is 57B", "'HeaderName2' is 58B")); } @Test public void setRequestHeaderSizeFilterTakesIntoAccountHeaderName() { - testClient.get().uri("/headerName").header("Host", "www.testrequestheadersizefiltername.org") - .header("HeaderName", "X").exchange().expectStatus() - .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() - .valueMatches("errorMessage", responseMesssage); + testClient.get().uri("/headers").header("Host", "www.testrequestheadersizefiltername.org") + .header("HeaderName", longString).exchange().expectStatus() + .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader().value("errorMessage", + header -> assertThat(header).contains("permissible limit (47B)", "'HeaderName' is 57B")); } @Test @@ -97,7 +106,7 @@ public class RequestHeaderSizeGatewayFilterFactoryTest extends BaseWebClientTest .filters(f -> f.setRequestHeaderSize(DataSize.of(46L, DataUnit.BYTES))).uri(uri)) .route("test_request_header_size_name", r -> r.order(1).host("**.testrequestheadersizefiltername.org") - .filters(f -> f.setRequestHeaderSize(DataSize.of(1L, DataUnit.BYTES))).uri(uri)) + .filters(f -> f.setRequestHeaderSize(DataSize.of(47L, DataUnit.BYTES))).uri(uri)) .build(); } diff --git a/spring-cloud-gateway-server/src/test/resources/application.yml b/spring-cloud-gateway-server/src/test/resources/application.yml index 2628f122..bc4f2c43 100644 --- a/spring-cloud-gateway-server/src/test/resources/application.yml +++ b/spring-cloud-gateway-server/src/test/resources/application.yml @@ -304,7 +304,7 @@ spring: predicates: - Host=**.requestheadersize.org filters: - - RequestHeaderSize=4 + - RequestHeaderSize=46 # ===================================== - id: rewrite_location_response_header_test