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:
@@ -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`.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user