From 7b9d06df442f9dca0e215786e3aa931a3b98e98c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 1 Dec 2016 13:43:33 -0700 Subject: [PATCH] Parse predicate if needed. Allow extra args in predicate. Create Header Predicate --- .../config/GatewayAutoConfiguration.java | 6 + .../gateway/config/GatewayProperties.java | 107 ------------------ .../gateway/config/PredicateDefinition.java | 90 +++++++++++++++ .../cloud/gateway/config/Route.java | 73 ++++++++++++ .../filter/RouteToRequestUrlFilter.java | 2 +- ...verWebExchangePredicateHandlerMapping.java | 11 +- .../predicate/GatewayPredicateFactory.java | 6 +- .../predicate/HeaderPredicateFactory.java | 36 ++++++ .../predicate/HostPredicateFactory.java | 2 +- .../predicate/MethodPredicateFactory.java | 2 +- .../predicate/UrlPredicateFactory.java | 2 +- .../gateway/test/GatewayIntegrationTests.java | 3 +- src/test/resources/application.yml | 13 +-- 13 files changed, 225 insertions(+), 128 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/config/PredicateDefinition.java create mode 100644 src/main/java/org/springframework/cloud/gateway/config/Route.java create mode 100644 src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderPredicateFactory.java diff --git a/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 45c0abb6a..4f47024ee 100644 --- a/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -8,6 +8,7 @@ import org.springframework.cloud.gateway.actuate.GatewayEndpoint; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; import org.springframework.cloud.gateway.handler.GatewayFilteringWebHandler; +import org.springframework.cloud.gateway.handler.predicate.HeaderPredicateFactory; import org.springframework.cloud.gateway.handler.predicate.HostPredicateFactory; import org.springframework.cloud.gateway.handler.predicate.GatewayPredicateFactory; import org.springframework.cloud.gateway.handler.GatewayWebHandler; @@ -76,6 +77,11 @@ public class GatewayAutoConfiguration { return new MethodPredicateFactory(); } + @Bean + public HeaderPredicateFactory headerPredicateFactory() { + return new HeaderPredicateFactory(); + } + @Configuration @ConditionalOnClass(Endpoint.class) protected static class GatewayActuatorConfiguration { diff --git a/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java b/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java index 07bfcbf2c..1321c4408 100644 --- a/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java +++ b/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java @@ -1,11 +1,8 @@ package org.springframework.cloud.gateway.config; -import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import org.hibernate.validator.constraints.NotEmpty; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.validation.Valid; @@ -32,108 +29,4 @@ public class GatewayProperties { this.routes = routes; } - public static class Route { - @NotEmpty - private String id; - - @NotEmpty - @Valid - private List predicates = new ArrayList<>(); - - @NotNull - private URI downstreamUrl; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public List getPredicates() { - return predicates; - } - - public void setPredicates(List predicates) { - this.predicates = predicates; - } - - public URI getDownstreamUrl() { - return downstreamUrl; - } - - public void setDownstreamUrl(URI downstreamUrl) { - this.downstreamUrl = downstreamUrl; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Route route = (Route) o; - return Objects.equals(id, route.id) && - Objects.equals(predicates, route.predicates) && - Objects.equals(downstreamUrl, route.downstreamUrl); - } - - @Override - public int hashCode() { - return Objects.hash(id, predicates, downstreamUrl); - } - - @Override - public String toString() { - return "Route{" + - "id='" + id + '\'' + - ", predicates=" + predicates + - ", downstreamUrl=" + downstreamUrl + - '}'; - } - } - - public static class Predicate { - @NotNull - private String name; - @NotNull - private String value; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Predicate predicate = (Predicate) o; - return Objects.equals(name, predicate.name) && - Objects.equals(value, predicate.value); - } - - @Override - public int hashCode() { - return Objects.hash(name, value); - } - - @Override - public String toString() { - return "Predicate{" + - "name='" + name + '\'' + - ", value='" + value + '\'' + - '}'; - } - } } diff --git a/src/main/java/org/springframework/cloud/gateway/config/PredicateDefinition.java b/src/main/java/org/springframework/cloud/gateway/config/PredicateDefinition.java new file mode 100644 index 000000000..776f24105 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/config/PredicateDefinition.java @@ -0,0 +1,90 @@ +package org.springframework.cloud.gateway.config; + +import java.util.Arrays; +import java.util.Objects; + +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; + +import static org.springframework.util.StringUtils.tokenizeToStringArray; + +/** + * @author Spencer Gibb + */ +public class PredicateDefinition { + @NotNull + private String name; + @NotNull + private String value; + + private String[] args; + + public PredicateDefinition() { + } + + public PredicateDefinition(String text) { + String[] parts = text.split("="); + if (parts.length != 2) { + throw new ValidationException("Unable to parse Predicate text '" + text + "'" + + ", must be of the form name=value"); + } + setName(parts[0]); + + String[] args = tokenizeToStringArray(parts[1], ","); + + setValue(args[0]); + + if (args.length > 1) { + setArgs(Arrays.copyOfRange(args, 1, args.length)); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String[] getArgs() { + return args; + } + + public void setArgs(String[] args) { + this.args = args; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PredicateDefinition that = (PredicateDefinition) o; + return Objects.equals(name, that.name) && + Objects.equals(value, that.value) && + Arrays.equals(args, that.args); + } + + @Override + public int hashCode() { + return Objects.hash(name, value, args); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("PredicateDefinition{"); + sb.append("name='").append(name).append('\''); + sb.append(", value='").append(value).append('\''); + sb.append(", args=").append(Arrays.toString(args)); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/config/Route.java b/src/main/java/org/springframework/cloud/gateway/config/Route.java new file mode 100644 index 000000000..7485bee5c --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/config/Route.java @@ -0,0 +1,73 @@ +package org.springframework.cloud.gateway.config; + +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author Spencer Gibb + */ +public class Route { + @NotEmpty + private String id; + + @NotEmpty + @Valid + private List predicates = new ArrayList<>(); + + @NotNull + private URI downstreamUrl; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public List getPredicates() { + return predicates; + } + + public void setPredicates(List predicates) { + this.predicates = predicates; + } + + public URI getDownstreamUrl() { + return downstreamUrl; + } + + public void setDownstreamUrl(URI downstreamUrl) { + this.downstreamUrl = downstreamUrl; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Route route = (Route) o; + return Objects.equals(id, route.id) && + Objects.equals(predicates, route.predicates) && + Objects.equals(downstreamUrl, route.downstreamUrl); + } + + @Override + public int hashCode() { + return Objects.hash(id, predicates, downstreamUrl); + } + + @Override + public String toString() { + return "Route{" + + "id='" + id + '\'' + + ", predicates=" + predicates + + ", downstreamUrl=" + downstreamUrl + + '}'; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java index 84226e823..223b326b8 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java @@ -5,7 +5,7 @@ import java.net.URI; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.config.GatewayProperties.Route; +import org.springframework.cloud.gateway.config.Route; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilterChain; diff --git a/src/main/java/org/springframework/cloud/gateway/handler/ServerWebExchangePredicateHandlerMapping.java b/src/main/java/org/springframework/cloud/gateway/handler/ServerWebExchangePredicateHandlerMapping.java index d333d5322..ac53e4755 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/ServerWebExchangePredicateHandlerMapping.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/ServerWebExchangePredicateHandlerMapping.java @@ -7,7 +7,8 @@ import java.util.function.Predicate; import org.springframework.beans.BeansException; import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.config.GatewayProperties.Route; +import org.springframework.cloud.gateway.config.Route; +import org.springframework.cloud.gateway.config.PredicateDefinition; import org.springframework.cloud.gateway.handler.predicate.GatewayPredicateFactory; import org.springframework.web.reactive.handler.AbstractHandlerMapping; import org.springframework.web.server.ServerWebExchange; @@ -113,10 +114,10 @@ public class ServerWebExchangePredicateHandlerMapping extends AbstractHandlerMap private Predicate combinePredicates(Route route) { - List predicates = route.getPredicates(); + List predicates = route.getPredicates(); Predicate predicate = lookup(predicates.get(0)); - for (GatewayProperties.Predicate andPredicate : predicates.subList(1, predicates.size())) { + for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { Predicate found = lookup(andPredicate); predicate = predicate.and(found); } @@ -124,12 +125,12 @@ public class ServerWebExchangePredicateHandlerMapping extends AbstractHandlerMap return predicate; } - private Predicate lookup(GatewayProperties.Predicate predicate) { + private Predicate lookup(PredicateDefinition predicate) { GatewayPredicateFactory found = this.predicateFactories.get(predicate.getName()); if (found == null) { throw new IllegalArgumentException("Unable to find GatewayPredicateFactory with name " + predicate.getName()); } - return found.create(predicate.getValue()); + return found.create(predicate.getValue(), predicate.getArgs()); } /** diff --git a/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateFactory.java b/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateFactory.java index 3008fa148..9d105f998 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateFactory.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateFactory.java @@ -1,9 +1,9 @@ package org.springframework.cloud.gateway.handler.predicate; -import org.springframework.web.server.ServerWebExchange; - import java.util.function.Predicate; +import org.springframework.web.server.ServerWebExchange; + /** * @author Spencer Gibb */ @@ -11,5 +11,5 @@ public interface GatewayPredicateFactory { String getName(); - Predicate create(String value); + Predicate create(String value, String[] args); } diff --git a/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderPredicateFactory.java b/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderPredicateFactory.java new file mode 100644 index 000000000..d4abf939d --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderPredicateFactory.java @@ -0,0 +1,36 @@ +package org.springframework.cloud.gateway.handler.predicate; + +import java.util.List; +import java.util.function.Predicate; + +import org.springframework.util.Assert; +import org.springframework.web.server.ServerWebExchange; + +/** + * @author Spencer Gibb + */ +public class HeaderPredicateFactory implements GatewayPredicateFactory { + + @Override + public String getName() { + return "Header"; + } + + @Override + public Predicate create(String header, String[] args) { + //TODO: caching can happen here + return exchange -> { + Assert.isTrue(args != null && args.length == 1, + "args must have one entry"); + + String regexp = args[0]; + List values = exchange.getRequest().getHeaders().get(header); + for (String value : values) { + if (value.matches(regexp)) { + return true; + } + } + return false; + }; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostPredicateFactory.java b/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostPredicateFactory.java index d56b12526..3e583909e 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostPredicateFactory.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostPredicateFactory.java @@ -23,7 +23,7 @@ public class HostPredicateFactory implements GatewayPredicateFactory { } @Override - public Predicate create(String pattern) { + public Predicate create(String pattern, String[] args) { //TODO: caching can happen here return exchange -> { String host = exchange.getRequest().getHeaders().getFirst("Host"); diff --git a/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodPredicateFactory.java b/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodPredicateFactory.java index eb325c38f..a770b5fce 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodPredicateFactory.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodPredicateFactory.java @@ -16,7 +16,7 @@ public class MethodPredicateFactory implements GatewayPredicateFactory { } @Override - public Predicate create(String method) { + public Predicate create(String method, String[] args) { //TODO: caching can happen here return exchange -> { HttpMethod requestMethod = exchange.getRequest().getMethod(); diff --git a/src/main/java/org/springframework/cloud/gateway/handler/predicate/UrlPredicateFactory.java b/src/main/java/org/springframework/cloud/gateway/handler/predicate/UrlPredicateFactory.java index cb267b4f2..0809ff613 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/predicate/UrlPredicateFactory.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/predicate/UrlPredicateFactory.java @@ -37,7 +37,7 @@ public class UrlPredicateFactory implements GatewayPredicateFactory { } @Override - public Predicate create(String pattern) { + public Predicate create(String pattern, String[] args) { return exchange -> { String lookupPath = getPathHelper().getLookupPathForRequest(exchange); return getPathMatcher().match(pattern, lookupPath); diff --git a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java index e5532d696..396599969 100644 --- a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java @@ -8,7 +8,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.config.GatewayProperties.Route; +import org.springframework.cloud.gateway.config.Route; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.handler.ServerWebExchangePredicateHandlerMapping; import org.springframework.context.annotation.Bean; @@ -88,6 +88,7 @@ public class GatewayIntegrationTests { Mono result = webClient.exchange( GET("http://localhost:" + port + "/headers") .header("Host", "www.foo.org") + .header("X-Request-Id", "123") .build() ); diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 536e4841a..b01118f81 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -7,19 +7,16 @@ spring: - id: host_example_to_httpbin downstreamUrl: http://httpbin.org:80 predicates: - - name: Host - value: '**.example.org' + - Host=**.example.org # ===================================== - id: host_foo_path_headers_to_httpbin downstreamUrl: http://httpbin.org:80 predicates: - - name: Host - value: '**.foo.org' - - name: Url - value: /headers - - name: Method - value: GET + - Host=**.foo.org + - Url=/headers + - Method=GET + - Header=X-Request-Id, \d+ # ===================================== - id: default_path_to_httpbin