diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java index 507ec2d97..5003ed374 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.handler.predicate; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Predicate; @@ -40,6 +41,7 @@ import static org.springframework.http.server.PathContainer.parsePath; */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { private static final Log log = LogFactory.getLog(RoutePredicateFactory.class); + private static final String MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY = "matchOptionalTrailingSeparator"; private PathPatternParser pathPatternParser = new PathPatternParser(); @@ -53,12 +55,13 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory shortcutFieldOrder() { - return Collections.singletonList(PATTERN_KEY); + return Arrays.asList(PATTERN_KEY, MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY); } @Override public Predicate apply(Config config) { synchronized (this.pathPatternParser) { + pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchOptionalTrailingSeparator()); config.pathPattern = this.pathPatternParser.parse(config.pattern); } return exchange -> { @@ -88,6 +91,7 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory c.setPattern(pattern))); } + /** + * A predicate that checks if the path of the request matches the given pattern + * @param pattern the pattern to check the path against. + * The pattern is a {@link org.springframework.util.PathMatcher} pattern + * @param matchOptionalTrailingSeparator set to false if you do not want this path to match + * when there is a trailing / + * @return a {@link BooleanSpec} to be used to add logical operators + */ + public BooleanSpec path(String pattern, boolean matchOptionalTrailingSeparator) { + return asyncPredicate(getBean(PathRoutePredicateFactory.class) + .applyAsync(c -> c.setPattern(pattern).setMatchOptionalTrailingSeparator(matchOptionalTrailingSeparator))); + } + /** * This predicate is BETA and may be subject to change in a future release. * A predicate that checks the contents of the request body diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index 5603c75c2..70e508435 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -44,6 +44,12 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { .expectStatus().isOk() .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) .expectHeader().valueEquals(ROUTE_ID_HEADER, "path_test"); + + //since the configuration does not allow the trailing / to match this should fail + testClient.get().uri("/abc/123/function/") + .header(HttpHeaders.HOST, "www.path.org") + .exchange() + .expectStatus().is4xxClientError(); } @Test diff --git a/spring-cloud-gateway-core/src/test/resources/application.yml b/spring-cloud-gateway-core/src/test/resources/application.yml index 6ae8ed009..41e8bb5d1 100644 --- a/spring-cloud-gateway-core/src/test/resources/application.yml +++ b/spring-cloud-gateway-core/src/test/resources/application.yml @@ -128,7 +128,7 @@ spring: - id: path_test uri: ${test.uri} predicates: - - Path=/{org}/{scope}/function + - Path=/{org}/{scope}/function,false - Host=**.path.org filters: - SetPath=/anything/{org}{scope}