diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 3e6cec88..25130f29 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -1596,6 +1596,33 @@ errorMessage` : `Request size is larger than permissible limit. Request size is NOTE: The default request size is set to five MB if not provided as a filter argument in the route definition. +=== The `SetRequestHost` `GatewayFilter` Factory + +There are certain situation when the host header may need to be overridden. In this situation, the `SetRequestHost` `GatewayFilter` factory can replace the existing host header with a specified vaue. +The filter takes a `host` parameter. +The following listing configures a `SetRequestHost` `GatewayFilter`: + +.application.yml +==== +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: set_request_host_header_route + uri: http://localhost:8080/headers + predicates: + - Path=/headers + filters: + - name: SetRequestHost + args: + host: example.org +---- +==== + +The `SetRequestHost` `GatewayFilter` factory replaces the value of the host header with `example.org`. + === Modify a Request Body `GatewayFilter` Factory You can use the `ModifyRequestBody` filter filter to modify the request body before it is sent downstream by the gateway. 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 c2a50f8e..f97fca84 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 @@ -90,6 +90,7 @@ import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilt import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties; import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; @@ -525,6 +526,11 @@ public class GatewayAutoConfiguration { return new SetRequestHeaderGatewayFilterFactory(); } + @Bean + public SetRequestHostHeaderGatewayFilterFactory setRequestHostHeaderGatewayFilterFactory() { + return new SetRequestHostHeaderGatewayFilterFactory(); + } + @Bean public SetResponseHeaderGatewayFilterFactory setResponseHeaderGatewayFilterFactory() { return new SetResponseHeaderGatewayFilterFactory(); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java new file mode 100644 index 00000000..1024c651 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright 2013-2019 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 + * + * https://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 java.util.Collections; +import java.util.List; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; + +/** + * @author Andrew Fitzgerald + */ +public class SetRequestHostHeaderGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + public SetRequestHostHeaderGatewayFilterFactory() { + super(Config.class); + } + + @Override + public List shortcutFieldOrder() { + return Collections.singletonList("host"); + } + + @Override + public GatewayFilter apply(Config config) { + return new GatewayFilter() { + @Override + public Mono filter(ServerWebExchange exchange, + GatewayFilterChain chain) { + String value = ServerWebExchangeUtils.expand(exchange, config.getHost()); + + ServerHttpRequest request = exchange.getRequest().mutate() + .headers(httpHeaders -> { + httpHeaders.remove("Host"); + httpHeaders.add("Host", value); + }).build(); + + // Make sure the header we just set is preserved + exchange.getAttributes().put(PRESERVE_HOST_HEADER_ATTRIBUTE, true); + + return chain.filter(exchange.mutate().request(request).build()); + } + + @Override + public String toString() { + return filterToStringCreator( + SetRequestHostHeaderGatewayFilterFactory.this) + .append(config.getHost()).toString(); + } + }; + } + + public static class Config { + + private String host; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + } + +} 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 9871d5cc..e41a69d0 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 @@ -63,6 +63,7 @@ import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilter import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; @@ -383,6 +384,17 @@ public class GatewayFilterSpec extends UriSpec { return filter(getBean(PreserveHostHeaderGatewayFilterFactory.class).apply()); } + /** + * A filter that will set the Host header to + * {@param hostName} on the outgoing request + * @param hostName the updated Host header value + * @return a {@link GatewayFilterSpec} that can be used to apply additional filters + */ + public GatewayFilterSpec setHostHeader(String hostName) { + return filter(getBean(SetRequestHostHeaderGatewayFilterFactory.class) + .apply(c -> c.setHost(hostName))); + } + /** * A filter that will return a redirect response back to the client. * @param status an HTTP status code, should be a {@code 300} series redirect diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java new file mode 100644 index 00000000..048f3e38 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2013-2019 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 + * + * https://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 java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import static org.springframework.cloud.gateway.test.TestUtils.getMap; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +@DirtiesContext +public class SetRequestHostHeaderGatewayFilterFactoryTests extends BaseWebClientTests { + + @Test + public void setRequestHostHeaderFilterWorks() { + testClient.get().uri("/headers").header("Host", "www.setrequesthostheader.org") + .exchange().expectStatus().isOk().expectBody(Map.class) + .consumeWith(result -> { + Map headers = getMap(result.getResponseBody(), + "headers"); + assertThat(headers).hasEntrySatisfying("Host", + val -> assertThat(val).isEqualTo("otherhost.io")); + }); + } + + @Test + public void toStringFormat() { + SetRequestHostHeaderGatewayFilterFactory.Config config = new SetRequestHostHeaderGatewayFilterFactory.Config(); + config.setHost("myhost"); + GatewayFilter filter = new SetRequestHostHeaderGatewayFilterFactory() + .apply(config); + assertThat(filter.toString()).contains("myhost"); + } + + @EnableAutoConfiguration + @SpringBootConfiguration + @Import(DefaultTestConfig.class) + public static class TestConfig { + + } + +} diff --git a/spring-cloud-gateway-core/src/test/resources/application.yml b/spring-cloud-gateway-core/src/test/resources/application.yml index 0d176368..455782b7 100644 --- a/spring-cloud-gateway-core/src/test/resources/application.yml +++ b/spring-cloud-gateway-core/src/test/resources/application.yml @@ -364,6 +364,15 @@ spring: filters: - SecureHeaders + # ===================================== + - id: set_request_host_header_test + uri: ${test.uri} + predicates: + - Host=**.setrequesthostheader.org + - Path=/headers + filters: + - SetRequestHostHeader=otherhost.io + # ===================================== - id: set_path_test uri: ${test.uri}