From cd46e48e2b00868c5b20b4eb02c4bf3cd55553dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E4=BB=98=E4=B8=80=E7=94=B7?= Date: Thu, 16 Jan 2025 22:14:09 +0800 Subject: [PATCH] Support setting Spring.webflux.base-path and supporting it in path predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 郭付一男 --- .../config/GatewayAutoConfiguration.java | 6 +- .../predicate/PathRoutePredicateFactory.java | 26 ++++++- .../GatewayPredicateVisitorTests.java | 75 ++++++++++++++++++- .../PathRoutePredicateFactoryTests.java | 5 +- ...dicatePathContainerAttrBenchMarkTests.java | 3 +- .../GatewayPathTagsProviderTests.java | 7 +- 6 files changed, 111 insertions(+), 11 deletions(-) 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 be86096b..e9d920f3 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.ServerProperties; 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 @@ import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyReques * @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 class GatewayAutoConfiguration { @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 509b78d7..45550df5 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 java.util.function.Predicate; 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 @@ import static org.springframework.http.server.PathContainer.parsePath; /** * @author Spencer Gibb * @author Dhawal Kapil + * @author FuYiNan Guo */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { @@ -50,8 +53,20 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { - 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 bb5c0d12..c55087fc 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 class GatewayPredicateVisitorTests { @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 class GatewayPredicateVisitorTests { .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 a27c7559..539d8564 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.annotation.Value; 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 class PathRoutePredicateFactoryTests extends BaseWebClientTests { @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 41055e5c..5fb9a092 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.Mode; 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 bb058821..ccdc9ba6 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 java.util.List; 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 @@ public class GatewayPathTagsProviderTests { 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 @@ public class GatewayPathTagsProviderTests { 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());