Merge branch '2.2.x'

This commit is contained in:
spencergibb
2020-09-15 14:09:06 -04:00
13 changed files with 161 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*/

View File

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

View File

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