diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-proxy-exchange.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-proxy-exchange.adoc
index 5fb8d8f7..8e39d751 100644
--- a/docs/modules/ROOT/pages/spring-cloud-gateway-proxy-exchange.adoc
+++ b/docs/modules/ROOT/pages/spring-cloud-gateway-proxy-exchange.adoc
@@ -76,5 +76,5 @@ You can add headers to the downstream response by using the `header()` methods o
You can also manipulate response headers (and anything else you like in the response) by adding a mapper to the `get()` method (and other methods).
The mapper is a `Function` that takes the incoming `ResponseEntity` and converts it to an outgoing one.
-First-class support is provided for "`sensitive`" headers (by default, `cookie` and `authorization`), which are not passed downstream, and for "`proxy`" (`x-forwarded-*`) headers.
+First-class support is provided for "`sensitive`" headers (by default, `cookie` and `authorization`) and "`skipped`" headers (by default, `content-length` and `host`), which are not passed downstream, and for "`proxy`" (`x-forwarded-*`) headers. The idea behind "`skipped`" headers is that they may result in problems when copied over to the downstream request. For example: because of the way that the `ProxyExchange` calls the downstream endpoint the content's length might have changed or even use a `Transfer-Encoding: chunked` instead of a `Content-Length` header.
diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java
index e1bb63f8..531bee0f 100644
--- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java
+++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java
@@ -85,11 +85,11 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBody
*
*
* By default the incoming request body and headers are sent intact to the downstream
- * service (with the exception of "sensitive" headers). To manipulate the downstream
+ * service (with the exception of "excluded" headers). To manipulate the downstream
* request there are "builder" style methods in {@link ProxyExchange}, but only the
- * {@link #uri(String)} is mandatory. You can change the sensitive headers by calling the
- * {@link #sensitive(String...)} method (Authorization and Cookie are sensitive by
- * default).
+ * {@link #uri(String)} is mandatory. You can change the excluded headers by calling the
+ * {@link #excluded(String...)} method (the argument resolver will populate these with
+ * some sensible defaults).
*
*
* The type parameter T in ProxyExchange<T> is the type of
@@ -137,12 +137,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBody
*/
public class ProxyExchange {
- /**
- * Contains headers that are considered case-sensitive by default.
- */
- public static Set DEFAULT_SENSITIVE = Collections
- .unmodifiableSet(new HashSet<>(Arrays.asList("cookie", "authorization")));
-
private URI uri;
private RestTemplate rest;
@@ -157,7 +151,7 @@ public class ProxyExchange {
private WebDataBinderFactory binderFactory;
- private Set sensitive;
+ private Set excluded;
private HttpHeaders headers = new HttpHeaders();
@@ -210,19 +204,19 @@ public class ProxyExchange {
}
/**
- * Sets the names of sensitive headers that are not passed downstream to the backend
+ * Sets the names of excluded headers that are not passed downstream to the backend
* service.
- * @param names the names of sensitive headers
+ * @param names the names of excluded headers
* @return this for convenience
*/
- public ProxyExchange sensitive(String... names) {
- if (this.sensitive == null) {
- this.sensitive = new HashSet<>();
+ public ProxyExchange excluded(String... names) {
+ if (this.excluded == null) {
+ this.excluded = new HashSet<>();
}
- this.sensitive.clear();
+ this.excluded.clear();
for (String name : names) {
- this.sensitive.add(name.toLowerCase());
+ this.excluded.add(name.toLowerCase());
}
return this;
}
@@ -369,8 +363,8 @@ public class ProxyExchange {
}
private Set filterHeaderKeys(Collection headerNames) {
- final Set sensitiveHeaders = this.sensitive != null ? this.sensitive : DEFAULT_SENSITIVE;
- return headerNames.stream().filter(header -> !sensitiveHeaders.contains(header.toLowerCase()))
+ final Set excludedHeaders = this.excluded != null ? this.excluded : Collections.emptySet();
+ return headerNames.stream().filter(header -> !excludedHeaders.contains(header.toLowerCase()))
.collect(Collectors.toSet());
}
diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java
index 01f05cf1..b7a27418 100644
--- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java
+++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java
@@ -47,7 +47,7 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol
private Set autoForwardedHeaders;
- private Set sensitive;
+ private Set excluded;
public ProxyExchangeArgumentResolver(RestTemplate builder) {
this.rest = builder;
@@ -62,8 +62,8 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol
: autoForwardedHeaders.stream().map(String::toLowerCase).collect(toSet());
}
- public void setSensitive(Set sensitive) {
- this.sensitive = sensitive;
+ public void setExcluded(Set excluded) {
+ this.excluded = excluded;
}
@Override
@@ -77,7 +77,7 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol
ProxyExchange> proxy = new ProxyExchange<>(rest, webRequest, mavContainer, binderFactory, type(parameter));
configureHeaders(proxy);
configureAutoForwardedHeaders(proxy, webRequest);
- configureSensitive(proxy);
+ configureExcluded(proxy);
return proxy;
}
@@ -115,9 +115,9 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol
}
}
- private void configureSensitive(final ProxyExchange> proxy) {
- if (sensitive != null) {
- proxy.sensitive(sensitive.toArray(new String[0]));
+ private void configureExcluded(final ProxyExchange> proxy) {
+ if (excluded != null) {
+ proxy.excluded(excluded.toArray(new String[0]));
}
}
diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java
index 5ee4b605..c7f2f3f3 100644
--- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java
+++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java
@@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
*
* @author Dave Syer
* @author Tim Ysewyn
+ * @author Joris Kuipers
*
*/
@ConfigurationProperties("spring.cloud.gateway.proxy")
@@ -42,14 +43,19 @@ public class ProxyProperties {
private Map headers = new LinkedHashMap<>();
/**
- * A set of header names that should be send downstream by default.
+ * A set of header names that should be sent downstream by default.
*/
private Set autoForward = new HashSet<>();
/**
* A set of sensitive header names that will not be sent downstream by default.
*/
- private Set sensitive = null;
+ private Set sensitive = Set.of("cookie", "authorization");
+
+ /**
+ * A set of header names that will not be sent downstream because they could be problematic.
+ */
+ private Set skipped = Set.of("content-length", "host");
public Map getHeaders() {
return headers;
@@ -75,6 +81,14 @@ public class ProxyProperties {
this.sensitive = sensitive;
}
+ public Set getSkipped() {
+ return skipped;
+ }
+
+ public void setSkipped(Set skipped) {
+ this.skipped = skipped;
+ }
+
public HttpHeaders convertHeaders() {
HttpHeaders headers = new HttpHeaders();
for (String key : this.headers.keySet()) {
diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java
index 0b69bab0..4086dea6 100644
--- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java
+++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java
@@ -17,8 +17,10 @@
package org.springframework.cloud.gateway.mvc.config;
import java.io.IOException;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -70,7 +72,14 @@ public class ProxyResponseAutoConfiguration implements WebMvcConfigurer {
ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver(template);
resolver.setHeaders(proxy.convertHeaders());
resolver.setAutoForwardedHeaders(proxy.getAutoForward());
- resolver.setSensitive(proxy.getSensitive()); // can be null
+ Set excludedHeaderNames = new HashSet<>();
+ if (proxy.getSensitive() != null) {
+ excludedHeaderNames.addAll(proxy.getSensitive());
+ }
+ if (proxy.getSkipped() != null) {
+ excludedHeaderNames.addAll(proxy.getSkipped());
+ }
+ resolver.setExcluded(excludedHeaderNames);
return resolver;
}
diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java
index 5b2a0981..ea506dbb 100644
--- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java
+++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java
@@ -111,7 +111,7 @@ public class GetWithBodyRequestTests {
generateConfiguredRestTemplate());
resolver.setHeaders(proxy.convertHeaders());
resolver.setAutoForwardedHeaders(proxy.getAutoForward());
- resolver.setSensitive(proxy.getSensitive());
+ resolver.setExcluded(proxy.getSensitive());
return resolver;
}
diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java
index a0295e10..7fd4e976 100644
--- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java
+++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java
@@ -40,6 +40,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
@@ -56,8 +57,7 @@ import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
-@SpringBootTest(properties = { "spring.cloud.gateway.proxy.auto-forward=Baz" },
- webEnvironment = WebEnvironment.RANDOM_PORT)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = TestApplication.class)
public class ProductionConfigurationTests {
@@ -112,6 +112,21 @@ public class ProductionConfigurationTests {
.isEqualTo("host=localhost:" + port + ";foo");
}
+ @Test
+ public void postJsonWithWhitespace() {
+ var json = """
+ {
+ "foo": "bar"
+ }""";
+
+ var headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.setContentLength(json.length());
+ var request = new HttpEntity<>(json, headers);
+ assertThat(rest.postForEntity("/proxy/checkContentLength", request, Void.class).getStatusCode())
+ .isEqualTo(HttpStatus.OK);
+ }
+
@Test
public void forward() {
assertThat(rest.getForObject("/forward/foos/0", Foo.class).getName()).isEqualTo("bye");
@@ -424,7 +439,7 @@ public class ProductionConfigurationTests {
@GetMapping("/proxy/headers")
@SuppressWarnings("Duplicates")
public ResponseEntity