Skip to content

Commit 2de7542

Browse files
committed
Merge branch 'FantasticDream/main'
2 parents 031e249 + 389f950 commit 2de7542

File tree

5 files changed

+188
-14
lines changed

5 files changed

+188
-14
lines changed

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,8 @@ public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publi
272272

273273
@Bean
274274
@ConditionalOnMissingBean
275-
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
276-
return new FilteringWebHandler(globalFilters);
275+
public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters, GatewayProperties properties) {
276+
return new FilteringWebHandler(globalFilters, properties.isRouteFilterCacheEnabled());
277277
}
278278

279279
@Bean

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ public class GatewayProperties {
6868
*/
6969
private boolean failOnRouteDefinitionError = true;
7070

71+
/**
72+
* Enables the route filter cache, defaults to false.
73+
*/
74+
private boolean routeFilterCacheEnabled = false;
75+
76+
public boolean isRouteFilterCacheEnabled() {
77+
return routeFilterCacheEnabled;
78+
}
79+
80+
public void setRouteFilterCacheEnabled(boolean routeFilterCacheEnabled) {
81+
this.routeFilterCacheEnabled = routeFilterCacheEnabled;
82+
}
83+
7184
public List<RouteDefinition> getRoutes() {
7285
return routes;
7386
}
@@ -109,6 +122,7 @@ public String toString() {
109122
.append("defaultFilters", defaultFilters)
110123
.append("streamingMediaTypes", streamingMediaTypes)
111124
.append("failOnRouteDefinitionError", failOnRouteDefinitionError)
125+
.append("routeFilterCacheEnabled", routeFilterCacheEnabled)
112126
.toString();
113127

114128
}

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import java.util.concurrent.ConcurrentHashMap;
2122
import java.util.stream.Collectors;
2223

2324
import org.apache.commons.logging.Log;
2425
import org.apache.commons.logging.LogFactory;
2526
import reactor.core.publisher.Mono;
2627

28+
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
2729
import org.springframework.cloud.gateway.filter.GatewayFilter;
2830
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
2931
import org.springframework.cloud.gateway.filter.GlobalFilter;
3032
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
3133
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
3234
import org.springframework.cloud.gateway.route.Route;
35+
import org.springframework.context.ApplicationListener;
3336
import org.springframework.core.DecoratingProxy;
3437
import org.springframework.core.Ordered;
3538
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -49,14 +52,28 @@
4952
* @author Yuxin Wang
5053
* @since 0.1
5154
*/
52-
public class FilteringWebHandler implements WebHandler {
55+
public class FilteringWebHandler implements WebHandler, ApplicationListener<RefreshRoutesEvent> {
5356

5457
protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);
5558

5659
private final List<GatewayFilter> globalFilters;
5760

61+
private final ConcurrentHashMap<Route, List<GatewayFilter>> routeFilterMap = new ConcurrentHashMap();
62+
63+
private final boolean routeFilterCacheEnabled;
64+
65+
@Deprecated
5866
public FilteringWebHandler(List<GlobalFilter> globalFilters) {
67+
this(globalFilters, false);
68+
}
69+
70+
public FilteringWebHandler(List<GlobalFilter> globalFilters, boolean routeFilterCacheEnabled) {
5971
this.globalFilters = loadFilters(globalFilters);
72+
this.routeFilterCacheEnabled = routeFilterCacheEnabled;
73+
}
74+
75+
/* for testing */ ConcurrentHashMap<Route, List<GatewayFilter>> getRouteFilterMap() {
76+
return routeFilterMap;
6077
}
6178

6279
private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
@@ -76,20 +93,17 @@ private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
7693
}).collect(Collectors.toList());
7794
}
7895

79-
/*
80-
* TODO: relocate @EventListener(RefreshRoutesEvent.class) void handleRefresh() {
81-
* this.combinedFiltersForRoute.clear();
82-
*/
96+
@Override
97+
public void onApplicationEvent(RefreshRoutesEvent event) {
98+
if (this.routeFilterCacheEnabled) {
99+
routeFilterMap.clear();
100+
}
101+
}
83102

84103
@Override
85104
public Mono<Void> handle(ServerWebExchange exchange) {
86105
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
87-
List<GatewayFilter> gatewayFilters = route.getFilters();
88-
89-
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
90-
combined.addAll(gatewayFilters);
91-
// TODO: needed or cached?
92-
AnnotationAwareOrderComparator.sort(combined);
106+
List<GatewayFilter> combined = getCombinedFilters(route);
93107

94108
if (logger.isDebugEnabled()) {
95109
logger.debug("Sorted gatewayFilterFactories: " + combined);
@@ -98,6 +112,23 @@ public Mono<Void> handle(ServerWebExchange exchange) {
98112
return new DefaultGatewayFilterChain(combined).filter(exchange);
99113
}
100114

115+
protected List<GatewayFilter> getCombinedFilters(Route route) {
116+
if (this.routeFilterCacheEnabled) {
117+
return routeFilterMap.computeIfAbsent(route, this::getAllFilters);
118+
}
119+
else {
120+
return getAllFilters(route);
121+
}
122+
}
123+
124+
protected List<GatewayFilter> getAllFilters(Route route) {
125+
List<GatewayFilter> gatewayFilters = route.getFilters();
126+
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
127+
combined.addAll(gatewayFilters);
128+
AnnotationAwareOrderComparator.sort(combined);
129+
return combined;
130+
}
131+
101132
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
102133

103134
private final int index;

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteRefreshListener.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ public RouteRefreshListener(ApplicationEventPublisher publisher) {
4646
public void onApplicationEvent(ApplicationEvent event) {
4747
if (event instanceof ContextRefreshedEvent) {
4848
ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
49-
if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) {
49+
boolean isManagementCtxt = WebServerApplicationContext
50+
.hasServerNamespace(refreshedEvent.getApplicationContext(), "management");
51+
boolean isLoadBalancerCtxt = refreshedEvent.getApplicationContext().getDisplayName() != null
52+
&& refreshedEvent.getApplicationContext().getDisplayName().startsWith("LoadBalancerClientFactory-");
53+
54+
if (!isManagementCtxt && !isLoadBalancerCtxt) {
5055
reset();
5156
}
5257
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2013-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.handler;
18+
19+
import java.net.URI;
20+
import java.time.Duration;
21+
import java.util.Arrays;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.SpringBootConfiguration;
27+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
28+
import org.springframework.boot.test.context.SpringBootTest;
29+
import org.springframework.cloud.gateway.filter.FilterDefinition;
30+
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
31+
import org.springframework.cloud.gateway.route.RouteDefinition;
32+
import org.springframework.cloud.gateway.route.RouteLocator;
33+
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
34+
import org.springframework.cloud.gateway.test.BaseWebClientTests;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Import;
37+
import org.springframework.http.MediaType;
38+
import org.springframework.test.annotation.DirtiesContext;
39+
import org.springframework.web.reactive.function.BodyInserters;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
43+
44+
@SpringBootTest(webEnvironment = RANDOM_PORT,
45+
properties = { "spring.cloud.gateway.route-filter-cache-enabled=true",
46+
"management.endpoint.gateway.enabled=true", "management.endpoints.web.exposure.include=*",
47+
"spring.cloud.gateway.actuator.verbose.enabled=true" })
48+
@DirtiesContext
49+
public class FilteringWebHandlerCacheEnabledIntegrationTests extends BaseWebClientTests {
50+
51+
@Autowired
52+
private FilteringWebHandler webHandler;
53+
54+
@Test
55+
public void filteringWebHandlerCacheEnabledWorks() {
56+
// prime the cache
57+
callRoute("/get");
58+
assertThat(webHandler.getRouteFilterMap()).hasSize(1);
59+
60+
callRoute("/anything/testRoute1");
61+
62+
assertThat(webHandler.getRouteFilterMap()).hasSize(2);
63+
64+
RouteDefinition testRouteDefinition = new RouteDefinition();
65+
testRouteDefinition.setId("testRoute2");
66+
testRouteDefinition.setUri(URI.create("lb://testservice"));
67+
68+
FilterDefinition filterDefinition = new FilterDefinition("PrefixPath=/httpbin");
69+
testRouteDefinition.getFilters().add(filterDefinition);
70+
71+
PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition("Path=/anything/testRoute2");
72+
testRouteDefinition.setPredicates(Arrays.asList(hostRoutePredicateDefinition));
73+
74+
testClient.post()
75+
.uri("http://localhost:" + port + "/actuator/gateway/routes/testRoute2")
76+
.accept(MediaType.APPLICATION_JSON)
77+
.body(BodyInserters.fromValue(testRouteDefinition))
78+
.exchange()
79+
.expectStatus()
80+
.isCreated();
81+
82+
testClient.post()
83+
.uri("http://localhost:" + port + "/actuator/gateway/refresh")
84+
.exchange()
85+
.expectStatus()
86+
.isOk();
87+
88+
callRoute("/get");
89+
callRoute("/anything/testRoute1");
90+
callRoute("/anything/testRoute2");
91+
92+
assertThat(webHandler.getRouteFilterMap()).hasSize(3);
93+
}
94+
95+
private void callRoute(String uri) {
96+
testClient.mutate()
97+
.responseTimeout(Duration.ofMinutes(5))
98+
.build()
99+
.get()
100+
.uri(uri)
101+
.exchange()
102+
.expectStatus()
103+
.isOk();
104+
}
105+
106+
@EnableAutoConfiguration
107+
@SpringBootConfiguration
108+
@Import(DefaultTestConfig.class)
109+
public static class TestConfig {
110+
111+
@Bean
112+
RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
113+
return builder.routes()
114+
.route("get_route", r -> r.path("/get").filters(f -> f.prefixPath("/httpbin")).uri("lb://testservice"))
115+
.route("testRoute1",
116+
r -> r.path("/anything/testRoute1")
117+
.filters(f -> f.prefixPath("/httpbin"))
118+
.uri("lb://testservice"))
119+
.build();
120+
}
121+
122+
}
123+
124+
}

0 commit comments

Comments
 (0)