Spencer Gibb
8 years ago
11 changed files with 411 additions and 328 deletions
@ -1,224 +0,0 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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
|
||||
}; |
||||
} |
||||
} |
Loading…
Reference in new issue