Merge branch '3.1.x'

This commit is contained in:
spencergibb
2022-03-21 12:48:53 -04:00
4 changed files with 89 additions and 23 deletions

View File

@@ -1143,6 +1143,29 @@ spring:
This will remove the `red` parameter before it is sent downstream.
=== `RequestHeaderSize` `GatewayFilter` Factory
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
====
[source,yaml]
----
spring:
cloud:
gateway:
routes:
- id: requestheadersize_route
uri: https://example.org
filters:
- RequestHeaderSize=1000B
----
====
This will send a status 431 if size of any request header is greater than 1000 Bytes.
=== The `RewritePath` `GatewayFilter` Factory
The `RewritePath` `GatewayFilter` factory takes a path `regexp` parameter and a `replacement` parameter.

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;
@@ -34,18 +35,20 @@ import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
/**
* This filter validates the size of each Request Header in the request. If size of any of
* the request header is greater than the configured maxSize,it blocks the request.
* Default max size of request header is 16KB.
* This filter validates the size of each Request Header in the request, including the
* key. If size of the request header is greater than the configured maxSize, it blocks
* the request. Default max size of request header is 16KB.
*
* @author Sakalya Deshpande
* @author Marta Medio
*/
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);
@@ -58,24 +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()) {
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();
}
@@ -86,19 +95,24 @@ public class RequestHeaderSizeGatewayFilterFactory
@Override
public String toString() {
return filterToStringCreator(RequestHeaderSizeGatewayFilterFactory.this)
.append("max", config.getMaxSize()).toString();
.append("maxSize", config.getMaxSize()).toString();
}
};
}
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;
}
@@ -107,6 +121,14 @@ public class RequestHeaderSizeGatewayFilterFactory
this.maxSize = maxSize;
}
public String getErrorHeaderName() {
return errorHeaderName;
}
public void setErrorHeaderName(String errorHeaderName) {
this.errorHeaderName = errorHeaderName;
}
}
}

View File

@@ -39,29 +39,47 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
/**
* @author Sakalya Deshpande
* @author Marta Medio
*/
@SpringBootTest(webEnvironment = RANDOM_PORT)
@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("/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
@@ -69,7 +87,7 @@ public class RequestHeaderSizeGatewayFilterFactoryTest extends BaseWebClientTest
Config config = new Config();
config.setMaxSize(DataSize.ofBytes(1000L));
GatewayFilter filter = new RequestHeaderSizeGatewayFilterFactory().apply(config);
assertThat(filter.toString()).contains("max").contains("1000B");
assertThat(filter.toString()).contains("maxSize").contains("1000B");
}
@EnableAutoConfiguration
@@ -86,6 +104,9 @@ public class RequestHeaderSizeGatewayFilterFactoryTest extends BaseWebClientTest
.route("test_request_header_size",
r -> r.order(-1).host("**.testrequestheadersizefilter.org")
.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(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