Merge branch 'FantasticDream/main'
This commit is contained in:
@@ -272,8 +272,8 @@ public class GatewayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
|
||||
return new FilteringWebHandler(globalFilters);
|
||||
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters, GatewayProperties properties) {
|
||||
return new FilteringWebHandler(globalFilters, properties.isRouteFilterCacheEnabled());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -68,6 +68,19 @@ public class GatewayProperties {
|
||||
*/
|
||||
private boolean failOnRouteDefinitionError = true;
|
||||
|
||||
/**
|
||||
* Enables the route filter cache, defaults to false.
|
||||
*/
|
||||
private boolean routeFilterCacheEnabled = false;
|
||||
|
||||
public boolean isRouteFilterCacheEnabled() {
|
||||
return routeFilterCacheEnabled;
|
||||
}
|
||||
|
||||
public void setRouteFilterCacheEnabled(boolean routeFilterCacheEnabled) {
|
||||
this.routeFilterCacheEnabled = routeFilterCacheEnabled;
|
||||
}
|
||||
|
||||
public List<RouteDefinition> getRoutes() {
|
||||
return routes;
|
||||
}
|
||||
@@ -109,6 +122,7 @@ public class GatewayProperties {
|
||||
.append("defaultFilters", defaultFilters)
|
||||
.append("streamingMediaTypes", streamingMediaTypes)
|
||||
.append("failOnRouteDefinitionError", failOnRouteDefinitionError)
|
||||
.append("routeFilterCacheEnabled", routeFilterCacheEnabled)
|
||||
.toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -18,18 +18,21 @@ package org.springframework.cloud.gateway.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.route.Route;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.DecoratingProxy;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
@@ -49,14 +52,28 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
|
||||
* @author Yuxin Wang
|
||||
* @since 0.1
|
||||
*/
|
||||
public class FilteringWebHandler implements WebHandler {
|
||||
public class FilteringWebHandler implements WebHandler, ApplicationListener<RefreshRoutesEvent> {
|
||||
|
||||
protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);
|
||||
|
||||
private final List<GatewayFilter> globalFilters;
|
||||
|
||||
private final ConcurrentHashMap<Route, List<GatewayFilter>> routeFilterMap = new ConcurrentHashMap();
|
||||
|
||||
private final boolean routeFilterCacheEnabled;
|
||||
|
||||
@Deprecated
|
||||
public FilteringWebHandler(List<GlobalFilter> globalFilters) {
|
||||
this(globalFilters, false);
|
||||
}
|
||||
|
||||
public FilteringWebHandler(List<GlobalFilter> globalFilters, boolean routeFilterCacheEnabled) {
|
||||
this.globalFilters = loadFilters(globalFilters);
|
||||
this.routeFilterCacheEnabled = routeFilterCacheEnabled;
|
||||
}
|
||||
|
||||
/* for testing */ ConcurrentHashMap<Route, List<GatewayFilter>> getRouteFilterMap() {
|
||||
return routeFilterMap;
|
||||
}
|
||||
|
||||
private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
|
||||
@@ -76,20 +93,17 @@ public class FilteringWebHandler implements WebHandler {
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: relocate @EventListener(RefreshRoutesEvent.class) void handleRefresh() {
|
||||
* this.combinedFiltersForRoute.clear();
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(RefreshRoutesEvent event) {
|
||||
if (this.routeFilterCacheEnabled) {
|
||||
routeFilterMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
|
||||
List<GatewayFilter> gatewayFilters = route.getFilters();
|
||||
|
||||
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
|
||||
combined.addAll(gatewayFilters);
|
||||
// TODO: needed or cached?
|
||||
AnnotationAwareOrderComparator.sort(combined);
|
||||
List<GatewayFilter> combined = getCombinedFilters(route);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sorted gatewayFilterFactories: " + combined);
|
||||
@@ -98,6 +112,23 @@ public class FilteringWebHandler implements WebHandler {
|
||||
return new DefaultGatewayFilterChain(combined).filter(exchange);
|
||||
}
|
||||
|
||||
protected List<GatewayFilter> getCombinedFilters(Route route) {
|
||||
if (this.routeFilterCacheEnabled) {
|
||||
return routeFilterMap.computeIfAbsent(route, this::getAllFilters);
|
||||
}
|
||||
else {
|
||||
return getAllFilters(route);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<GatewayFilter> getAllFilters(Route route) {
|
||||
List<GatewayFilter> gatewayFilters = route.getFilters();
|
||||
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
|
||||
combined.addAll(gatewayFilters);
|
||||
AnnotationAwareOrderComparator.sort(combined);
|
||||
return combined;
|
||||
}
|
||||
|
||||
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
|
||||
|
||||
private final int index;
|
||||
|
||||
@@ -46,7 +46,12 @@ public class RouteRefreshListener implements ApplicationListener<ApplicationEven
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof ContextRefreshedEvent) {
|
||||
ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
|
||||
if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) {
|
||||
boolean isManagementCtxt = WebServerApplicationContext
|
||||
.hasServerNamespace(refreshedEvent.getApplicationContext(), "management");
|
||||
boolean isLoadBalancerCtxt = refreshedEvent.getApplicationContext().getDisplayName() != null
|
||||
&& refreshedEvent.getApplicationContext().getDisplayName().startsWith("LoadBalancerClientFactory-");
|
||||
|
||||
if (!isManagementCtxt && !isLoadBalancerCtxt) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.handler;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
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.http.MediaType;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT,
|
||||
properties = { "spring.cloud.gateway.route-filter-cache-enabled=true",
|
||||
"management.endpoint.gateway.enabled=true", "management.endpoints.web.exposure.include=*",
|
||||
"spring.cloud.gateway.actuator.verbose.enabled=true" })
|
||||
@DirtiesContext
|
||||
public class FilteringWebHandlerCacheEnabledIntegrationTests extends BaseWebClientTests {
|
||||
|
||||
@Autowired
|
||||
private FilteringWebHandler webHandler;
|
||||
|
||||
@Test
|
||||
public void filteringWebHandlerCacheEnabledWorks() {
|
||||
// prime the cache
|
||||
callRoute("/get");
|
||||
assertThat(webHandler.getRouteFilterMap()).hasSize(1);
|
||||
|
||||
callRoute("/anything/testRoute1");
|
||||
|
||||
assertThat(webHandler.getRouteFilterMap()).hasSize(2);
|
||||
|
||||
RouteDefinition testRouteDefinition = new RouteDefinition();
|
||||
testRouteDefinition.setId("testRoute2");
|
||||
testRouteDefinition.setUri(URI.create("lb://testservice"));
|
||||
|
||||
FilterDefinition filterDefinition = new FilterDefinition("PrefixPath=/httpbin");
|
||||
testRouteDefinition.getFilters().add(filterDefinition);
|
||||
|
||||
PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition("Path=/anything/testRoute2");
|
||||
testRouteDefinition.setPredicates(Arrays.asList(hostRoutePredicateDefinition));
|
||||
|
||||
testClient.post()
|
||||
.uri("http://localhost:" + port + "/actuator/gateway/routes/testRoute2")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.body(BodyInserters.fromValue(testRouteDefinition))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isCreated();
|
||||
|
||||
testClient.post()
|
||||
.uri("http://localhost:" + port + "/actuator/gateway/refresh")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
|
||||
callRoute("/get");
|
||||
callRoute("/anything/testRoute1");
|
||||
callRoute("/anything/testRoute2");
|
||||
|
||||
assertThat(webHandler.getRouteFilterMap()).hasSize(3);
|
||||
}
|
||||
|
||||
private void callRoute(String uri) {
|
||||
testClient.mutate()
|
||||
.responseTimeout(Duration.ofMinutes(5))
|
||||
.build()
|
||||
.get()
|
||||
.uri(uri)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@Import(DefaultTestConfig.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("get_route", r -> r.path("/get").filters(f -> f.prefixPath("/httpbin")).uri("lb://testservice"))
|
||||
.route("testRoute1",
|
||||
r -> r.path("/anything/testRoute1")
|
||||
.filters(f -> f.prefixPath("/httpbin"))
|
||||
.uri("lb://testservice"))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user