From 5dd0c8183924038bf039b6f261ff533624c6a6ee Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 3 Dec 2018 18:38:02 -0500 Subject: [PATCH] Allows multiple patterns in host route predicate. Each pattern is checked if it matches in order. fixes gh-589 --- .../main/asciidoc/spring-cloud-gateway.adoc | 6 +-- .../predicate/HostRoutePredicateFactory.java | 34 ++++++++++--- .../gateway/route/builder/PredicateSpec.java | 5 +- .../HostRoutePredicateFactoryTests.java | 49 +++++++++++++++++-- .../src/test/resources/application.yml | 20 ++++++++ 5 files changed, 100 insertions(+), 14 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 24f914cd..e93b1c7b 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -134,7 +134,7 @@ spring: This route matches if the request has a header named `X-Request-Id` whos value matches the `\d+` regular expression (has a value of one or more digits). === Host Route Predicate Factory -The Host Route Predicate Factory takes one parameter: the host name pattern. The pattern is an Ant style pattern with `.` as the separator. This predicates matches the `Host` header that matches the pattern. +The Host Route Predicate Factory takes one parameter: a list of host name patterns. The pattern is an Ant style pattern with `.` as the separator. This predicates matches the `Host` header that matches the pattern. .application.yml [source,yaml] @@ -146,10 +146,10 @@ spring: - id: host_route uri: http://example.org predicates: - - Host=**.somehost.org + - Host=**.somehost.org,**.anotherhost.org ---- -This route would match if the request has a `Host` header has the value `www.somehost.org` or `beta.somehost.org`. +This route would match if the request has a `Host` header has the value `www.somehost.org` or `beta.somehost.org` or `www.anotherhost.org`. === Method Route Predicate Factory diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java index df9a5971..8481c603 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java @@ -17,12 +17,14 @@ package org.springframework.cloud.gateway.handler.predicate; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Predicate; import org.springframework.core.style.ToStringCreator; import org.springframework.util.AntPathMatcher; +import org.springframework.util.CollectionUtils; import org.springframework.util.PathMatcher; import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; @@ -44,34 +46,54 @@ public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory shortcutFieldOrder() { - return Collections.singletonList(PATTERN_KEY); + return Collections.singletonList("patterns"); + } + + @Override + public ShortcutType shortcutType() { + return ShortcutType.GATHER_LIST; } @Override public Predicate apply(Config config) { return exchange -> { String host = exchange.getRequest().getHeaders().getFirst("Host"); - return this.pathMatcher.match(config.getPattern(), host); + return config.getPatterns().stream() + .anyMatch(pattern -> this.pathMatcher.match(pattern, host)); }; } @Validated public static class Config { - private String pattern; + private List patterns = new ArrayList<>(); + @Deprecated public String getPattern() { - return pattern; + if (!CollectionUtils.isEmpty(this.patterns)) { + return patterns.get(0); + } + return null; } + @Deprecated public Config setPattern(String pattern) { - this.pattern = pattern; + this.patterns = new ArrayList<>(); + this.patterns.add(pattern); return this; } + public List getPatterns() { + return patterns; + } + + public void setPatterns(List patterns) { + this.patterns = patterns; + } + @Override public String toString() { return new ToStringCreator(this) - .append("pattern", pattern) + .append("patterns", patterns) .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 18d110eb..953546af 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 @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.route.builder; import java.time.ZonedDateTime; +import java.util.Arrays; import java.util.function.Predicate; import org.springframework.cloud.gateway.handler.AsyncPredicate; @@ -134,9 +135,9 @@ public class PredicateSpec extends UriSpec { * @param pattern the pattern to check against. The pattern is an Ant style pattern with {@code .} as a separator * @return a {@link BooleanSpec} to be used to add logical operators */ - public BooleanSpec host(String pattern) { + public BooleanSpec host(String... pattern) { return asyncPredicate(getBean(HostRoutePredicateFactory.class) - .applyAsync(c-> c.setPattern(pattern))); + .applyAsync(c-> c.setPatterns(Arrays.asList(pattern)))); } /** diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java index 7c88da69..90699a4a 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java @@ -19,11 +19,15 @@ package org.springframework.cloud.gateway.handler.predicate; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; @@ -37,18 +41,57 @@ public class HostRoutePredicateFactoryTests extends BaseWebClientTests { @Test public void hostRouteWorks() { + expectHostRoute("www.example.org", "host_example_to_httpbin"); + } + + public void expectHostRoute(String host, String routeId) { testClient.get() .uri("/get") - .header("Host", "www.example.org") + .header("Host", host) .exchange() .expectStatus().isOk() .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) - .expectHeader().valueEquals(ROUTE_ID_HEADER, "host_example_to_httpbin"); + .expectHeader().valueEquals(ROUTE_ID_HEADER, routeId); + } + + @Test + public void hostRouteBackwardsCompatiblePatternWorks() { + expectHostRoute("www.hostpatternarg.org", "host_backwards_compatible_test"); + } + + @Test + public void hostRouteBackwardsCompatibleShortcutWorks() { + expectHostRoute("www.hostpatternshortcut.org", "host_backwards_compatible_shortcut_test"); + } + + @Test + public void mulitHostRouteWorks() { + expectHostRoute("www.hostmulti1.org", "host_multi_test"); + expectHostRoute("www.hostmulti2.org", "host_multi_test"); + } + + @Test + public void mulitHostRouteDslWorks() { + expectHostRoute("www.hostmultidsl1.org", "host_multi_dsl"); + expectHostRoute("www.hostmultidsl2.org", "host_multi_dsl"); } @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) - public static class TestConfig { } + public static class TestConfig { + + @Value("${test.uri}") + String uri; + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + .route("host_multi_dsl", r -> r.host("**.hostmultidsl1.org", "**.hostmultidsl2.org") + .filters(f -> f.prefixPath("/httpbin")) + .uri(uri)) + .build(); + } + } } diff --git a/spring-cloud-gateway-core/src/test/resources/application.yml b/spring-cloud-gateway-core/src/test/resources/application.yml index 157db07d..b4e95928 100644 --- a/spring-cloud-gateway-core/src/test/resources/application.yml +++ b/spring-cloud-gateway-core/src/test/resources/application.yml @@ -81,6 +81,26 @@ spring: predicates: - Host=**.forward.org + # ===================================== + - id: host_backwards_compatible_test + uri: ${test.uri} + predicates: + - name: Host + args: + pattern: '**.hostpatternarg.org' + + # ===================================== + - id: host_backwards_compatible_shortcut_test + uri: ${test.uri} + predicates: + - Host=**.hostpatternshortcut.org + + # ===================================== + - id: host_multi_test + uri: ${test.uri} + predicates: + - Host=**.hostmulti1.org,**.hostmulti2.org + # ===================================== - id: hystrix_failure_test uri: ${test.uri}