diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway/request-predicates-factories.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway/request-predicates-factories.adoc index 93a1e10e8e..c1cb13eed9 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway/request-predicates-factories.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway/request-predicates-factories.adoc @@ -192,6 +192,8 @@ This route matches if the request path was, for example: `/red/1` or `/red/1/` o If `matchTrailingSlash` is set to `false`, then request path `/red/1/` will not be matched. +If you have set `spring.webflux.base-path` property, this will influence the path matching. The property value will be automatically prepended to the path patterns. For example, with `spring.webflux.base-path=/app` and a path pattern of `/red/{segment}`, the full pattern used for matching would be `/app/red/{segment}`. + This predicate extracts the URI template variables (such as `segment`, defined in the preceding example) as a map of names and values and places it in the `ServerWebExchange.getAttributes()` with a key defined in `ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE`. Those values are then available for use by <> diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index be86096bea..e9d920f3aa 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -56,6 +56,7 @@ import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.ssl.SslBundles; @@ -190,6 +191,7 @@ * @author Mete Alpaslan Katırcıoğlu * @author Alberto C. Ríos * @author Olga Maciaszek-Sharma + * @author FuYiNan Guo */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) @@ -471,8 +473,8 @@ public MethodRoutePredicateFactory methodRoutePredicateFactory() { @Bean @ConditionalOnEnabledPredicate - public PathRoutePredicateFactory pathRoutePredicateFactory() { - return new PathRoutePredicateFactory(); + public PathRoutePredicateFactory pathRoutePredicateFactory(WebFluxProperties webFluxProperties) { + return new PathRoutePredicateFactory(webFluxProperties); } @Bean diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java index 509b78d70d..45550df5ca 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java @@ -24,8 +24,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.core.style.ToStringCreator; import org.springframework.http.server.PathContainer; +import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern.PathMatchInfo; @@ -41,6 +43,7 @@ /** * @author Spencer Gibb * @author Dhawal Kapil + * @author FuYiNan Guo */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { @@ -50,8 +53,20 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory apply(Config config) { synchronized (this.pathPatternParser) { pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash()); config.getPatterns().forEach(pattern -> { - PathPattern pathPattern = this.pathPatternParser.parse(pattern); + String basePath = webFluxProperties.getBasePath(); + boolean basePathIsNotBlank = StringUtils.hasText(basePath); + String pathPatternStr = pattern; + if (basePathIsNotBlank) { + if (pattern.length() > 1 && !pattern.startsWith("/")) { + basePath += ("/"); + } + pathPatternStr = basePath + pattern; + } + PathPattern pathPattern = this.pathPatternParser.parse(pathPatternStr); pathPatterns.add(pathPattern); }); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java index bb5c0d122e..c55087fc7f 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java @@ -16,25 +16,41 @@ package org.springframework.cloud.gateway.handler.predicate; +import java.net.URI; import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; import java.util.function.Predicate; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import reactor.core.publisher.Mono; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.route.Route; +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_ORIGINAL_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; /** * @author Spencer Gibb + * @author FuYiNan Guo */ public class GatewayPredicateVisitorTests { @Test public void asyncPredicateVisitVisitsEachNode() { - PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(); + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(new WebFluxProperties()); HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory(); ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory1 = new ReadBodyRoutePredicateFactory(); ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory2 = new ReadBodyRoutePredicateFactory(); @@ -55,7 +71,7 @@ public void asyncPredicateVisitVisitsEachNode() { @Test public void predicateVisitVisitsEachNode() { - PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(); + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(new WebFluxProperties()); HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory(); Predicate predicate = pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig()) .and(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig())); @@ -68,4 +84,59 @@ public void predicateVisitVisitsEachNode() { .hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class, HostRoutePredicateFactory.Config.class); } + @Test + public void pathRoutePredicateVisitWithSetWebfluxBasePath() { + WebFluxProperties webFluxProperties = new WebFluxProperties(); + webFluxProperties.setBasePath("/gw/api/v1"); + + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties); + PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() + .setPatterns(List.of("/temp/**")) + .setMatchTrailingSlash(true); + + Predicate predicate = pathRoutePredicateFactory.apply(config); + + ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test") + .build()); + + assertThat(predicate.test(exchange)).isEqualTo(true); + } + + @Test + public void pathRoutePredicateVisitWithSetWebfluxBasePathStripPrefix() { + WebFluxProperties webFluxProperties = new WebFluxProperties(); + webFluxProperties.setBasePath("/gw/api/v1"); + + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties); + PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() + .setPatterns(List.of("/temp/**")) + .setMatchTrailingSlash(true); + + Predicate predicate = pathRoutePredicateFactory.apply(config); + + ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test") + .build()); + + assertThat(predicate.test(exchange)).isEqualTo(true); + + // webflux base path strips prefix is 3 + GatewayFilter filter = new StripPrefixGatewayFilterFactory().apply(c -> c.setParts(3)); + + GatewayFilterChain filterChain = mock(GatewayFilterChain.class); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); + when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); + + filter.filter(exchange, filterChain); + + ServerWebExchange webExchange = captor.getValue(); + + assertThat(webExchange.getRequest().getURI()).hasPath("/temp/test"); + + URI requestUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + assertThat(requestUrl).hasScheme("http").hasHost("127.0.0.1").hasPort(8080).hasPath("/temp/test"); + + LinkedHashSet uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); + assertThat(uris).contains(exchange.getRequest().getURI()); + } } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index a27c75592d..539d85642f 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory.Config; @@ -133,14 +134,14 @@ public void matchOptionalTrailingSeparatorCopiedToMatchTrailingSlash() { @Test public void toStringFormat() { Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(false); - Predicate predicate = new PathRoutePredicateFactory().apply(config); + Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config); assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("false"); } @Test public void toStringFormatMatchTrailingSlashTrue() { Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(true); - Predicate predicate = new PathRoutePredicateFactory().apply(config); + Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config); assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("true"); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java index 41055e5c0a..5fb9a092b2 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java @@ -30,6 +30,7 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; @@ -54,7 +55,7 @@ public class PathRoutePredicatePathContainerAttrBenchMarkTests { PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() .setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i)) .setMatchTrailingSlash(true); - Predicate predicate = new PathRoutePredicateFactory().apply(config); + Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config); predicates.add(predicate); } } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java index bb058821a3..ccdc9ba6fd 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java @@ -22,6 +22,7 @@ import io.micrometer.core.instrument.Tags; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; @@ -56,7 +57,7 @@ void addPathToRoutes() { Route route = Route.async() .id("git") .uri(ROUTE_URI) - .predicate(new PathRoutePredicateFactory().apply(pathConfig) + .predicate(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig) .and(new HostRoutePredicateFactory().apply(hostConfig))) .build(); @@ -81,8 +82,8 @@ void addsMultiplePathToRoutes() { Route route = Route.async() .id("git") .uri(ROUTE_URI) - .predicate(new PathRoutePredicateFactory().apply(pathConfig) - .or(new PathRoutePredicateFactory().apply(pathConfig2))) + .predicate(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig) + .or(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig2))) .build(); ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build());