Fixes RequestHeaderSizeGatewayFilterFactory to check individual headers.

Previously, it checked the aggregate length of all headers.

Fixes gh-2567
This commit is contained in:
spencergibb
2022-03-21 12:44:41 -04:00
parent ed88edd3a8
commit ec07a67930
4 changed files with 54 additions and 26 deletions

View File

@@ -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

View File

@@ -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<RequestHeaderSizeGatewayFilterFactory.Config> {
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<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
Long headerSizeInBytes = 0L;
HashMap<String, Long> longHeaders = new HashMap<>();
for (Map.Entry<String, List<String>> headerEntry : headers.entrySet()) {
headerSizeInBytes += (long) headerEntry.getKey().getBytes().length;
long headerSizeInBytes = 0L;
headerSizeInBytes += headerEntry.getKey().getBytes().length;
List<String> 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<String, Long> 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;
}
}
}

View File

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

View File

@@ -304,7 +304,7 @@ spring:
predicates:
- Host=**.requestheadersize.org
filters:
- RequestHeaderSize=4
- RequestHeaderSize=46
# =====================================
- id: rewrite_location_response_header_test