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 2b89cc0e49..9b67b582bc 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 @@ -54,6 +54,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; @@ -189,13 +190,14 @@ * @author Alberto C. RĂ­os * @author Olga Maciaszek-Sharma * @author Dominic Niemann + * @author Guo FuYiNan */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) @EnableConfigurationProperties -@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class }) -@AutoConfigureAfter({ GatewayReactiveLoadBalancerClientAutoConfiguration.class, - GatewayClassPathWarningAutoConfiguration.class }) +@AutoConfigureBefore({HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class}) +@AutoConfigureAfter({GatewayReactiveLoadBalancerClientAutoConfiguration.class, + GatewayClassPathWarningAutoConfiguration.class}) @ConditionalOnClass(DispatcherHandler.class) public class GatewayAutoConfiguration { @@ -468,8 +470,8 @@ public MethodRoutePredicateFactory methodRoutePredicateFactory() { @Bean @ConditionalOnEnabledPredicate - public PathRoutePredicateFactory pathRoutePredicateFactory() { - return new PathRoutePredicateFactory(); + public PathRoutePredicateFactory pathRoutePredicateFactory(WebFluxProperties webFluxProperties) { + return new PathRoutePredicateFactory(webFluxProperties); } @Bean @@ -623,7 +625,7 @@ public PrincipalNameKeyResolver principalNameKeyResolver() { } @Bean - @ConditionalOnBean({ RateLimiter.class, KeyResolver.class }) + @ConditionalOnBean({RateLimiter.class, KeyResolver.class}) @ConditionalOnEnabledFilter public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, KeyResolver resolver) { @@ -763,7 +765,7 @@ public HttpClientSslConfigurer httpClientSslConfigurer(ServerProperties serverPr } @Bean - @ConditionalOnMissingBean({ HttpClient.class, HttpClientFactory.class }) + @ConditionalOnMissingBean({HttpClient.class, HttpClientFactory.class}) public HttpClientFactory gatewayHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties, List customizers, HttpClientSslConfigurer sslConfigurer) { @@ -866,7 +868,7 @@ static class VerboseDisabled { @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) - @ConditionalOnClass({ OAuth2AuthorizedClient.class, SecurityWebFilterChain.class, SecurityProperties.class }) + @ConditionalOnClass({OAuth2AuthorizedClient.class, SecurityWebFilterChain.class, SecurityProperties.class}) @ConditionalOnEnabledFilter(TokenRelayGatewayFilterFactory.class) protected static class TokenRelayConfiguration { 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..6c6cecba0f 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 Guo FuYiNan */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { @@ -50,8 +53,11 @@ 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); + if (basePathIsNotBlank) { + if (pattern.length() > 1 && !pattern.startsWith("/")) { + basePath += ("/"); + } + } + String pathPatternStr = basePathIsNotBlank ? basePath + pattern : 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..64dc75a88f 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 Guo FuYiNan */ 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());