Merge branch '2.2.x'
This commit is contained in:
@@ -33,6 +33,8 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Tim Ysewyn
|
||||
@@ -56,7 +58,8 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol
|
||||
}
|
||||
|
||||
public void setAutoForwardedHeaders(Set<String> autoForwardedHeaders) {
|
||||
this.autoForwardedHeaders = autoForwardedHeaders;
|
||||
this.autoForwardedHeaders = autoForwardedHeaders == null ? null
|
||||
: autoForwardedHeaders.stream().map(String::toLowerCase).collect(toSet());
|
||||
}
|
||||
|
||||
public void setSensitive(Set<String> sensitive) {
|
||||
@@ -100,7 +103,7 @@ public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResol
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String header = headerNames.nextElement();
|
||||
if (this.autoForwardedHeaders.contains(header)) {
|
||||
if (this.autoForwardedHeaders.contains(header.toLowerCase())) {
|
||||
headers.addAll(header,
|
||||
Collections.list(nativeRequest.getHeaders(header)));
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = { "spring.cloud.gateway.proxy.auto-forward=baz" },
|
||||
@SpringBootTest(properties = { "spring.cloud.gateway.proxy.auto-forward=Baz" },
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(classes = TestApplication.class)
|
||||
public class ProductionConfigurationTests {
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
|
||||
import org.springframework.cloud.gateway.route.Route;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -46,9 +48,15 @@ public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint
|
||||
public GatewayControllerEndpoint(List<GlobalFilter> globalFilters,
|
||||
List<GatewayFilterFactory> gatewayFilters,
|
||||
List<RoutePredicateFactory> routePredicates,
|
||||
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
|
||||
super(null, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter,
|
||||
routeLocator);
|
||||
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator,
|
||||
RouteDefinitionLocator routeDefinitionLocator) {
|
||||
super(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates,
|
||||
routeDefinitionWriter, routeLocator);
|
||||
}
|
||||
|
||||
@GetMapping("/routedefinitions")
|
||||
public Flux<RouteDefinition> routesdef() {
|
||||
return this.routeDefinitionLocator.getRouteDefinitions();
|
||||
}
|
||||
|
||||
// TODO: Flush out routes without a definition
|
||||
|
||||
@@ -793,9 +793,11 @@ public class GatewayAutoConfiguration {
|
||||
List<GlobalFilter> globalFilters,
|
||||
List<GatewayFilterFactory> gatewayFilters,
|
||||
List<RoutePredicateFactory> routePredicates,
|
||||
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
|
||||
RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator,
|
||||
RouteDefinitionLocator routeDefinitionLocator) {
|
||||
return new GatewayControllerEndpoint(globalFilters, gatewayFilters,
|
||||
routePredicates, routeDefinitionWriter, routeLocator);
|
||||
routePredicates, routeDefinitionWriter, routeLocator,
|
||||
routeDefinitionLocator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.cloud.gateway.config.LoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -121,17 +122,18 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
|
||||
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
|
||||
URI uri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
|
||||
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory
|
||||
.getInstance(uri.getHost(), ReactorLoadBalancer.class,
|
||||
ServiceInstance.class);
|
||||
.getInstance(uri.getHost(), ReactorServiceInstanceLoadBalancer.class);
|
||||
if (loadBalancer == null) {
|
||||
throw new NotFoundException("No loadbalancer available for " + uri.getHost());
|
||||
}
|
||||
return loadBalancer.choose(createRequest());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private Request createRequest() {
|
||||
return ReactiveLoadBalancer.REQUEST;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
|
||||
return new NegateAsyncPredicate<>(this);
|
||||
}
|
||||
|
||||
default AsyncPredicate<T> not(AsyncPredicate<? super T> other) {
|
||||
return new NegateAsyncPredicate<>(other);
|
||||
}
|
||||
|
||||
default AsyncPredicate<T> or(AsyncPredicate<? super T> other) {
|
||||
return new OrAsyncPredicate<>(this, other);
|
||||
}
|
||||
@@ -84,7 +88,7 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("!%s", this.predicate);
|
||||
return String.format("!(%s)", this.predicate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.gateway.route;
|
||||
|
||||
import org.springframework.boot.web.context.WebServerApplicationContext;
|
||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
@@ -43,8 +44,14 @@ public class RouteRefreshListener implements ApplicationListener<ApplicationEven
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof ContextRefreshedEvent
|
||||
|| event instanceof RefreshScopeRefreshedEvent
|
||||
if (event instanceof ContextRefreshedEvent) {
|
||||
ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
|
||||
if (!WebServerApplicationContext.hasServerNamespace(
|
||||
refreshedEvent.getApplicationContext(), "management")) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
else if (event instanceof RefreshScopeRefreshedEvent
|
||||
|| event instanceof InstanceRegisteredEvent) {
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -113,6 +113,27 @@ public class BooleanSpec extends UriSpec {
|
||||
return new BooleanSpec(this.routeBuilder, this.builder);
|
||||
}
|
||||
|
||||
public BooleanSpec not(Function<PredicateSpec, BooleanSpec> fn) {
|
||||
return fn
|
||||
.apply(new NotOpSpec(this.routeBuilder, this.builder, this.operator));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NotOpSpec extends BooleanOpSpec {
|
||||
|
||||
NotOpSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder,
|
||||
Operator operator) {
|
||||
super(routeBuilder, builder, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanSpec asyncPredicate(AsyncPredicate<ServerWebExchange> predicate) {
|
||||
AsyncPredicate<ServerWebExchange> negated = this.routeBuilder.getPredicate()
|
||||
.not(predicate);
|
||||
return super.asyncPredicate(negated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.route.builder;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.cloud.gateway.handler.AsyncPredicate;
|
||||
@@ -294,4 +295,8 @@ public class PredicateSpec extends UriSpec {
|
||||
return predicate(exchange -> true);
|
||||
}
|
||||
|
||||
public BooleanSpec not(Function<PredicateSpec, BooleanSpec> fn) {
|
||||
return alwaysTrue().and().not(fn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
|
||||
@@ -124,8 +124,7 @@ public class ReactiveLoadBalancerClientFilterTests {
|
||||
RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer(
|
||||
ServiceInstanceListSuppliers.toProvider("myservice", serviceInstance),
|
||||
"myservice", -1);
|
||||
when(clientFactory.getInstance("myservice", ReactorLoadBalancer.class,
|
||||
ServiceInstance.class)).thenReturn(loadBalancer);
|
||||
when(clientFactory.getInstance("myservice", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer);
|
||||
|
||||
when(chain.filter(exchange)).thenReturn(Mono.empty());
|
||||
|
||||
@@ -134,8 +133,8 @@ public class ReactiveLoadBalancerClientFilterTests {
|
||||
assertThat((LinkedHashSet<URI>) exchange
|
||||
.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR)).contains(url);
|
||||
|
||||
verify(clientFactory).getInstance("myservice", ReactorLoadBalancer.class,
|
||||
ServiceInstance.class);
|
||||
verify(clientFactory).getInstance("myservice",
|
||||
ReactorServiceInstanceLoadBalancer.class);
|
||||
|
||||
verifyNoMoreInteractions(clientFactory);
|
||||
|
||||
@@ -249,8 +248,7 @@ public class ReactiveLoadBalancerClientFilterTests {
|
||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
|
||||
RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer(
|
||||
ServiceInstanceListSuppliers.toProvider("service1"), "service1", -1);
|
||||
when(clientFactory.getInstance("service1", ReactorLoadBalancer.class,
|
||||
ServiceInstance.class)).thenReturn(loadBalancer);
|
||||
when(clientFactory.getInstance("service1", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer);
|
||||
properties.setUse404(true);
|
||||
ReactiveLoadBalancerClientFilter filter = new ReactiveLoadBalancerClientFilter(
|
||||
clientFactory, properties);
|
||||
@@ -279,8 +277,7 @@ public class ReactiveLoadBalancerClientFilterTests {
|
||||
new DefaultServiceInstance("service1_1", "service1",
|
||||
"service1-host1", 8081, false)),
|
||||
"service1", -1);
|
||||
when(clientFactory.getInstance("service1", ReactorLoadBalancer.class,
|
||||
ServiceInstance.class)).thenReturn(loadBalancer);
|
||||
when(clientFactory.getInstance("service1", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer);
|
||||
|
||||
ReactiveLoadBalancerClientFilter filter = new ReactiveLoadBalancerClientFilter(
|
||||
clientFactory, properties);
|
||||
|
||||
@@ -74,6 +74,20 @@ public class AsyncPredicateTest {
|
||||
right.assertTested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negateOperatorWorks() {
|
||||
TestAsyncPredicate<Object> falsePredicate = new TestAsyncPredicate<>(o -> false);
|
||||
TestAsyncPredicate<Object> truePredicate = new TestAsyncPredicate<>(o -> true);
|
||||
Publisher<Boolean> falseNot = falsePredicate.negate().apply(new Object());
|
||||
Publisher<Boolean> trueNot = truePredicate.negate().apply(new Object());
|
||||
|
||||
StepVerifier.create(falseNot).expectNext(true).expectComplete().verify();
|
||||
StepVerifier.create(trueNot).expectNext(false).expectComplete().verify();
|
||||
|
||||
falsePredicate.assertTested();
|
||||
truePredicate.assertTested();
|
||||
}
|
||||
|
||||
/**
|
||||
* An AsyncPredicate decorator that records if the apply method was called.
|
||||
*/
|
||||
|
||||
@@ -21,14 +21,21 @@ import org.junit.BeforeClass;
|
||||
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.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 org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@@ -57,11 +64,51 @@ public class RoutePredicateHandlerMappingIntegrationTests extends BaseWebClientT
|
||||
.uri("/get").exchange().expectStatus().isNotFound();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void andNotWorksWithMissingParameter() {
|
||||
testClient.get().uri("/andnotquery").exchange().expectBody(String.class)
|
||||
.isEqualTo("notsupplied");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void andNotWorksWithParameter() {
|
||||
testClient.get().uri("/andnotquery?myquery=shouldnotsee").exchange()
|
||||
.expectBody(String.class).isEqualTo("hasquery");
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@Import(DefaultTestConfig.class)
|
||||
@RestController
|
||||
public static class TestConfig {
|
||||
|
||||
@Value("${test.uri:http://httpbin.org:80}")
|
||||
String uri;
|
||||
|
||||
@GetMapping("/httpbin/andnotquery")
|
||||
String andnotquery(@RequestParam(name = "myquery",
|
||||
defaultValue = "notsupplied") String myquery) {
|
||||
return myquery;
|
||||
}
|
||||
|
||||
@GetMapping("/httpbin/hasquery")
|
||||
String hasquery() {
|
||||
return "hasquery";
|
||||
}
|
||||
|
||||
@Bean
|
||||
RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("and_not_missing_myquery",
|
||||
r -> r.path("/andnotquery").and().not(p -> p.query("myquery"))
|
||||
.filters(f -> f.prefixPath("/httpbin")).uri(uri))
|
||||
.route("and_not_has_myquery",
|
||||
r -> r.path("/andnotquery").and().query("myquery")
|
||||
.filters(f -> f.setPath("/httpbin/hasquery"))
|
||||
.uri(uri))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,19 +18,48 @@ package org.springframework.cloud.gateway.route;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.web.context.WebServerApplicationContext;
|
||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class RouteRefreshListenerTests {
|
||||
|
||||
@Test
|
||||
public void onContextRefreshedEventManagement() {
|
||||
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
|
||||
RouteRefreshListener listener = new RouteRefreshListener(publisher);
|
||||
|
||||
WebServerApplicationContext applicationContext = mock(
|
||||
WebServerApplicationContext.class);
|
||||
when(applicationContext.getServerNamespace()).thenReturn("management");
|
||||
listener.onApplicationEvent(new ContextRefreshedEvent(applicationContext));
|
||||
|
||||
verifyNoInteractions(publisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onContextRefreshedEvent() {
|
||||
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
|
||||
RouteRefreshListener listener = new RouteRefreshListener(publisher);
|
||||
|
||||
listener.onApplicationEvent(
|
||||
new ContextRefreshedEvent(mock(ApplicationContext.class)));
|
||||
|
||||
verify(publisher).publishEvent(any(RefreshRoutesEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onInstanceRegisteredEvent() {
|
||||
ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class);
|
||||
|
||||
Reference in New Issue
Block a user