Allow to deactivate global cache (#2895)

* Allow to deactivate global cache

* Update cache tests

* Update cache docs

* Rollback rename of global cache filter
This commit is contained in:
Marta Medio
2023-03-27 18:14:17 +02:00
committed by GitHub
parent 162863020d
commit bc57f129e4
6 changed files with 170 additions and 50 deletions

View File

@@ -1044,7 +1044,7 @@ public class GRPCLocalConfiguration {
}
----
[[local-cache-response-filter]]
=== The `LocalResponseCache` `GatewayFilter` Factory
This filter allows caching the response body and headers to follow these rules:
@@ -1054,9 +1054,9 @@ This filter allows caching the response body and headers to follow these rules:
* Response data is not cached if `Cache-Control` header does not allow it (`no-store` present in the request or `no-store` or `private` present in the response).
* If the response is already cached and a new request is performed with no-cache value in `Cache-Control` header, it returns a bodiless response with 304 (Not Modified).
This filter (which configures the local response cache per route) is available only if the <<local-cache-response-global-filter, local response global cache>> is enabled.
This filter configures the local response cache per route and is available only if the `spring.cloud.gateway.filter.local-response-cache.enabled` property is enabled. And a <<local-cache-response-global-filter, local response cache configured globally>> is also available as feature.
It accepts the first parameter to override the time to expire a cache entry (expressed in `s` for seconds, `m` for minutes, and `h` for hours) and a second parameter to set the maximum size of the cache to evict entries for this route (KB, MB, or GB).
It accepts the first parameter to override the time to expire a cache entry (expressed in `s` for seconds, `m` for minutes, and `h` for hours) and a second parameter to set the maximum size of the cache to evict entries for this route (`KB`, `MB`, or `GB`).
The following listing shows how to add local response cache `GatewayFilter`:
@@ -1090,11 +1090,16 @@ spring:
filters:
- LocalResponseCache=30m,500MB
----
====
NOTE: This filter also automatically calculates the `max-age` value in the HTTP `Cache-Control` header.
Only if `max-age` is present on the original response is the value rewritten with the number of seconds set in the `timeToLive` configuration parameter.
In consecutive calls, this value is recalculated with the number of seconds left until the response expires.
====
NOTE: To enable this feature, add `com.github.ben-manes.caffeine:caffeine` and `spring-boot-starter-cache` as project dependencies.
WARNING: If your project creates custom `CacheManager` beans, it will either need to be marked with `@Primary` or injected using `@Qualifier`.
=== The `MapRequestHeader` `GatewayFilter` Factory
@@ -2166,7 +2171,12 @@ NOTE: To enable the prometheus endpoint, add `micrometer-registry-prometheus` as
[[local-cache-response-global-filter]]
=== The Local Response Cache Filter
The `LocalResponseCache` runs if its associated property is enabled (`spring.cloud.gateway.filter.local-response-cache.enabled`) and activates a local cache using Caffeine for all responses that meet the following criteria:
The `LocalResponseCache` runs if associated properties are enabled:
* `spring.cloud.gateway.global-filter.local-response-cache.enabled`: Activates the global cache for all routes
* `spring.cloud.gateway.filter.local-response-cache.enabled`: Activates the associated filter to use at route level
This feature enables a local cache using Caffeine for all responses that meet the following criteria:
* The request is a bodiless GET.
* The response has one of the following status codes: HTTP 200 (OK), HTTP 206 (Partial Content), or HTTP 301 (Moved Permanently).
@@ -2175,7 +2185,7 @@ The `LocalResponseCache` runs if its associated property is enabled (`spring.clo
It accepts two configuration parameters:
* `spring.cloud.gateway.filter.local-response-cache.size`: Sets the maximum size of the cache to evict entries for this route (in KB, MB and GB).
* `spring.cloud.gateway.filter.local-response-cache.timeToLive` Sets the time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).
* `spring.cloud.gateway.filter.local-response-cache.time-to-live` Sets the time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).
If none of these parameters are configured but the global filter is enabled, by default, it configures 5 minutes of time to live for the cached response.
@@ -2183,6 +2193,8 @@ This filter also implements the automatic calculation of the `max-age` value in
If `max-age` is present on the original response, the value is rewritten with the number of seconds set in the `timeToLive` configuration parameter.
In subsequent calls, this value is recalculated with the number of seconds left until the response expires.
Setting `spring.cloud.gateway.global-filter.local-response-cache.enabled` to `false` deactivate the local response cache for all routes, the <<local-cache-response-filter, LocalResponseCache filter>> allows to use this functionality at route level.
NOTE: To enable this feature, add `com.github.ben-manes.caffeine:caffeine` and `spring-boot-starter-cache` as project dependencies.
WARNING: If your project creates custom `CacheManager` beans, it will either need to be marked with `@Primary` or injected using `@Qualifier`.

View File

@@ -31,6 +31,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter;
import org.springframework.cloud.gateway.filter.factory.cache.GlobalLocalResponseCacheGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties;
@@ -48,7 +49,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ LocalResponseCacheProperties.class })
@ConditionalOnClass({ Weigher.class, Caffeine.class, CaffeineCacheManager.class })
@Conditional(LocalResponseCacheAutoConfiguration.OnGlobalLocalResponseCacheCondition.class)
@ConditionalOnEnabledFilter(LocalResponseCacheGatewayFilterFactory.class)
public class LocalResponseCacheAutoConfiguration {
private static final Log LOGGER = LogFactory.getLog(LocalResponseCacheAutoConfiguration.class);
@@ -58,6 +59,7 @@ public class LocalResponseCacheAutoConfiguration {
/* for testing */ static final String RESPONSE_CACHE_MANAGER_NAME = "gatewayCacheManager";
@Bean
@Conditional(LocalResponseCacheAutoConfiguration.OnGlobalLocalResponseCacheCondition.class)
public GlobalLocalResponseCacheGatewayFilter globalLocalResponseCacheGatewayFilter(
ResponseCacheManagerFactory responseCacheManagerFactory,
@Qualifier(RESPONSE_CACHE_MANAGER_NAME) CacheManager cacheManager,
@@ -66,13 +68,16 @@ public class LocalResponseCacheAutoConfiguration {
properties.getTimeToLive());
}
@Bean(name = RESPONSE_CACHE_MANAGER_NAME)
@Conditional(LocalResponseCacheAutoConfiguration.OnGlobalLocalResponseCacheCondition.class)
public CacheManager gatewayCacheManager(LocalResponseCacheProperties cacheProperties) {
return createGatewayCacheManager(cacheProperties);
}
@Bean
public LocalResponseCacheGatewayFilterFactory localResponseCacheGatewayFilterFactory(
ResponseCacheManagerFactory responseCacheManagerFactory,
@Qualifier(RESPONSE_CACHE_MANAGER_NAME) CacheManager cacheManager,
LocalResponseCacheProperties properties) {
return new LocalResponseCacheGatewayFilterFactory(responseCacheManagerFactory, responseCache(cacheManager),
properties.getTimeToLive());
ResponseCacheManagerFactory responseCacheManagerFactory, LocalResponseCacheProperties properties) {
return new LocalResponseCacheGatewayFilterFactory(responseCacheManagerFactory, properties.getTimeToLive());
}
@Bean
@@ -85,11 +90,6 @@ public class LocalResponseCacheAutoConfiguration {
return new CacheKeyGenerator();
}
@Bean(name = RESPONSE_CACHE_MANAGER_NAME)
public CacheManager gatewayCacheManager(LocalResponseCacheProperties cacheProperties) {
return createGatewayCacheManager(cacheProperties);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static CaffeineCacheManager createGatewayCacheManager(LocalResponseCacheProperties cacheProperties) {
Caffeine caffeine = Caffeine.newBuilder();
@@ -129,6 +129,12 @@ public class LocalResponseCacheAutoConfiguration {
}
@ConditionalOnProperty(name = "spring.cloud.gateway.global-filter.local-response-cache.enabled",
havingValue = "true", matchIfMissing = true)
static class OnGlobalLocalResponseCachePropertyEnabled {
}
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.cloud.gateway.filter.factory.cache;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.Cache;
@@ -50,17 +49,14 @@ public class LocalResponseCacheGatewayFilterFactory
*/
public static final String LOCAL_RESPONSE_CACHE_FILTER_APPLIED = "LocalResponseCacheGatewayFilter-Applied";
private final Cache globalCache;
ResponseCacheManagerFactory cacheManagerFactory;
Duration configuredTimeToLive;
public LocalResponseCacheGatewayFilterFactory(ResponseCacheManagerFactory cacheManagerFactory, Cache globalCache,
public LocalResponseCacheGatewayFilterFactory(ResponseCacheManagerFactory cacheManagerFactory,
Duration configuredTimeToLive) {
super(RouteCacheConfiguration.class);
this.cacheManagerFactory = cacheManagerFactory;
this.globalCache = globalCache;
this.configuredTimeToLive = configuredTimeToLive;
}
@@ -68,19 +64,10 @@ public class LocalResponseCacheGatewayFilterFactory
public GatewayFilter apply(RouteCacheConfiguration config) {
LocalResponseCacheProperties cacheProperties = mapRouteCacheConfig(config);
if (shouldUseGlobalCacheConfiguration(config)) {
return new ResponseCacheGatewayFilter(cacheManagerFactory.create(globalCache, configuredTimeToLive));
}
else {
Cache routeCache = LocalResponseCacheAutoConfiguration.createGatewayCacheManager(cacheProperties)
.getCache(config.getRouteId() + "-cache");
return new ResponseCacheGatewayFilter(
cacheManagerFactory.create(routeCache, cacheProperties.getTimeToLive()));
}
}
Cache routeCache = LocalResponseCacheAutoConfiguration.createGatewayCacheManager(cacheProperties)
.getCache(config.getRouteId() + "-cache");
return new ResponseCacheGatewayFilter(cacheManagerFactory.create(routeCache, cacheProperties.getTimeToLive()));
private boolean shouldUseGlobalCacheConfiguration(RouteCacheConfiguration config) {
return Objects.isNull(config.getTimeToLive()) && Objects.isNull(config.getSize());
}
private LocalResponseCacheProperties mapRouteCacheConfig(RouteCacheConfiguration config) {

View File

@@ -39,7 +39,7 @@
{
"name": "spring.cloud.gateway.filter.local-response-cache.enabled",
"type": "java.lang.Boolean",
"description": "Enables the local-response-cache filter for all routes, it allows to add a specific configuration at route level using LocalResponseCache filter.",
"description": "Enables the local-response-cache filter.",
"defaultValue": "false"
},
{
@@ -49,7 +49,7 @@
"defaultValue": "5m"
},
{
"name": "spring.cloud.gateway.filter.local-response-cache.timeToLive",
"name": "spring.cloud.gateway.filter.local-response-cache.time-to-live",
"type": "java.time.Duration",
"description": "Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours)."
},
@@ -281,6 +281,12 @@
"description": "Enables the load-balancer-client global filter.",
"defaultValue": "true"
},
{
"name": "spring.cloud.gateway.global-filter.local-response-cache.enabled",
"type": "java.lang.Boolean",
"description": "Enables the local-response-cache filter for all routes, it allows to add a specific configuration at route level using LocalResponseCache filter.",
"defaultValue": "true"
},
{
"name": "spring.cloud.gateway.predicate.after.enabled",
"type": "java.lang.Boolean",

View File

@@ -56,17 +56,6 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe
private static final String CUSTOM_HEADER = "X-Custom-Date";
@Test
void shouldGlobalCacheResponseWhenRouteDoesNotHaveFilter() {
String uri = "/" + UUID.randomUUID() + "/global-cache/headers";
testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange()
.expectBody().jsonPath("$.headers." + CUSTOM_HEADER);
testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange()
.expectBody().jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("1");
}
@Test
void shouldNotCacheResponseWhenGetRequestHasBody() {
String uri = "/" + UUID.randomUUID() + "/cache/headers";
@@ -252,9 +241,6 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe
@Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("global_local_response_cache_java_test",
r -> r.path("/{namespace}/global-cache/**").and().host("{sub}.localresponsecache.org")
.filters(f -> f.stripPrefix(2).prefixPath("/httpbin")).uri(uri))
.route("local_response_cache_java_test",
r -> r.path("/{namespace}/cache/**").and().host("{sub}.localresponsecache.org")
.filters(f -> f.stripPrefix(2).prefixPath("/httpbin")

View File

@@ -0,0 +1,123 @@
/*
* 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.filter.factory.cache;
import java.util.UUID;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
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.ActiveProfiles;
/**
* @author Ignacio Lozano
* @author Marta Medio
*/
@DirtiesContext
@ActiveProfiles(profiles = "local-cache-filter")
public class LocalResponseCacheGlobalFilterTests {
private static final String CUSTOM_HEADER = "X-Custom-Date";
@Nested
@SpringBootTest(
properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true",
"spring.cloud.gateway.global-filter.local-response-cache.enabled=false" },
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GlobalCacheNotEnabled extends BaseWebClientTests {
@Test
void shouldNotCacheResponseWhenGlobalIsNotEnabled() {
String uri = "/" + UUID.randomUUID() + "/global-cache-deactivated/headers";
testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange()
.expectBody().jsonPath("$.headers." + CUSTOM_HEADER);
testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange()
.expectBody().jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2");
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig {
@Value("${test.uri}")
String uri;
@Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("global_local_response_cache_deactivated_java_test",
r -> r.path("/{namespace}/global-cache-deactivated/**").and()
.host("{sub}.localresponsecache.org")
.filters(f -> f.stripPrefix(2).prefixPath("/httpbin")).uri(uri))
.build();
}
}
}
@Nested
@SpringBootTest(properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true" },
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GlobalCacheEnabled extends BaseWebClientTests {
@Test
void shouldGlobalCacheResponseWhenRouteDoesNotHaveFilter() {
String uri = "/" + UUID.randomUUID() + "/global-cache/headers";
testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange()
.expectBody().jsonPath("$.headers." + CUSTOM_HEADER);
testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange()
.expectBody().jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("1");
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
public static class TestConfig {
@Value("${test.uri}")
String uri;
@Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("global_local_response_cache_java_test",
r -> r.path("/{namespace}/global-cache/**").and().host("{sub}.localresponsecache.org")
.filters(f -> f.stripPrefix(2).prefixPath("/httpbin")).uri(uri))
.build();
}
}
}
}