diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 69d79215..ceb3f6ed 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -270,7 +270,7 @@ This route matches if the request method was a `GET` or a `POST`. === The Path Route Predicate Factory -The `Path` Route Predicate Factory takes two parameters: a list of Spring `PathMatcher` `patterns` and an optional flag called `matchOptionalTrailingSeparator`. +The `Path` Route Predicate Factory takes two parameters: a list of Spring `PathMatcher` `patterns` and an optional flag called `matchTrailingSlash` (defaults to `true`). The following example configures a path route predicate: .application.yml @@ -288,7 +288,9 @@ spring: ---- ==== -This route matches if the request path was, for example: `/red/1` or `/red/blue` or `/blue/green`. +This route matches if the request path was, for example: `/red/1` or `/red/1/` or `/red/blue` or `/blue/green`. + +If `matchTrailingSlash` is set to `false`, then request path `/red/1/` will not be matched. 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-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 2d23d2c5..bcbbfb30 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 @@ -38,13 +38,14 @@ import static org.springframework.http.server.PathContainer.parsePath; /** * @author Spencer Gibb + * @author Dhawal Kapil */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { - private static final Log log = LogFactory.getLog(RoutePredicateFactory.class); + private static final Log log = LogFactory.getLog(PathRoutePredicateFactory.class); - private static final String MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY = "matchOptionalTrailingSeparator"; + private static final String MATCH_TRAILING_SLASH = "matchTrailingSlash"; private PathPatternParser pathPatternParser = new PathPatternParser(); @@ -67,7 +68,7 @@ public class PathRoutePredicateFactory @Override public List shortcutFieldOrder() { - return Arrays.asList("patterns", MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY); + return Arrays.asList("patterns", MATCH_TRAILING_SLASH); } @Override @@ -79,8 +80,8 @@ public class PathRoutePredicateFactory public Predicate apply(Config config) { final ArrayList pathPatterns = new ArrayList<>(); synchronized (this.pathPatternParser) { - pathPatternParser.setMatchOptionalTrailingSeparator( - config.isMatchOptionalTrailingSeparator()); + pathPatternParser + .setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash()); config.getPatterns().forEach(pattern -> { PathPattern pathPattern = this.pathPatternParser.parse(pattern); pathPatterns.add(pathPattern); @@ -111,7 +112,7 @@ public class PathRoutePredicateFactory @Override public String toString() { return String.format("Paths: %s, match trailing slash: %b", - config.getPatterns(), config.isMatchOptionalTrailingSeparator()); + config.getPatterns(), config.isMatchTrailingSlash()); } }; } @@ -121,7 +122,7 @@ public class PathRoutePredicateFactory private List patterns = new ArrayList<>(); - private boolean matchOptionalTrailingSeparator = true; + private boolean matchTrailingSlash = true; public List getPatterns() { return patterns; @@ -132,22 +133,37 @@ public class PathRoutePredicateFactory return this; } + /** + * @deprecated use {@link #isMatchTrailingSlash()} + */ + @Deprecated public boolean isMatchOptionalTrailingSeparator() { - return matchOptionalTrailingSeparator; + return isMatchTrailingSlash(); } + /** + * @deprecated use {@link #setMatchTrailingSlash(boolean)} + */ + @Deprecated public Config setMatchOptionalTrailingSeparator( boolean matchOptionalTrailingSeparator) { - this.matchOptionalTrailingSeparator = matchOptionalTrailingSeparator; + setMatchTrailingSlash(matchOptionalTrailingSeparator); + return this; + } + + public boolean isMatchTrailingSlash() { + return matchTrailingSlash; + } + + public Config setMatchTrailingSlash(boolean matchTrailingSlash) { + this.matchTrailingSlash = matchTrailingSlash; return this; } @Override public String toString() { return new ToStringCreator(this).append("patterns", patterns) - .append("matchOptionalTrailingSeparator", - matchOptionalTrailingSeparator) - .toString(); + .append(MATCH_TRAILING_SLASH, matchTrailingSlash).toString(); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java index 67780bce..3c715f8c 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java @@ -185,14 +185,14 @@ public class PredicateSpec extends UriSpec { * A predicate that checks if the path of the request matches the given pattern * @param patterns 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 + * @param matchTrailingSlash 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(boolean matchOptionalTrailingSeparator, String... patterns) { + public BooleanSpec path(boolean matchTrailingSlash, String... patterns) { return asyncPredicate(getBean(PathRoutePredicateFactory.class).applyAsync(c -> c .setPatterns(Arrays.asList(patterns)) - .setMatchOptionalTrailingSeparator(matchOptionalTrailingSeparator))); + .setMatchTrailingSlash(matchTrailingSlash))); } /** 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 5599b3e7..21065852 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 @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @@ -98,6 +99,13 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { .expectHeader().valueEquals(ROUTE_ID_HEADER, "path_test"); } + @Test + public void matchOptionalTrailingSeparatorCopiedToMatchTrailingSlash() { + Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")) + .setMatchOptionalTrailingSeparator(false); + assertThat(config.isMatchTrailingSlash()).isEqualTo(false); + } + @Test public void toStringFormat() { Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")) @@ -107,6 +115,16 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { .contains("false"); } + @Test + public void toStringFormatMatchTrailingSlashTrue() { + Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")) + .setMatchTrailingSlash(true); + Predicate predicate = new PathRoutePredicateFactory().apply(config); + assertThat(predicate.toString()).contains("patternA").contains("patternB") + .contains("true"); + } + + @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class)