Resolving conflicts
This commit is contained in:
@@ -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
|
||||
}));
|
||||
};
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 +
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user