sgibb
12 months ago
18 changed files with 638 additions and 142 deletions
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
/* |
||||
* 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 org.springframework.http.server.reactive.ServerHttpRequest; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
public final class LocalResponseCacheUtils { |
||||
|
||||
private LocalResponseCacheUtils() { |
||||
} |
||||
|
||||
public static boolean isNoCacheRequest(ServerHttpRequest request) { |
||||
String cacheControl = request.getHeaders().getCacheControl(); |
||||
return StringUtils.hasText(cacheControl) && cacheControl.matches(".*(\s|,|^)no-cache(\\s|,|$).*"); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* 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.postprocessor; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
public class SetCacheDirectivesByMaxAgeAfterCacheExchangeMutator implements AfterCacheExchangeMutator { |
||||
|
||||
final Pattern MAX_AGE_PATTERN = Pattern.compile("(?:,|^)\\s*max-age=(\\d+)"); |
||||
|
||||
@Override |
||||
public void accept(ServerWebExchange exchange, CachedResponse cachedResponse) { |
||||
Optional<Integer> maxAge = Optional.ofNullable(exchange.getResponse().getHeaders().getCacheControl()) |
||||
.map(MAX_AGE_PATTERN::matcher).filter(Matcher::find).map(matcher -> matcher.group(1)) |
||||
.map(Integer::parseInt); |
||||
|
||||
if (maxAge.isPresent()) { |
||||
if (maxAge.get() > 0) { |
||||
removeNoCacheHeaders(exchange); |
||||
} |
||||
else { |
||||
keepNoCacheHeaders(exchange); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void keepNoCacheHeaders(ServerWebExchange exchange) { |
||||
// at least it contains 'max-age' so we can append items with commas safely
|
||||
String cacheControl = exchange.getResponse().getHeaders().getCacheControl(); |
||||
StringBuilder newCacheControl = new StringBuilder(cacheControl); |
||||
|
||||
if (!cacheControl.contains("no-cache")) { |
||||
newCacheControl.append(",no-cache"); |
||||
} |
||||
|
||||
if (!cacheControl.contains("must-revalidate")) { |
||||
newCacheControl.append(",must-revalidate"); |
||||
} |
||||
exchange.getResponse().getHeaders().setCacheControl(newCacheControl.toString()); |
||||
} |
||||
|
||||
private void removeNoCacheHeaders(ServerWebExchange exchange) { |
||||
String cacheControl = exchange.getResponse().getHeaders().getCacheControl(); |
||||
List<String> cacheControlValues = Arrays.asList(cacheControl.split("\\s*,\\s*")); |
||||
|
||||
String newCacheControl = cacheControlValues.stream() |
||||
.filter(s -> !s.matches("must-revalidate|no-cache|no-store")).collect(Collectors.joining(",")); |
||||
exchange.getResponse().getHeaders().setCacheControl(newCacheControl); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* 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 org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.ValueSource; |
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
class LocalResponseCacheUtilsTests { |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "", "no-store", "no-store, wrong-no-cache", "s-no-cache" }) |
||||
void shouldNotIdentifyRequestAsNoCacheRequest(String cacheControl) { |
||||
MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this") |
||||
.header("Cache-Control", cacheControl).build(); |
||||
|
||||
boolean result = LocalResponseCacheUtils.isNoCacheRequest(httpRequest); |
||||
|
||||
assertThat(result).isFalse(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "no-cache", "s-no-cache, no-cache", "private,no-cache", " no-cache", "no-cache " }) |
||||
void shouldIdentifyRequestAsNoCacheRequest(String cacheControl) { |
||||
MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this") |
||||
.header("Cache-Control", cacheControl).build(); |
||||
|
||||
boolean result = LocalResponseCacheUtils.isNoCacheRequest(httpRequest); |
||||
|
||||
assertThat(result).isTrue(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
/* |
||||
* 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.postprocessor; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.ValueSource; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest; |
||||
import org.springframework.mock.http.server.reactive.MockServerHttpResponse; |
||||
import org.springframework.mock.web.server.MockServerWebExchange; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
class SetCacheDirectivesByMaxAgeAfterCacheExchangeMutatorTests { |
||||
|
||||
private MockServerWebExchange inputExchange; |
||||
|
||||
private SetCacheDirectivesByMaxAgeAfterCacheExchangeMutator toTest; |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
HttpHeaders responseHeaders = new HttpHeaders(); |
||||
responseHeaders.setCacheControl("max-age=1234"); |
||||
MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this").build(); |
||||
|
||||
inputExchange = MockServerWebExchange.from(httpRequest); |
||||
MockServerHttpResponse httpResponse = inputExchange.getResponse(); |
||||
httpResponse.setStatusCode(HttpStatus.OK); |
||||
httpResponse.getHeaders().putAll(responseHeaders); |
||||
|
||||
toTest = new SetCacheDirectivesByMaxAgeAfterCacheExchangeMutator(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "", "ETag=1234-123", "s-max-age=20" }) |
||||
void doesntModifyCacheControlWhenNoMaxAge(String cacheControlValue) { |
||||
inputExchange.getResponse().getHeaders().setCacheControl(cacheControlValue); |
||||
|
||||
toTest.accept(inputExchange, null); |
||||
|
||||
assertThat(inputExchange.getResponse().getHeaders().getCacheControl()).isEqualTo(cacheControlValue); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource( |
||||
strings = { "max-age=0", "ETag=1234-123,max-age=0", "s-max-age=20,max-age=0", "ETag=with-spaces, max-age=0", |
||||
"ETag=with-spaces, max-age=0,Expires=123123123", " max-age=0, ETag=with-spaces" }) |
||||
void directivesNoCacheAreAddedWhenMaxAgeIsZero(String cacheControlValue) { |
||||
inputExchange.getResponse().getHeaders().setCacheControl(cacheControlValue); |
||||
|
||||
toTest.accept(inputExchange, null); |
||||
|
||||
assertThat(inputExchange.getResponse().getHeaders().getCacheControl()).doesNotContainPattern(",\\s*,") |
||||
.contains("max-age=0").contains("must-revalidate").contains("no-cache"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ValueSource(strings = { "max-age=10,must-revalidate", "must-revalidate,ETag=1234-123,max-age=10", |
||||
"must-revalidate,s-max-age=0,max-age=10", " max-age=10, must-revalidate,ETag=with-spaces", |
||||
"ETag=with-spaces,must-revalidate, max-age=10,Expires=123123123", |
||||
"ETag=with-spaces,must-revalidate, max-age=10", "max-age=10,no-store" }) |
||||
void directivesNoCacheAreRemovedWhenMaxAgePositive(String cacheControlValue) { |
||||
inputExchange.getResponse().getHeaders().setCacheControl(cacheControlValue); |
||||
|
||||
toTest.accept(inputExchange, null); |
||||
|
||||
assertThat(inputExchange.getResponse().getHeaders().getCacheControl()).contains("max-age=10") |
||||
.doesNotContainPattern(",\\s*,").doesNotContain("no-store").doesNotContain("must-revalidate") |
||||
.doesNotContain("no-cache"); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue