Merge branch '2.2.x'
This commit is contained in:
@@ -281,7 +281,7 @@ spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: host_route
|
||||
- id: path_route
|
||||
uri: https://example.org
|
||||
predicates:
|
||||
- Path=/red/{segment},/blue/{segment}
|
||||
@@ -2073,7 +2073,7 @@ To enable this, set `spring.cloud.gateway.discovery.locator.enabled=true` and ma
|
||||
|
||||
==== Configuring Predicates and Filters For `DiscoveryClient` Routes
|
||||
|
||||
By default, the fateway defines a single predicate and filter for routes created with a `DiscoveryClient`.
|
||||
By default, the gateway defines a single predicate and filter for routes created with a `DiscoveryClient`.
|
||||
|
||||
The default predicate is a path predicate defined with the pattern `/serviceId/**`, where `serviceId` is
|
||||
the ID of the service from the `DiscoveryClient`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
* Copyright 2013-2020 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.
|
||||
@@ -133,9 +133,11 @@ import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
@@ -324,11 +326,6 @@ public class GatewayAutoConfiguration {
|
||||
return new WeightCalculatorWebFilter(routeLocator, configurationService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AfterRoutePredicateFactory afterRoutePredicateFactory() {
|
||||
return new AfterRoutePredicateFactory();
|
||||
}
|
||||
|
||||
/*
|
||||
* @Bean //TODO: default over netty? configurable public WebClientHttpRoutingFilter
|
||||
* webClientHttpRoutingFilter() { //TODO: WebClient bean return new
|
||||
@@ -340,6 +337,11 @@ public class GatewayAutoConfiguration {
|
||||
|
||||
// Predicate Factory beans
|
||||
|
||||
@Bean
|
||||
public AfterRoutePredicateFactory afterRoutePredicateFactory() {
|
||||
return new AfterRoutePredicateFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BeforeRoutePredicateFactory beforeRoutePredicateFactory() {
|
||||
return new BeforeRoutePredicateFactory();
|
||||
@@ -575,7 +577,8 @@ public class GatewayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HttpClient gatewayHttpClient(HttpClientProperties properties) {
|
||||
public HttpClient gatewayHttpClient(HttpClientProperties properties,
|
||||
List<HttpClientCustomizer> customizers) {
|
||||
|
||||
// configure pool resources
|
||||
HttpClientProperties.Pool pool = properties.getPool();
|
||||
@@ -595,6 +598,7 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
HttpClient httpClient = HttpClient.create(connectionProvider)
|
||||
// TODO: move customizations to HttpClientCustomizers
|
||||
.httpResponseDecoder(spec -> {
|
||||
if (properties.getMaxHeaderSize() != null) {
|
||||
// cast to int is ok, since @Max is Integer.MAX_VALUE
|
||||
@@ -673,6 +677,13 @@ public class GatewayAutoConfiguration {
|
||||
httpClient = httpClient.wiretap(true);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(customizers)) {
|
||||
customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
|
||||
for (HttpClientCustomizer customizer : customizers) {
|
||||
httpClient = customizer.customize(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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.config;
|
||||
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface HttpClientCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the specified {@link HttpClient}.
|
||||
* @param httpClient the http client to customize.
|
||||
* @return the customized HttpClient.
|
||||
*/
|
||||
HttpClient customize(HttpClient httpClient);
|
||||
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
|
||||
.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
|
||||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
|
||||
|
||||
Flux<HttpClientResponse> responseFlux = httpClientWithTimeoutFrom(route)
|
||||
Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
|
||||
.headers(headers -> {
|
||||
headers.add(httpHeaders);
|
||||
// Will either be set below, or later by Netty
|
||||
@@ -242,7 +242,16 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpClient httpClientWithTimeoutFrom(Route route) {
|
||||
/**
|
||||
* Creates a new HttpClient with per route timeout configuration. Sub-classes that
|
||||
* override, should call super.getHttpClient() if they want to honor the per route
|
||||
* timeout configuration.
|
||||
* @param route the current route.
|
||||
* @param exchange the current ServerWebExchange.
|
||||
* @param chain the current GatewayFilterChain.
|
||||
* @return
|
||||
*/
|
||||
protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
|
||||
Integer connectTimeout = (Integer) route.getMetadata().get(CONNECT_TIMEOUT_ATTR);
|
||||
if (connectTimeout != null) {
|
||||
return this.httpClient.tcpConfiguration((tcpClient) -> tcpClient
|
||||
|
||||
@@ -108,7 +108,7 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
|
||||
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(
|
||||
response.getServer(), overrideScheme);
|
||||
|
||||
URI requestUrl = LoadBalancerUriTools.reconstructURI(serviceInstance, uri);
|
||||
URI requestUrl = reconstructURI(serviceInstance, uri);
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
|
||||
@@ -117,6 +117,10 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
|
||||
}).then(chain.filter(exchange));
|
||||
}
|
||||
|
||||
protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
|
||||
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
|
||||
}
|
||||
|
||||
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
|
||||
URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
|
||||
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
|
||||
@@ -106,8 +105,8 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
|
||||
|
||||
@Override
|
||||
public Publisher<Boolean> apply(T t) {
|
||||
return Flux.zip(left.apply(t), right.apply(t))
|
||||
.map(tuple -> tuple.getT1() && tuple.getT2());
|
||||
return Mono.from(left.apply(t)).flatMap(
|
||||
result -> !result ? Mono.just(false) : Mono.from(right.apply(t)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,8 +132,8 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
|
||||
|
||||
@Override
|
||||
public Publisher<Boolean> apply(T t) {
|
||||
return Flux.zip(left.apply(t), right.apply(t))
|
||||
.map(tuple -> tuple.getT1() || tuple.getT2());
|
||||
return Mono.from(left.apply(t)).flatMap(
|
||||
result -> result ? Mono.just(true) : Mono.from(right.apply(t)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
* Copyright 2013-2020 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.gateway.config;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
@@ -30,6 +32,8 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex
|
||||
import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint;
|
||||
import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
||||
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||
|
||||
@@ -81,7 +85,7 @@ public class GatewayAutoConfigurationTests {
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class,
|
||||
MetricsAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class,
|
||||
GatewayAutoConfiguration.class))
|
||||
GatewayAutoConfiguration.class, HttpClientCustomizedConfig.class))
|
||||
.withPropertyValues(
|
||||
"spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true",
|
||||
"spring.cloud.gateway.httpclient.connect-timeout=10",
|
||||
@@ -113,6 +117,9 @@ public class GatewayAutoConfigurationTests {
|
||||
.getBean(ReactorNettyWebSocketClient.class);
|
||||
assertThat(webSocketClient.getMaxFramePayloadLength())
|
||||
.isEqualTo(1024);
|
||||
HttpClientCustomizedConfig config = context
|
||||
.getBean(HttpClientCustomizedConfig.class);
|
||||
assertThat(config.called.get()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,4 +150,19 @@ public class GatewayAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class HttpClientCustomizedConfig {
|
||||
|
||||
private final AtomicBoolean called = new AtomicBoolean();
|
||||
|
||||
@Bean
|
||||
HttpClientCustomizer myCustomCustomizer() {
|
||||
return httpClient -> {
|
||||
called.compareAndSet(false, true);
|
||||
return httpClient;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
public class AsyncPredicateTest {
|
||||
|
||||
@Test
|
||||
public void andPredicateShouldNotTestRightOperatorIfLeftOperatorIsFalse() {
|
||||
TestAsyncPredicate<Object> left = new TestAsyncPredicate<>(o -> false);
|
||||
TestAsyncPredicate<Object> right = new TestAsyncPredicate<>(o -> true);
|
||||
Publisher<Boolean> andTest = left.and(right).apply(new Object());
|
||||
|
||||
StepVerifier.create(andTest).expectNext(false).expectComplete().verify();
|
||||
|
||||
left.assertTested();
|
||||
right.assertUntested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void andPredicateShouldTestRightOperatorIfLeftOperatorIsTrue() {
|
||||
TestAsyncPredicate<Object> left = new TestAsyncPredicate<>(o -> true);
|
||||
TestAsyncPredicate<Object> right = new TestAsyncPredicate<>(o -> false);
|
||||
Publisher<Boolean> andTest = left.and(right).apply(new Object());
|
||||
|
||||
StepVerifier.create(andTest).expectNext(false).expectComplete().verify();
|
||||
|
||||
left.assertTested();
|
||||
right.assertTested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orPredicateShouldNotTestRightOperatorIfLeftOperatorIsTrue() {
|
||||
TestAsyncPredicate<Object> left = new TestAsyncPredicate<>(o -> true);
|
||||
TestAsyncPredicate<Object> right = new TestAsyncPredicate<>(o -> false);
|
||||
Publisher<Boolean> orTest = left.or(right).apply(new Object());
|
||||
|
||||
StepVerifier.create(orTest).expectNext(true).expectComplete().verify();
|
||||
|
||||
left.assertTested();
|
||||
right.assertUntested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orPredicateShouldTestRightOperatorIfLeftOperatorIsFalse() {
|
||||
TestAsyncPredicate<Object> left = new TestAsyncPredicate<>(o -> false);
|
||||
TestAsyncPredicate<Object> right = new TestAsyncPredicate<>(o -> true);
|
||||
Publisher<Boolean> orTest = left.or(right).apply(new Object());
|
||||
|
||||
StepVerifier.create(orTest).expectNext(true).expectComplete().verify();
|
||||
|
||||
left.assertTested();
|
||||
right.assertTested();
|
||||
}
|
||||
|
||||
/**
|
||||
* An AsyncPredicate decorator that records if the apply method was called.
|
||||
*/
|
||||
private final static class TestAsyncPredicate<T> implements AsyncPredicate<T> {
|
||||
|
||||
private final Predicate<T> delegate;
|
||||
|
||||
private boolean tested = false;
|
||||
|
||||
private TestAsyncPredicate(Predicate<T> predicate) {
|
||||
this.delegate = predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Boolean> apply(T t) {
|
||||
tested = true;
|
||||
return Mono.just(delegate.test(t));
|
||||
}
|
||||
|
||||
public void assertTested() {
|
||||
Assert.assertTrue("predicate must have been tested", tested);
|
||||
}
|
||||
|
||||
public void assertUntested() {
|
||||
Assert.assertFalse("predicate must not have been tested", tested);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user