Browse Source

Support Predicate based matching.

pull/41/head
Spencer Gibb 8 years ago
parent
commit
209d3c4905
No known key found for this signature in database
GPG Key ID: 7788A47380690861
  1. 1
      mvnw
  2. 21
      src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
  3. 110
      src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java
  4. 224
      src/main/java/org/springframework/cloud/gateway/handler/GatewayHostHandlerMapping.java
  5. 15
      src/main/java/org/springframework/cloud/gateway/handler/GatewayPredicateFactory.java
  6. 67
      src/main/java/org/springframework/cloud/gateway/handler/GatewayUrlHandlerMapping.java
  7. 33
      src/main/java/org/springframework/cloud/gateway/handler/HostPredicateFactory.java
  8. 147
      src/main/java/org/springframework/cloud/gateway/handler/ServerWebExchangePredicateHandlerMapping.java
  9. 47
      src/main/java/org/springframework/cloud/gateway/handler/UrlPredicateFactory.java
  10. 46
      src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java
  11. 28
      src/test/resources/application.yml

1
mvnw vendored

@ -136,7 +136,6 @@ if $mingw ; then @@ -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

21
src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@ -8,9 +8,11 @@ import org.springframework.cloud.gateway.actuate.GatewayEndpoint; @@ -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 { @@ -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

110
src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java

@ -1,10 +1,15 @@ @@ -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; @@ -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 { @@ -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 + '\'' +
'}';
}
}

224
src/main/java/org/springframework/cloud/gateway/handler/GatewayHostHandlerMapping.java

@ -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;
}
}
}

15
src/main/java/org/springframework/cloud/gateway/handler/GatewayPredicateFactory.java

@ -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);
}

67
src/main/java/org/springframework/cloud/gateway/handler/GatewayUrlHandlerMapping.java

@ -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;
}
}
}

33
src/main/java/org/springframework/cloud/gateway/handler/HostPredicateFactory.java

@ -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);
};
}
}

147
src/main/java/org/springframework/cloud/gateway/handler/ServerWebExchangePredicateHandlerMapping.java

@ -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 {
}
}

47
src/main/java/org/springframework/cloud/gateway/handler/UrlPredicateFactory.java

@ -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
};
}
}

46
src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java

@ -5,11 +5,15 @@ import org.apache.commons.logging.LogFactory; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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);
};
}

28
src/test/resources/application.yml

@ -3,12 +3,28 @@ spring: @@ -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

Loading…
Cancel
Save