Support Predicate based matching.
This commit is contained in:
1
mvnw
vendored
1
mvnw
vendored
@@ -136,7 +136,6 @@ if $mingw ; then
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
|
||||
@@ -8,9 +8,11 @@ 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.GatewayHostHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.GatewayUrlHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.HostPredicateFactory;
|
||||
import org.springframework.cloud.gateway.handler.GatewayPredicateFactory;
|
||||
import org.springframework.cloud.gateway.handler.GatewayWebHandler;
|
||||
import org.springframework.cloud.gateway.handler.ServerWebExchangePredicateHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.UrlPredicateFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
@@ -52,13 +54,20 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GatewayUrlHandlerMapping gatewayUrlHandlerMapping(GatewayProperties properties, GatewayFilteringWebHandler webHandler) {
|
||||
return new GatewayUrlHandlerMapping(properties, webHandler);
|
||||
public ServerWebExchangePredicateHandlerMapping serverWebExchangePredicateHandlerMapping(GatewayProperties properties,
|
||||
GatewayFilteringWebHandler webHandler,
|
||||
List<GatewayPredicateFactory> predicateFactories) {
|
||||
return new ServerWebExchangePredicateHandlerMapping(webHandler, predicateFactories, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GatewayHostHandlerMapping gatewayHostHandlerMapping(GatewayProperties properties, GatewayFilteringWebHandler webHandler) {
|
||||
return new GatewayHostHandlerMapping(properties, webHandler);
|
||||
public HostPredicateFactory hostPredicateFactory() {
|
||||
return new HostPredicateFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UrlPredicateFactory urlPredicateFactory() {
|
||||
return new UrlPredicateFactory();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
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 java.net.URI;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -12,40 +17,46 @@ import java.util.Map;
|
||||
@ConfigurationProperties("spring.cloud.gateway")
|
||||
public class GatewayProperties {
|
||||
|
||||
|
||||
/**
|
||||
* Map of route names to properties.
|
||||
*/
|
||||
private Map<String, Route> routes = new LinkedHashMap<>();
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<Route> routes = new ArrayList<>();
|
||||
|
||||
public Map<String, Route> getRoutes() {
|
||||
public List<Route> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
|
||||
public void setRoutes(Map<String, Route> routes) {
|
||||
public void setRoutes(List<Route> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
public static class Route {
|
||||
@NotEmpty
|
||||
private String id;
|
||||
private String requestPath;
|
||||
private String requestHost;
|
||||
|
||||
@NotEmpty
|
||||
@Valid
|
||||
private List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
@NotNull
|
||||
private URI downstreamUrl;
|
||||
|
||||
public String getRequestPath() {
|
||||
return this.requestPath;
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setRequestPath(String requestPath) {
|
||||
this.requestPath = requestPath;
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getRequestHost() {
|
||||
return requestHost;
|
||||
public List<Predicate> getPredicates() {
|
||||
return predicates;
|
||||
}
|
||||
|
||||
public void setRequestHost(String requestHost) {
|
||||
this.requestHost = requestHost;
|
||||
public void setPredicates(List<Predicate> predicates) {
|
||||
this.predicates = predicates;
|
||||
}
|
||||
|
||||
public URI getDownstreamUrl() {
|
||||
@@ -56,13 +67,72 @@ public class GatewayProperties {
|
||||
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 + '\'' +
|
||||
", requestPath='" + requestPath + '\'' +
|
||||
", requestHost='" + requestHost + '\'' +
|
||||
", downstreamUrl='" + downstreamUrl + '\'' +
|
||||
", 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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties.Route;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_ROUTE_ATTR;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class GatewayHostHandlerMapping extends AbstractHandlerMapping {
|
||||
|
||||
private GatewayProperties properties;
|
||||
private WebHandler webHandler;
|
||||
|
||||
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
|
||||
|
||||
public GatewayHostHandlerMapping(GatewayProperties properties, WebHandler webHandler) {
|
||||
this.properties = properties;
|
||||
this.webHandler = webHandler;
|
||||
setOrder(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initApplicationContext() throws BeansException {
|
||||
super.initApplicationContext();
|
||||
registerHandlers(this.properties.getRoutes());
|
||||
setPathMatcher(new AntPathMatcher("."));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
|
||||
exchange.getAttributes().put(GatewayFilter.GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());
|
||||
String host = exchange.getRequest().getHeaders().getFirst("Host");
|
||||
Object handler;
|
||||
try {
|
||||
handler = lookupHandler(host, exchange);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
if (handler != null && logger.isDebugEnabled()) {
|
||||
logger.debug("Mapping [" + host + "] to " + handler);
|
||||
}
|
||||
else if (handler == null && logger.isTraceEnabled()) {
|
||||
logger.trace("No handler mapping found for [" + host + "]");
|
||||
}
|
||||
|
||||
return Mono.justOrEmpty(handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Look up a handler instance for the given URL path.
|
||||
*
|
||||
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
|
||||
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
|
||||
* both "/test" and "/team". For details, see the AntPathMatcher class.
|
||||
*
|
||||
* <p>Looks for the most exact pattern, where most exact is defined as
|
||||
* the longest path pattern.
|
||||
*
|
||||
* @param host URL the bean is mapped to
|
||||
* @param exchange the current exchange
|
||||
* @return the associated handler instance, or {@code null} if not found
|
||||
* @see org.springframework.util.AntPathMatcher
|
||||
*/
|
||||
protected Object lookupHandler(String host, ServerWebExchange exchange) throws Exception {
|
||||
// Direct match?
|
||||
Object handler = this.handlerMap.get(host);
|
||||
if (handler != null) {
|
||||
return handleMatch(handler, host, exchange);
|
||||
}
|
||||
// Pattern match?
|
||||
List<String> matches = new ArrayList<>();
|
||||
for (String pattern : this.handlerMap.keySet()) {
|
||||
if (getPathMatcher().match(pattern, host)) {
|
||||
matches.add(pattern);
|
||||
}
|
||||
}
|
||||
String bestMatch = null;
|
||||
Comparator<String> comparator = getPathMatcher().getPatternComparator(host);
|
||||
if (!matches.isEmpty()) {
|
||||
Collections.sort(matches, comparator);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Matching patterns for request [" + host + "] are " + matches);
|
||||
}
|
||||
bestMatch = matches.get(0);
|
||||
}
|
||||
if (bestMatch != null) {
|
||||
handler = this.handlerMap.get(bestMatch);
|
||||
if (handler == null) {
|
||||
Assert.isTrue(bestMatch.endsWith("/"));
|
||||
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
|
||||
}
|
||||
return handleMatch(handler, bestMatch, exchange);
|
||||
}
|
||||
// No handler found...
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Object handleMatch(Object handler, String bestMatch,
|
||||
ServerWebExchange exchange) throws Exception {
|
||||
|
||||
// Bean name or resolved handler?
|
||||
if (handler instanceof String) {
|
||||
String handlerName = (String) handler;
|
||||
handler = getApplicationContext().getBean(handlerName);
|
||||
}
|
||||
|
||||
if (handler instanceof RouteHolder) {
|
||||
RouteHolder holder = (RouteHolder) handler;
|
||||
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, holder.route);
|
||||
return holder.webHandler;
|
||||
}
|
||||
|
||||
validateHandler(handler, exchange);
|
||||
|
||||
exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatch);
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given handler against the current request.
|
||||
* <p>The default implementation is empty. Can be overridden in subclasses,
|
||||
* for example to enforce specific preconditions expressed in URL mappings.
|
||||
* @param handler the handler object to validate
|
||||
* @param exchange current exchange
|
||||
* @throws Exception if validation failed
|
||||
*/
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
protected void validateHandler(Object handler, ServerWebExchange exchange) throws Exception {
|
||||
}
|
||||
|
||||
|
||||
protected void registerHandlers(Map<String, Route> routes) {
|
||||
for (Route route : routes.values()) {
|
||||
if (StringUtils.hasText(route.getRequestHost())) {
|
||||
registerHandler(route.getRequestHost(), new RouteHolder(route, this.webHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified handler for the given host path.
|
||||
* @param hostPath the host the bean should be mapped to
|
||||
* @param handler the handler instance or handler bean name String
|
||||
* (a bean name will automatically be resolved into the corresponding handler bean)
|
||||
* @throws BeansException if the handler couldn't be registered
|
||||
* @throws IllegalStateException if there is a conflicting handler registered
|
||||
*/
|
||||
protected void registerHandler(String hostPath, Object handler) throws BeansException, IllegalStateException {
|
||||
Assert.notNull(hostPath, "host path must not be null");
|
||||
Assert.notNull(handler, "Handler object must not be null");
|
||||
Object resolvedHandler = handler;
|
||||
|
||||
// Eagerly resolve handler if referencing singleton via name.
|
||||
/*if (!this.lazyInitHandlers && handler instanceof String) {
|
||||
String handlerName = (String) handler;
|
||||
if (getApplicationContext().isSingleton(handlerName)) {
|
||||
resolvedHandler = getApplicationContext().getBean(handlerName);
|
||||
}
|
||||
}*/
|
||||
|
||||
Object mappedHandler = this.handlerMap.get(hostPath);
|
||||
if (mappedHandler != null) {
|
||||
if (mappedHandler != resolvedHandler) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot map " + getHandlerDescription(handler) + " to host path [" + hostPath +
|
||||
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.handlerMap.put(hostPath, resolvedHandler);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Mapped host path [" + hostPath + "] onto " + getHandlerDescription(handler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getHandlerDescription(Object handler) {
|
||||
String desc;
|
||||
if (handler instanceof String) {
|
||||
desc = "'" + handler + "'";
|
||||
} else if (handler instanceof RouteHolder) {
|
||||
desc = "of type [" + ((RouteHolder) handler).webHandler.getClass() + "]";
|
||||
} else {
|
||||
desc = "of type [" + handler.getClass() + "]";
|
||||
}
|
||||
return "handler " + desc;
|
||||
}
|
||||
|
||||
private class RouteHolder {
|
||||
private final Route route;
|
||||
private final WebHandler webHandler;
|
||||
|
||||
public RouteHolder(Route route, WebHandler webHandler) {
|
||||
this.route = route;
|
||||
this.webHandler = webHandler;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface GatewayPredicateFactory {
|
||||
|
||||
String getName();
|
||||
|
||||
Predicate<ServerWebExchange> create(String value);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties.Route;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.handler.AbstractUrlHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class GatewayUrlHandlerMapping extends AbstractUrlHandlerMapping {
|
||||
|
||||
private GatewayProperties properties;
|
||||
private WebHandler webHandler;
|
||||
|
||||
public GatewayUrlHandlerMapping(GatewayProperties properties, WebHandler webHandler) {
|
||||
this.properties = properties;
|
||||
this.webHandler = webHandler;
|
||||
setOrder(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initApplicationContext() throws BeansException {
|
||||
super.initApplicationContext();
|
||||
registerHandlers(this.properties.getRoutes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> getHandler(ServerWebExchange exchange) {
|
||||
return super.getHandler(exchange).map(o -> {
|
||||
if (o instanceof RouteHolder) {
|
||||
RouteHolder holder = (RouteHolder) o;
|
||||
exchange.getAttributes().put(GatewayFilter.GATEWAY_HANDLER_MAPPER_ATTR,
|
||||
GatewayUrlHandlerMapping.this.getClass().getSimpleName());
|
||||
exchange.getAttributes().put(GatewayFilter.GATEWAY_ROUTE_ATTR, holder.route);
|
||||
return holder.webHandler;
|
||||
}
|
||||
return o;
|
||||
});
|
||||
}
|
||||
|
||||
protected void registerHandlers(Map<String, Route> routes) {
|
||||
for (Route route : routes.values()) {
|
||||
if (StringUtils.hasText(route.getRequestPath())) {
|
||||
registerHandler(route.getRequestPath(), new RouteHolder(route, this.webHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RouteHolder {
|
||||
private final Route route;
|
||||
private final WebHandler webHandler;
|
||||
|
||||
public RouteHolder(Route route, WebHandler webHandler) {
|
||||
this.route = route;
|
||||
this.webHandler = webHandler;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class HostPredicateFactory implements GatewayPredicateFactory {
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher(".");
|
||||
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Host";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> create(String pattern) {
|
||||
//TODO: caching can happen here
|
||||
return exchange -> {
|
||||
String host = exchange.getRequest().getHeaders().getFirst("Host");
|
||||
return this.pathMatcher.match(pattern, host);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.filter.GatewayFilter;
|
||||
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_HANDLER_MAPPER_ATTR;
|
||||
import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_ROUTE_ATTR;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class ServerWebExchangePredicateHandlerMapping extends AbstractHandlerMapping {
|
||||
|
||||
private Map<String, GatewayPredicateFactory> predicateFactories = new LinkedHashMap<>();
|
||||
private GatewayProperties properties;
|
||||
private WebHandler webHandler;
|
||||
|
||||
private List<Route> routes;
|
||||
|
||||
public ServerWebExchangePredicateHandlerMapping(WebHandler webHandler, List<GatewayPredicateFactory> predicateFactories, GatewayProperties properties) {
|
||||
this.webHandler = webHandler;
|
||||
this.properties = properties;
|
||||
|
||||
for (GatewayPredicateFactory factory : predicateFactories) {
|
||||
if (this.predicateFactories.containsKey(factory.getName())) {
|
||||
this.logger.warn("A GatewayPredicateFactory named "+ factory.getName()
|
||||
+ " already exists, class: " + this.predicateFactories.get(factory.getName())
|
||||
+ ". It will be overwritten.");
|
||||
}
|
||||
this.predicateFactories.put(factory.getName(), factory);
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Loaded GatewayPredicateFactory [" + factory.getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
setOrder(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initApplicationContext() throws BeansException {
|
||||
super.initApplicationContext();
|
||||
//TODO: move properties.getRoutes() to interface/impl
|
||||
registerHandlers(this.properties.getRoutes());
|
||||
}
|
||||
|
||||
protected void registerHandlers(List<Route> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
|
||||
exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());
|
||||
|
||||
Route route;
|
||||
try {
|
||||
route = lookupRoute(this.routes, exchange);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
if (route != null) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + route);
|
||||
}
|
||||
|
||||
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route);
|
||||
return Mono.just(this.webHandler);
|
||||
}
|
||||
else if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("No Route found for [" + getExchangeDesc(exchange) + "]");
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
//TODO: get desc from factory?
|
||||
private String getExchangeDesc(ServerWebExchange exchange) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
out.append("Exchange: ");
|
||||
out.append(exchange.getRequest().getMethod());
|
||||
out.append(" ");
|
||||
out.append(exchange.getRequest().getURI());
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
|
||||
protected Route lookupRoute(List<Route> routes, ServerWebExchange exchange) throws Exception {
|
||||
for (Route route : routes) {
|
||||
if (!route.getPredicates().isEmpty()) {
|
||||
//TODO: cache predicate
|
||||
Predicate<ServerWebExchange> predicate = combinePredicates(route);
|
||||
if (predicate.test(exchange)) {
|
||||
validateRoute(route, exchange);
|
||||
return route;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Predicate<ServerWebExchange> combinePredicates(Route route) {
|
||||
List<GatewayProperties.Predicate> predicates = route.getPredicates();
|
||||
Predicate<ServerWebExchange> predicate = lookup(predicates.get(0));
|
||||
|
||||
for (GatewayProperties.Predicate andPredicate : predicates.subList(1, predicates.size())) {
|
||||
Predicate<ServerWebExchange> found = lookup(andPredicate);
|
||||
predicate = predicate.and(found);
|
||||
}
|
||||
|
||||
return predicate;
|
||||
}
|
||||
|
||||
private Predicate<ServerWebExchange> lookup(GatewayProperties.Predicate 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given handler against the current request.
|
||||
* <p>The default implementation is empty. Can be overridden in subclasses,
|
||||
* for example to enforce specific preconditions expressed in URL mappings.
|
||||
* @param route the Route object to validate
|
||||
* @param exchange current exchange
|
||||
* @throws Exception if validation failed
|
||||
*/
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
protected void validateRoute(Route route, ServerWebExchange exchange) throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.support.HttpRequestPathHelper;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class UrlPredicateFactory implements GatewayPredicateFactory {
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
private HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
|
||||
|
||||
public PathMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
public HttpRequestPathHelper getPathHelper() {
|
||||
return pathHelper;
|
||||
}
|
||||
|
||||
public void setPathHelper(HttpRequestPathHelper pathHelper) {
|
||||
this.pathHelper = pathHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Url";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> create(String pattern) {
|
||||
return exchange -> {
|
||||
String lookupPath = getPathHelper().getLookupPathForRequest(exchange);
|
||||
return getPathMatcher().match(pattern, lookupPath);
|
||||
//TODO: support trailingSlashMatch
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,15 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties.Route;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.handler.GatewayHostHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.GatewayUrlHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.HostPredicateFactory;
|
||||
import org.springframework.cloud.gateway.handler.ServerWebExchangePredicateHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.UrlPredicateFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -32,6 +36,7 @@ import reactor.test.StepVerifier;
|
||||
public class GatewayIntegrationTests {
|
||||
|
||||
public static final String HANDLER_MAPPER_HEADER = "X-Gateway-Handler-Mapper-Class";
|
||||
public static final String ROUTE_ID_HEADER = "X-Gateway-Route-Id";
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
@@ -49,7 +54,9 @@ public class GatewayIntegrationTests {
|
||||
.consumeNextWith(
|
||||
httpHeaders -> {
|
||||
assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER))
|
||||
.isEqualTo(GatewayUrlHandlerMapping.class.getSimpleName());
|
||||
.isEqualTo(ServerWebExchangePredicateHandlerMapping.class.getSimpleName());
|
||||
assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER))
|
||||
.isEqualTo("default_path_to_httpbin");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
@@ -70,13 +77,40 @@ public class GatewayIntegrationTests {
|
||||
HttpHeaders httpHeaders = response.headers().asHttpHeaders();
|
||||
HttpStatus statusCode = response.statusCode();
|
||||
assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER))
|
||||
.isEqualTo(GatewayHostHandlerMapping.class.getSimpleName());
|
||||
.isEqualTo(ServerWebExchangePredicateHandlerMapping.class.getSimpleName());
|
||||
assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER))
|
||||
.isEqualTo("host_example_to_httpbin");
|
||||
assertThat(statusCode).isEqualTo(HttpStatus.OK);
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compositeRouteWorks() {
|
||||
Mono<ClientResponse> result = webClient.exchange(
|
||||
GET("http://localhost:" + port + "/headers")
|
||||
.header("Host", "www.foo.org")
|
||||
.build()
|
||||
);
|
||||
|
||||
StepVerifier
|
||||
.create(result)
|
||||
.consumeNextWith(
|
||||
response -> {
|
||||
HttpHeaders httpHeaders = response.headers().asHttpHeaders();
|
||||
HttpStatus statusCode = response.statusCode();
|
||||
assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER))
|
||||
.isEqualTo(ServerWebExchangePredicateHandlerMapping.class.getSimpleName());
|
||||
assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER))
|
||||
.isEqualTo("host_foo_path_headers_to_httpbin");
|
||||
assertThat(statusCode).isEqualTo(HttpStatus.OK);
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
@@ -89,6 +123,10 @@ public class GatewayIntegrationTests {
|
||||
log.info("modifyResponseFilter start");
|
||||
String value = (String) exchange.getAttribute(GatewayFilter.GATEWAY_HANDLER_MAPPER_ATTR).orElse("N/A");
|
||||
exchange.getResponse().getHeaders().add(HANDLER_MAPPER_HEADER, value);
|
||||
Route route = (Route) exchange.getAttribute(GatewayFilter.GATEWAY_ROUTE_ATTR).orElse(null);
|
||||
if (route != null) {
|
||||
exchange.getResponse().getHeaders().add(ROUTE_ID_HEADER, route.getId());
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,12 +3,28 @@ spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
test1:
|
||||
requestPath: /**
|
||||
downstreamUrl: http://httpbin.org:80
|
||||
test2:
|
||||
requestHost: '**.example.org'
|
||||
downstreamUrl: http://httpbin.org:80
|
||||
# =====================================
|
||||
- id: host_example_to_httpbin
|
||||
downstreamUrl: http://httpbin.org:80
|
||||
predicates:
|
||||
- name: Host
|
||||
value: '**.example.org'
|
||||
|
||||
# =====================================
|
||||
- id: host_foo_path_headers_to_httpbin
|
||||
downstreamUrl: http://httpbin.org:80
|
||||
predicates:
|
||||
- name: Host
|
||||
value: '**.foo.org'
|
||||
- name: Url
|
||||
value: '/headers'
|
||||
|
||||
# =====================================
|
||||
- id: default_path_to_httpbin
|
||||
downstreamUrl: http://httpbin.org:80
|
||||
predicates:
|
||||
- name: Url
|
||||
value: /**
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway: TRACE
|
||||
|
||||
Reference in New Issue
Block a user