Resolving conflicts

This commit is contained in:
Tony Clarke
2018-07-11 14:17:34 -04:00
21 changed files with 593 additions and 31 deletions

View File

@@ -781,7 +781,46 @@ The `GlobalFilter` interface has the same signature as `GatewayFilter`. These ar
=== Combined Global Filter and GatewayFilter Ordering
TODO: document ordering
When a request comes in (and matches a Route) the Filtering Web Handler will add all instances of `GlobalFilter` and all route specific instances of `GatewayFilter` to a filter chain. This combined filter chain is sorted by the `org.springframework.core.Ordered` interface, which can be set by implementing the `getOrder()` method or by using the `@Order` annotation.
As Spring Cloud Gateway distinguishes between "pre" and "post" phases for filter logic execution (see: How It Works), the filter with the highest precedence will be the first in the "pre"-phase and the last in the "post"-phase.
.ExampleConfiguration.java
[source,java]
----
@Bean
@Order(-1)
public GlobalFilter a() {
return (exchange, chain) -> {
log.info("first pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("third post filter");
}));
};
}
@Bean
@Order(0)
public GlobalFilter b() {
return (exchange, chain) -> {
log.info("second pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("second post filter");
}));
};
}
@Bean
@Order(1)
public GlobalFilter c() {
return (exchange, chain) -> {
log.info("third pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("first post filter");
}));
};
}
----
=== Forward Routing Filter
@@ -990,7 +1029,7 @@ public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostG
// grab configuration from Config object
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpReponse response = exchange.getResponse();
ServerHttpResponse response = exchange.getResponse();
//Manipulate the response in some way
}));
};

View File

@@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>2.0.2.RELEASE</version>
<version>2.0.2.BUILD-SNAPSHOT</version>
<relativePath/>
</parent>
<scm>
@@ -48,8 +48,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud-commons.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.0.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-commons.version>2.0.1.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.0.1.BUILD-SNAPSHOT</spring-cloud-netflix.version>
</properties>
<dependencyManagement>

View File

@@ -89,6 +89,7 @@ import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
@@ -216,8 +217,9 @@ public class GatewayAutoConfiguration {
@Bean
public NettyRoutingFilter routingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
return new NettyRoutingFilter(httpClient, headersFilters);
ObjectProvider<List<HttpHeadersFilter>> headersFilters,
HttpClientProperties properties) {
return new NettyRoutingFilter(httpClient, headersFilters, properties);
}
@Bean
@@ -437,6 +439,11 @@ public class GatewayAutoConfiguration {
return new WeightRoutePredicateFactory();
}
@Bean
public CloudFoundryRouteServiceRoutePredicateFactory cloudFoundryRouteServiceRoutePredicateFactory() {
return new CloudFoundryRouteServiceRoutePredicateFactory();
}
// GatewayFilter Factory beans
@Bean

View File

@@ -20,6 +20,8 @@ package org.springframework.cloud.gateway.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import reactor.ipc.netty.resources.PoolResources;
import java.time.Duration;
/**
* Configuration properties for the Netty {@link reactor.ipc.netty.http.client.HttpClient}
*/
@@ -29,6 +31,9 @@ public class HttpClientProperties {
/** The connect timeout in millis, the default is 45s. */
private Integer connectTimeout;
/** The response timeout. */
private Duration responseTimeout;
/** Pool configuration for Netty HttpClient */
private Pool pool = new Pool();
@@ -42,6 +47,14 @@ public class HttpClientProperties {
return connectTimeout;
}
public Duration getResponseTimeout() {
return responseTimeout;
}
public void setResponseTimeout(Duration responseTimeout) {
this.responseTimeout = responseTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
@@ -220,6 +233,7 @@ public class HttpClientProperties {
public String toString() {
return "HttpClientProperties{" +
"connectTimeout=" + connectTimeout +
", responseTimeout=" + responseTimeout +
", pool=" + pool +
", proxy=" + proxy +
", ssl=" + ssl +

View File

@@ -26,10 +26,13 @@ import reactor.core.publisher.Mono;
import reactor.ipc.netty.NettyPipeline;
import reactor.ipc.netty.http.client.HttpClient;
import reactor.ipc.netty.http.client.HttpClientRequest;
import reactor.ipc.netty.http.client.HttpClientResponse;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.config.HttpClientProperties;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.http.HttpHeaders;
@@ -55,11 +58,14 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
private final HttpClient httpClient;
private final ObjectProvider<List<HttpHeadersFilter>> headersFilters;
private final HttpClientProperties properties;
public NettyRoutingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
ObjectProvider<List<HttpHeadersFilter>> headersFilters,
HttpClientProperties properties) {
this.httpClient = httpClient;
this.headersFilters = headersFilters;
this.properties = properties;
}
@Override
@@ -93,7 +99,7 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
return this.httpClient.request(method, url, req -> {
Mono<HttpClientResponse> responseMono = this.httpClient.request(method, url, req -> {
final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnEach)
.headers(httpHeaders)
.chunkedTransfer(chunkedTransfer)
@@ -107,8 +113,16 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
return proxyRequest.sendHeaders() //I shouldn't need this
.send(request.getBody().map(dataBuffer ->
((NettyDataBuffer)dataBuffer).getNativeBuffer()));
}).doOnNext(res -> {
((NettyDataBuffer) dataBuffer).getNativeBuffer()));
});
if (properties.getResponseTimeout() != null) {
responseMono.timeout(properties.getResponseTimeout(),
Mono.error(new TimeoutException("Response took longer than timeout: " +
properties.getResponseTimeout())));
}
return responseMono.doOnNext(res -> {
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();

View File

@@ -104,7 +104,7 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
private void changeSchemeIfIsWebSocketUpgrade(ServerWebExchange exchange) {
// Check the Upgrade
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
String scheme = requestUrl.getScheme().toLowerCase();
String upgrade = exchange.getRequest().getHeaders().getUpgrade();
// change the scheme if the socket client send a "http" or "https"
if ("WebSocket".equalsIgnoreCase(upgrade) && ("http".equals(scheme) || "https".equals(scheme))) {
@@ -117,8 +117,9 @@ public class WebsocketRoutingFilter implements GlobalFilter, Ordered {
}
}
private String convertHttpToWs(String scheme) {
return "http".equals(scheme) ? "ws" : "https".equals(scheme) ? "wws" : scheme;
/* for testing */ static String convertHttpToWs(String scheme) {
scheme = scheme.toLowerCase();
return "http".equals(scheme) ? "ws" : "https".equals(scheme) ? "wss" : scheme;
}
private static class ProxyWebSocketHandler implements WebSocketHandler {

View File

@@ -26,6 +26,7 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.support.TimeoutException;
import reactor.core.publisher.Mono;
import reactor.retry.Repeat;
import reactor.retry.RepeatContext;
@@ -146,12 +147,11 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
};
}
private static <T> List<T> toList(T item) {
ArrayList<T> list = new ArrayList<>();
list.add(item);
return list;
private static <T> List<T> toList(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
@SuppressWarnings("unchecked")
public static class RetryConfig {
private int retries = 3;
@@ -161,7 +161,7 @@ public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory<Retr
private List<HttpMethod> methods = toList(HttpMethod.GET);
private List<Class<? extends Throwable>> exceptions = toList(IOException.class);
private List<Class<? extends Throwable>> exceptions = toList(IOException.class, TimeoutException.class);
public RetryConfig setRetries(int retries) {
this.retries = retries;

View File

@@ -45,6 +45,8 @@ import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
/**
* This filter is BETA and may be subject to change in a future release.
*/
@@ -82,7 +84,7 @@ public class ModifyResponseBodyGatewayFilterFactory
Class inClass = config.getInClass();
Class outClass = config.getOutClass();
MediaType originalResponseContentType = exchange.getAttribute("original_response_content_type");
MediaType originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);

View File

@@ -20,6 +20,7 @@ package org.springframework.cloud.gateway.handler;
import java.util.function.Function;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.web.cors.CorsConfiguration;
@@ -30,8 +31,6 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
@@ -90,12 +89,20 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
}
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator.getRoutes()
.filterWhen(route -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, route.getId());
return route.getPredicate().apply(exchange);
})
return this.routeLocator
.getRoutes()
//individually filter routes so that filterWhen error delaying is not a problem
.concatMap(route -> Mono
.just(route)
.filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
//instead of immediately stopping main flux due to error, log and swallow it
.doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
.onErrorResume(e -> Mono.empty())
)
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))

View File

@@ -54,8 +54,11 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
List<String> values = exchange.getRequest().getQueryParams().get(config.param);
if (values == null) {
return false;
}
for (String value : values) {
if (value.matches(config.regexp)) {
if (value != null && value.matches(config.regexp)) {
return true;
}
}

View File

@@ -114,7 +114,9 @@ public class Route implements Ordered {
public B uri(URI uri) {
this.uri = uri;
if (this.uri.getPort() < 0 && this.uri.getScheme().startsWith("http")) {
String scheme = this.uri.getScheme();
Assert.hasText(scheme, "The parameter [" + this.uri + "] format is incorrect, scheme can not be empty");
if (this.uri.getPort() < 0 && scheme.startsWith("http")) {
// default known http ports
int port = this.uri.getScheme().equals("https") ? 443 : 80;
this.uri = UriComponentsBuilder.fromUri(this.uri)

View File

@@ -23,6 +23,7 @@ import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
@@ -248,6 +249,12 @@ public class PredicateSpec extends UriSpec {
.setWeight(weight)));
}
public BooleanSpec cloudFoundryRouteService() {
return predicate(
getBean(CloudFoundryRouteServiceRoutePredicateFactory.class).apply(c -> {
}));
}
/**
* A predicate which is always true
* @return a {@link BooleanSpec} to be used to add logical operators

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.gateway.support;
public class TimeoutException extends Exception {
public TimeoutException() {
}
public TimeoutException(String message) {
super(message);
}
}

View File

@@ -62,6 +62,7 @@ public class GatewayAutoConfigurationTests {
GatewayAutoConfiguration.class))
.withPropertyValues("spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true",
"spring.cloud.gateway.httpclient.connect-timeout=10",
"spring.cloud.gateway.httpclient.response-timeout=10s",
"spring.cloud.gateway.httpclient.pool.type=fixed",
"spring.cloud.gateway.httpclient.proxy.host=myhost")
.run(context -> {

View File

@@ -0,0 +1,112 @@
package org.springframework.cloud.gateway.filter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.Ordered;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
/**
* @author Arjun Curat
*/
@RunWith(MockitoJUnitRunner.class)
public class ForwardRoutingFilterTests {
private ServerWebExchange exchange;
@Mock
private GatewayFilterChain chain;
@Mock
private DispatcherHandler dispatcherHandler;
@InjectMocks
private ForwardRoutingFilter forwardRoutingFilter;
@Before
public void setup() {
exchange = MockServerWebExchange.from(MockServerHttpRequest.get("localendpoint").build());
}
@Test
public void shouldNotFilterWhenGatewayRequestUrlSchemeIsNotForward() {
URI uri = UriComponentsBuilder.fromUriString("http://endpoint").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
forwardRoutingFilter.filter(exchange, chain);
verifyZeroInteractions(dispatcherHandler);
verify(chain).filter(exchange);
verifyNoMoreInteractions(chain);
}
@Test
public void shouldFilterWhenGatewayRequestUrlSchemeIsForward() {
URI uri = UriComponentsBuilder.fromUriString("forward://endpoint").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
assertThat(exchange.getAttributes().get(GATEWAY_ALREADY_ROUTED_ATTR)).isNull();
forwardRoutingFilter.filter(exchange, chain);
verifyNoMoreInteractions(chain);
verify(dispatcherHandler).handle(exchange);
assertThat(exchange.getAttributes().get(GATEWAY_ALREADY_ROUTED_ATTR)).isEqualTo(true);
}
@Test
public void shouldFilterAndKeepHostPathAsSpecified() {
URI uri = UriComponentsBuilder.fromUriString("forward://host/outage").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
forwardRoutingFilter.filter(exchange, chain);
verify(dispatcherHandler).handle(captor.capture());
ServerWebExchange webExchange = captor.getValue();
URI forwardedUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(forwardedUrl).hasScheme("forward").hasHost("host").hasPath("/outage");
}
@Test
public void shouldNotFilterWhenGatewayRequestUrlSchemeIsForwardButAlreadyRouted() {
URI uri = UriComponentsBuilder.fromUriString("forward://host").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
exchange.getAttributes().put(GATEWAY_ALREADY_ROUTED_ATTR, true);
forwardRoutingFilter.filter(exchange, chain);
verifyZeroInteractions(dispatcherHandler);
verify(chain).filter(exchange);
verifyNoMoreInteractions(chain);
}
@Test
public void orderIsLowestPrecedence() {
assertThat(forwardRoutingFilter.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.gateway.filter;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.convertHttpToWs;
public class WebscketRoutingFilterTests {
@Test
public void testConvertHttpToWs() {
assertThat(convertHttpToWs("http")).isEqualTo("ws");
assertThat(convertHttpToWs("HTTP")).isEqualTo("ws");
assertThat(convertHttpToWs("https")).isEqualTo("wss");
assertThat(convertHttpToWs("HTTPS")).isEqualTo("wss");
assertThat(convertHttpToWs("tcp")).isEqualTo("tcp");
}
}

View File

@@ -0,0 +1,98 @@
package org.springframework.cloud.gateway.handler;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.hamcrest.Matchers.containsString;
/**
* @author Simon Baslé
*/
public class RoutePredicateHandlerMappingTest {
@Rule
public OutputCapture outputCapture = new OutputCapture();
@Test
public void lookupRouteFromSyncPredicates() {
Route routeFalse = Route.async()
.id("routeFalse")
.uri("http://localhost")
.predicate(swe -> false)
.build();
Route routeFail = Route.async()
.id("routeFail")
.uri("http://localhost")
.predicate(swe -> { throw new IllegalStateException("boom"); })
.build();
Route routeTrue = Route.async()
.id("routeTrue")
.uri("http://localhost")
.predicate(swe -> true)
.build();
RouteLocator routeLocator =
() -> Flux.just(routeFalse, routeFail, routeTrue).hide();
RoutePredicateHandlerMapping mapping =
new RoutePredicateHandlerMapping(null, routeLocator, new GlobalCorsProperties());
final Mono<Route> routeMono =
mapping.lookupRoute(Mockito.mock(ServerWebExchange.class));
StepVerifier.create(routeMono.map(Route::getId))
.expectNext("routeTrue")
.verifyComplete();
outputCapture.expect(containsString("Error applying predicate for route: routeFail"));
outputCapture.expect(containsString("java.lang.IllegalStateException: boom"));
}
@Test
public void lookupRouteFromAsyncPredicates() {
Route routeFalse = Route.async()
.id("routeFalse")
.uri("http://localhost")
.asyncPredicate(swe -> Mono.just(false))
.build();
Route routeError = Route.async()
.id("routeError")
.uri("http://localhost")
.asyncPredicate(swe -> Mono.error(new IllegalStateException("boom1")))
.build();
Route routeFail = Route.async()
.id("routeFail")
.uri("http://localhost")
.asyncPredicate(swe -> { throw new IllegalStateException("boom2"); })
.build();
Route routeTrue = Route.async()
.id("routeTrue")
.uri("http://localhost")
.asyncPredicate(swe -> Mono.just(true))
.build();
RouteLocator routeLocator =
() -> Flux.just(routeFalse, routeError, routeFail, routeTrue).hide();
RoutePredicateHandlerMapping mapping =
new RoutePredicateHandlerMapping(null, routeLocator, new GlobalCorsProperties());
final Mono<Route> routeMono =
mapping.lookupRoute(Mockito.mock(ServerWebExchange.class));
StepVerifier.create(routeMono.map(Route::getId))
.expectNext("routeTrue")
.verifyComplete();
outputCapture.expect(containsString("Error applying predicate for route: routeError"));
outputCapture.expect(containsString("java.lang.IllegalStateException: boom1"));
outputCapture.expect(containsString("Error applying predicate for route: routeFail"));
outputCapture.expect(containsString("java.lang.IllegalStateException: boom2"));
}
}

View File

@@ -0,0 +1,76 @@
package org.springframework.cloud.gateway.handler.predicate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.databind.JsonNode;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* @author Toshiaki Maki
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
public class CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests
extends BaseWebClientTests {
@LocalServerPort
int port;
@Test
public void predicateWorkWithProperties() {
testClient.get().uri("/").header("Host", "props.routeservice.example.com")
.header("X-CF-Forwarded-Url",
"http://localhost:" + port + "/actuator/health")
.header("X-CF-Proxy-Signature", "foo")
.header("X-CF-Proxy-Metadata", "bar").exchange()
.expectBody(JsonNode.class)
.consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue());
}
@Test
public void predicateWillNotWorkUnlessHeadersAreEnough() {
testClient.get().uri("/").header("Host", "props.routeservice.example.com")
.header("X-CF-Forwarded-Url",
"http://localhost:" + port + "/actuator/health")
.header("X-CF-Proxy-Metadata", "bar").exchange().expectStatus().isOk()
.expectHeader().valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin");
}
@Test
public void predicateWorkWithDsl() {
testClient.get().uri("/").header("Host", "dsl.routeservice.example.com")
.header("X-CF-Forwarded-Url",
"http://localhost:" + port + "/actuator/health")
.header("X-CF-Proxy-Signature", "foo")
.header("X-CF-Proxy-Metadata", "bar").exchange()
.expectBody(JsonNode.class)
.consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue());
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r.cloudFoundryRouteService().and()
.header("Host", "dsl.routeservice.example.com")
.filters(f -> f.requestHeaderToRequestUri("X-CF-Forwarded-Url"))
.uri("http://example.com")).build();
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.gateway.handler.predicate;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@DirtiesContext
public class QueryRoutePredicateFactoryTests extends BaseWebClientTests {
@Rule
public OutputCapture output = new OutputCapture();
@Test
public void noQueryParamWorks() {
testClient.get().uri("/get")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin");;
output.expect(not(containsString("Error applying predicate for route: foo_query_param")));
}
@Test
public void queryParamWorks() {
testClient.get().uri("/get?foo=bar")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(ROUTE_ID_HEADER, "foo_query_param");;
}
@Test
public void emptyQueryParamWorks() {
testClient.get().uri("/get?foo")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin");;
output.expect(not(containsString("Error applying predicate for route: foo_query_param")));
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig {
@Value("${test.uri}")
private String uri;
@Bean
RouteLocator queryRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("foo_query_param", r ->
r.query("foo", "bar")
.filters(f -> f.prefixPath("/httpbin"))
.uri(uri))
.build();
}
}
}

View File

@@ -17,12 +17,17 @@
package org.springframework.cloud.gateway.route;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
public class RouteTests {
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void defeaultHttpPort() {
Route route = Route.async().id("1")
@@ -47,7 +52,6 @@ public class RouteTests {
.hasPort(443);
}
@Test
public void fullUri() {
Route route = Route.async().id("1")
@@ -59,4 +63,12 @@ public class RouteTests {
.hasScheme("http")
.hasPort(8080);
}
@Test
public void nullScheme() {
exception.expect(IllegalArgumentException.class);
Route.async().id("1")
.predicate(exchange -> true)
.uri("/pathonly");
}
}

View File

@@ -258,6 +258,14 @@ spring:
filters:
- RequestHeaderToRequestUri=X-CF-Forwarded-Url
# =====================================
- id: cloudfoundry_routeservice_test
uri: ${test.uri}
predicates:
- CloudFoundryRouteService=
filters:
- RequestHeaderToRequestUri=X-CF-Forwarded-Url
# =====================================
- id: default_path_to_httpbin
uri: ${test.uri}