Merge branch 'main' into pr/3798
This commit is contained in:
2
.github/workflows/maven.yml
vendored
2
.github/workflows/maven.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'liberica'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
- name: Build with Maven
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
[[seturi-gatewayfilter-factory]]
|
||||
= `SetRequestUri` `GatewayFilter` Factory
|
||||
|
||||
The `SetRequestUri` `GatewayFilter` factory takes a `uri` parameter.
|
||||
It offers a simple way to manipulate the request uri by allowing templated segments of the path.
|
||||
This uses the URI templates from Spring Framework.
|
||||
Multiple matching segments are allowed.
|
||||
The following listing configures a `SetRequestUri` `GatewayFilter`:
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: seturi_route
|
||||
uri: no://op
|
||||
predicates:
|
||||
- Path=/{appId}/**
|
||||
filters:
|
||||
- SetRequestUri=http://{appId}.example.com
|
||||
----
|
||||
|
||||
For a request path of `/red-application/blue`, this sets the uri to `http://red-application.example.com` before making the downstream request and the final url, including path is going to be `http://red-application.example.com/red-application/blue`
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
To include Spring Cloud Gateway Server Web MVC in your project, use the starter with a group ID of `org.springframework.cloud` and an artifact ID of `spring-cloud-starter-gateway-server-webmvc`.
|
||||
See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] for details on setting up your build system with the current Spring Cloud Release Train.
|
||||
|
||||
If you include the starter, but you do not want the gateway to be enabled, set `spring.cloud.gateway.mvc.enabled=false`.
|
||||
If you include the starter, but you do not want the gateway to be enabled, set `spring.cloud.gateway.server.webmvc.enabled=false`.
|
||||
|
||||
IMPORTANT: Spring Cloud Gateway Server MVC is built on https://spring.io/projects/spring-boot#learn[Spring Boot] and https://docs.spring.io/spring-framework/reference/web/webmvc-functional.html[Spring WebMvc.fn].
|
||||
As a consequence, many of the asynchronous or reactive libraries may not apply when you use Spring Cloud Gateway Server MVC.
|
||||
|
||||
@@ -146,4 +146,30 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>github_actions</id>
|
||||
<!-- TODO: github actions fails with restricted header host -->
|
||||
<activation>
|
||||
<property>
|
||||
<name>env.GITHUB_ACTIONS</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<jdk.httpclient.allowRestrictedHeaders>host</jdk.httpclient.allowRestrictedHeaders>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -22,11 +22,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureBefore(GatewayServerMvcAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".enabled", matchIfMissing = true)
|
||||
public class GatewayMvcClassPathWarningAutoConfiguration {
|
||||
|
||||
private static final Log log = LogFactory.getLog(GatewayMvcClassPathWarningAutoConfiguration.class);
|
||||
|
||||
@@ -78,7 +78,7 @@ import org.springframework.web.client.RestClient;
|
||||
@AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, FilterAutoConfiguration.class, HandlerFunctionAutoConfiguration.class,
|
||||
PredicateAutoConfiguration.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".enabled", matchIfMissing = true)
|
||||
@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class)
|
||||
@ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class)
|
||||
public class GatewayServerMvcAutoConfiguration {
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR;
|
||||
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
|
||||
@@ -216,7 +215,7 @@ public abstract class BeforeFilterFunctions {
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(request.params());
|
||||
queryParams.remove(name);
|
||||
|
||||
MultiValueMap<String, String> encodedQueryParams = UriUtils.encodeQueryParams(queryParams);
|
||||
MultiValueMap<String, String> encodedQueryParams = MvcUtils.encodeQueryParams(queryParams);
|
||||
|
||||
// remove from uri
|
||||
URI newUri = UriComponentsBuilder.fromUri(request.uri())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
|
||||
@@ -47,6 +48,7 @@ import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilte
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -204,6 +206,27 @@ public class GatewayServerMvcAutoConfigurationTests {
|
||||
assertThat(builder).isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadBalancerFunctionHandlerAdded() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class,
|
||||
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class))
|
||||
.run(context -> assertThat(context).hasBean("lbHandlerFunctionDefinition"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadBalancerFunctionHandlerNotAddedWhenNoLoadBalancerClientOnClasspath() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class,
|
||||
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(LoadBalancerClient.class))
|
||||
.run(context -> assertThat(context).doesNotHaveBean("lbHandlerFunctionDefinition"));
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.server.mvc;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link FilterAutoConfiguration.LoadBalancerHandlerConfiguration}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*
|
||||
*/
|
||||
@SpringBootTest(classes = { ServerMvcLoadBalancerIntegrationTests.Config.class, FilterAutoConfiguration.class },
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
@ActiveProfiles("lb")
|
||||
public class ServerMvcLoadBalancerIntegrationTests {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
TestRestClient testRestClient;
|
||||
|
||||
@Test
|
||||
void shouldUseLbHandlerFunctionDefinitionToResolveHost() {
|
||||
testRestClient.get().uri("http://localhost:" + port + "/test").exchange().expectStatus().isOk();
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
@LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class)
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = { "spring.cloud.gateway.mvc.http-client.type=jdk" },
|
||||
@SpringBootTest(properties = { "spring.http.client.factory=jdk" },
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
public class VanillaRouterFunctionTests {
|
||||
|
||||
@@ -119,6 +119,7 @@ class BeforeFilterFunctionsTests {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path")
|
||||
.param("foo[]", "bar")
|
||||
.param("baz", "qux")
|
||||
.param("quux", "corge+")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
@@ -126,7 +127,9 @@ class BeforeFilterFunctionsTests {
|
||||
ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo[]", "replacement[]").apply(request);
|
||||
|
||||
assertThat(result.param("foo[]")).isPresent().hasValue("replacement[]");
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo%5B%5D=replacement%5B%5D");
|
||||
assertThat(result.param("quux")).isPresent().hasValue("corge+");
|
||||
assertThat(result.uri().toString())
|
||||
.hasToString("http://localhost/path?quux=corge%2B&baz=qux&foo%5B%5D=replacement%5B%5D");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routesMap:
|
||||
testsimplefunction:
|
||||
uri: fn:upper
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
mvc:
|
||||
routes:
|
||||
- id: test
|
||||
uri: lb://httpbin
|
||||
predicates:
|
||||
- Path=/test/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routesMap:
|
||||
route1:
|
||||
uri: https://example1.com
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routesMap:
|
||||
testsimplestream:
|
||||
uri: stream:hello-out-0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
strip.prefix.static.uri: http://${httpbin.host}:${httpbin.port}
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routes:
|
||||
- id: strip_prefix_static_port_config
|
||||
uri: ${strip.prefix.static.uri}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routes:
|
||||
- id: weight_high_test
|
||||
uri: https://examplel1.com
|
||||
|
||||
@@ -111,6 +111,7 @@ 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.SetRequestUriGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
|
||||
@@ -722,6 +723,12 @@ public class GatewayAutoConfiguration {
|
||||
return new RequestHeaderToRequestUriGatewayFilterFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledFilter
|
||||
public SetRequestUriGatewayFilterFactory setRequestUriGatewayFilterFactory() {
|
||||
return new SetRequestUriGatewayFilterFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledFilter
|
||||
public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() {
|
||||
|
||||
@@ -24,12 +24,12 @@ 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.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
|
||||
@@ -59,7 +59,8 @@ public class RemoveRequestParameterGatewayFilterFactory
|
||||
queryParams.remove(config.getName());
|
||||
|
||||
try {
|
||||
MultiValueMap<String, String> encodedQueryParams = UriUtils.encodeQueryParams(queryParams);
|
||||
MultiValueMap<String, String> encodedQueryParams = ServerWebExchangeUtils
|
||||
.encodeQueryParams(queryParams);
|
||||
URI newUri = UriComponentsBuilder.fromUri(request.getURI())
|
||||
.replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams))
|
||||
.build(true)
|
||||
|
||||
@@ -24,13 +24,13 @@ 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.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
|
||||
@@ -71,7 +71,8 @@ public class RewriteRequestParameterGatewayFilterFactory
|
||||
}
|
||||
|
||||
try {
|
||||
MultiValueMap<String, String> encodedQueryParams = UriUtils.encodeQueryParams(queryParams);
|
||||
MultiValueMap<String, String> encodedQueryParams = ServerWebExchangeUtils
|
||||
.encodeQueryParams(queryParams);
|
||||
URI uri = uriComponentsBuilder.replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams))
|
||||
.build(true)
|
||||
.toUri();
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.getUriTemplateVariables;
|
||||
|
||||
/**
|
||||
* This filter changes the request uri.
|
||||
*
|
||||
* @author Stepan Mikhailiuk
|
||||
*/
|
||||
public class SetRequestUriGatewayFilterFactory
|
||||
extends AbstractChangeRequestUriGatewayFilterFactory<SetRequestUriGatewayFilterFactory.Config> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SetRequestUriGatewayFilterFactory.class);
|
||||
|
||||
public SetRequestUriGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(NAME_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
// AbstractChangeRequestUriGatewayFilterFactory.apply() returns
|
||||
// OrderedGatewayFilter
|
||||
OrderedGatewayFilter gatewayFilter = (OrderedGatewayFilter) super.apply(config);
|
||||
return new OrderedGatewayFilter(gatewayFilter, gatewayFilter.getOrder()) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return filterToStringCreator(SetRequestUriGatewayFilterFactory.this)
|
||||
.append("template", config.getTemplate())
|
||||
.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
String getUri(ServerWebExchange exchange, Config config) {
|
||||
String template = config.getTemplate();
|
||||
|
||||
if (template.indexOf('{') == -1) {
|
||||
return template;
|
||||
}
|
||||
|
||||
Map<String, String> variables = getUriTemplateVariables(exchange);
|
||||
return UriComponentsBuilder.fromUriString(template).build().expand(variables).toUriString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<URI> determineRequestUri(ServerWebExchange exchange, Config config) {
|
||||
try {
|
||||
String url = getUri(exchange, config);
|
||||
URI uri = URI.create(url);
|
||||
if (!uri.isAbsolute()) {
|
||||
log.info("Request url is invalid: url={}, error=URI is not absolute", url);
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
return Optional.of(uri);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
log.info("Request url is invalid : url={}, error={}", config.getTemplate(), e.getMessage());
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
|
||||
private String template;
|
||||
|
||||
public String getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public void setTemplate(String template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,6 +58,7 @@ import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGa
|
||||
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetRequestUriGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
|
||||
@@ -869,6 +870,16 @@ public class GatewayFilterSpec extends UriSpec {
|
||||
return filter(getBean(RequestHeaderToRequestUriGatewayFilterFactory.class).apply(c -> c.setName(headerName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter which changes the URI the request will be routed to by the Gateway by
|
||||
* pulling it from a header on the request.
|
||||
* @param uri the URI
|
||||
* @return a {@link GatewayFilterSpec} that can be used to apply additional filters
|
||||
*/
|
||||
public GatewayFilterSpec setRequestUri(String uri) {
|
||||
return filter(getBean(SetRequestUriGatewayFilterFactory.class).apply(c -> c.setTemplate(uri)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter which change the URI the request will be routed to by the Gateway.
|
||||
* @param determineRequestUri a {@link Function} which takes a
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.cloud.gateway.support;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -48,9 +50,13 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -260,6 +266,17 @@ public final class ServerWebExchangeUtils {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
public static MultiValueMap<String, String> encodeQueryParams(MultiValueMap<String, String> params) {
|
||||
MultiValueMap<String, String> encodedQueryParams = new LinkedMultiValueMap<>(params.size());
|
||||
for (Map.Entry<String, List<String>> entry : params.entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
encodedQueryParams.add(UriUtils.encode(entry.getKey(), StandardCharsets.UTF_8),
|
||||
UriUtils.encode(value, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
return CollectionUtils.unmodifiableMultiValueMap(encodedQueryParams);
|
||||
}
|
||||
|
||||
public static HttpStatus parse(String statusString) {
|
||||
HttpStatus httpStatus;
|
||||
|
||||
|
||||
@@ -1966,16 +1966,6 @@
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.routes",
|
||||
"type": "java.util.List<org.springframework.cloud.gateway.route.RouteDefinition>",
|
||||
"description": "List of Routes.",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webflux.mvc.routes",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.observability.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
@@ -100,6 +100,7 @@ public class DisableBuiltInFiltersTests {
|
||||
"spring.cloud.gateway.server.webflux.filter.secure-headers.enabled=false",
|
||||
"spring.cloud.gateway.server.webflux.filter.set-request-header.enabled=false",
|
||||
"spring.cloud.gateway.server.webflux.filter.set-request-host-header.enabled=false",
|
||||
"spring.cloud.gateway.server.webflux.filter.set-request-uri.enabled=false",
|
||||
"spring.cloud.gateway.server.webflux.filter.set-response-header.enabled=false",
|
||||
"spring.cloud.gateway.server.webflux.filter.rewrite-response-header.enabled=false",
|
||||
"spring.cloud.gateway.server.webflux.filter.rewrite-location-response-header.enabled=false",
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -24,6 +26,7 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
@@ -123,6 +126,23 @@ class RemoveRequestParameterGatewayFilterFactoryTests {
|
||||
assertThat(actualRequest.getQueryParams()).containsEntry("ccc", singletonList(",xyz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeRequestParameterFilterShouldHandleRemainingPlusSignParams() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest
|
||||
.method(HttpMethod.GET, URI.create("http://localhost?foo=bar&aaa=%2Bxyz"))
|
||||
.build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
NameConfig config = new NameConfig();
|
||||
config.setName("foo");
|
||||
GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config);
|
||||
|
||||
filter.filter(exchange, filterChain);
|
||||
|
||||
ServerHttpRequest actualRequest = captor.getValue().getRequest();
|
||||
assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo");
|
||||
assertThat(actualRequest.getQueryParams()).containsEntry("aaa", singletonList("+xyz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeRequestParameterFilterShouldHandleEncodedParameterName() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost")
|
||||
|
||||
@@ -88,6 +88,12 @@ class RewriteRequestParameterGatewayFilterFactoryTests {
|
||||
Map.of("campaign[]", List.of("blue"), "color", List.of("white")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewriteRequestParameterFilterWithPlusSign() {
|
||||
testRewriteRequestParameterFilter("color", "white+", "campaign=blue%2B&color=green",
|
||||
Map.of("campaign", List.of("blue+"), "color", List.of("white+")));
|
||||
}
|
||||
|
||||
private void testRewriteRequestParameterFilter(String name, String replacement, String query,
|
||||
Map<String, List<String>> expectedQueryParams) {
|
||||
GatewayFilter filter = new RewriteRequestParameterGatewayFilterFactory()
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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 org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.cloud.gateway.test.BaseWebClientTests;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
/**
|
||||
* @author Stepan Mikhailiuk
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
public class SetRequestUriGatewayFilterFactoryIntegrationTests extends BaseWebClientTests {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Test
|
||||
public void setUriWorkWithProperties() {
|
||||
testClient.get().uri("/").header("Host", "testservice.setrequesturi.org").exchange().expectStatus().isOk();
|
||||
|
||||
testClient.get()
|
||||
.uri("/service/testservice")
|
||||
.header("Host", "setrequesturi.org")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@Import(DefaultTestConfig.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("map_subdomain_to_service_name",
|
||||
r -> r.host("{serviceName}.setrequesturi.org")
|
||||
.filters(f -> f.prefixPath("/httpbin").setRequestUri("lb://{serviceName}"))
|
||||
.uri("no://op"))
|
||||
.route("map_path_to_service_name",
|
||||
r -> r.host("setrequesturi.org")
|
||||
.and()
|
||||
.path("/service/{serviceName}")
|
||||
.filters(f -> f.rewritePath("/.*", "/").setRequestUri("lb://{serviceName}"))
|
||||
.uri("no://op"))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.net.URI;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
|
||||
/**
|
||||
* @author Stepan Mikhailiuk
|
||||
*/
|
||||
public class SetRequestUriGatewayFilterFactoryTests {
|
||||
|
||||
@Test
|
||||
public void filterChangeRequestUri() {
|
||||
SetRequestUriGatewayFilterFactory factory = new SetRequestUriGatewayFilterFactory();
|
||||
GatewayFilter filter = factory.apply(c -> c.setTemplate("https://example.com"));
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build();
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost"));
|
||||
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
|
||||
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
|
||||
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
|
||||
filter.filter(exchange, filterChain);
|
||||
ServerWebExchange webExchange = captor.getValue();
|
||||
URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR);
|
||||
assertThat(uri).isNotNull();
|
||||
assertThat(uri.toString()).isEqualTo("https://example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterDoesNotChangeRequestUriIfUriIsInvalid() throws Exception {
|
||||
SetRequestUriGatewayFilterFactory factory = new SetRequestUriGatewayFilterFactory();
|
||||
GatewayFilter filter = factory.apply(c -> c.setTemplate("invalid_uri"));
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build();
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost"));
|
||||
GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
|
||||
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
|
||||
when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());
|
||||
filter.filter(exchange, filterChain);
|
||||
ServerWebExchange webExchange = captor.getValue();
|
||||
URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR);
|
||||
assertThat(uri).isNotNull();
|
||||
assertThat(uri.toURL().toString()).isEqualTo("http://localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringFormat() {
|
||||
SetRequestUriGatewayFilterFactory.Config config = new SetRequestUriGatewayFilterFactory.Config();
|
||||
config.setTemplate("http://localhost:8080");
|
||||
GatewayFilter filter = new SetRequestUriGatewayFilterFactory().apply(config);
|
||||
assertThat(filter.toString()).contains("http://localhost:8080");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,6 +66,8 @@ import static org.junit.Assume.assumeThat;
|
||||
RewriteLocationResponseHeaderGatewayFilterFactoryTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactoryTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.SetRequestUriGatewayFilterFactoryTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.SetRequestUriGatewayFilterFactoryIntegrationTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactoryTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactoryTests.class,
|
||||
org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactoryIntegrationTests.class,
|
||||
|
||||
@@ -34,5 +34,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- TODO: temporary, remove for 5.0 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -33,5 +33,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<!-- TODO: temporary, remove for 5.0 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -33,5 +33,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- TODO: temporary, remove for 5.0 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -34,5 +34,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<!-- TODO: temporary, remove for 5.0 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user