Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.gateway.config;

import java.util.List;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Weigher;
import org.apache.commons.logging.Log;
Expand All @@ -36,6 +38,10 @@
import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheUtils;
import org.springframework.cloud.gateway.filter.factory.cache.ResponseCacheManagerFactory;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CookiesKeyValueGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.HeaderKeyValueGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.KeyValueGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.UriKeyValueGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -85,8 +91,25 @@ public ResponseCacheManagerFactory responseCacheManagerFactory(CacheKeyGenerator
}

@Bean
public CacheKeyGenerator cacheKeyGenerator() {
return new CacheKeyGenerator();
public UriKeyValueGenerator uriKeyValueGenerator(LocalResponseCacheProperties cacheProperties) {
return new UriKeyValueGenerator(cacheProperties.getEnableUriKeyGenerator());
}

@Bean
public HeaderKeyValueGenerator headerKeyValueGenerator(LocalResponseCacheProperties cacheProperties) {
return new HeaderKeyValueGenerator(cacheProperties.getEnableHeaderKeyGenerator(), "Authorization", ";");
}

@Bean
public CookiesKeyValueGenerator cookiesKeyValueGenerator(LocalResponseCacheProperties cacheProperties) {
return new CookiesKeyValueGenerator(cacheProperties.getEnableCookiesKeyGenerator(), ";");
}

@Bean
public CacheKeyGenerator cacheKeyGenerator(UriKeyValueGenerator uriGenerator,
HeaderKeyValueGenerator headerGenerator, CookiesKeyValueGenerator cookiesGenerator) {
List<KeyValueGenerator> defaultGenerators = List.of(uriGenerator, headerGenerator, cookiesGenerator);
return new CacheKeyGenerator(defaultGenerators);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ public class LocalResponseCacheProperties {

private RequestOptions request = new RequestOptions();

private boolean enableCookiesKeyGenerator = true;

private boolean enableHeaderKeyGenerator = true;

private boolean enableUriKeyGenerator = true;

public DataSize getSize() {
return size;
}
Expand Down Expand Up @@ -74,10 +80,35 @@ public void setRequest(RequestOptions request) {
this.request = request;
}

public boolean getEnableCookiesKeyGenerator() {
return enableCookiesKeyGenerator;
}

public void setEnableCookiesKeyGenerator(boolean enableCookiesKeyGenerator) {
this.enableCookiesKeyGenerator = enableCookiesKeyGenerator;
}

public boolean getEnableHeaderKeyGenerator() {
return enableHeaderKeyGenerator;
}

public void setEnableHeaderKeyGenerator(boolean enableHeaderKeyGenerator) {
this.enableHeaderKeyGenerator = enableHeaderKeyGenerator;
}

public boolean getEnableUriKeyGenerator() {
return enableUriKeyGenerator;
}

public void setEnableUriKeyGenerator(boolean enableUriKeyGenerator) {
this.enableUriKeyGenerator = enableUriKeyGenerator;
}

@Override
public String toString() {
return "LocalResponseCacheProperties{" + "size=" + size + ", timeToLive=" + timeToLive + ", request=" + request
+ '}';
+ ", enableCookiesKeyGenerator=" + enableCookiesKeyGenerator + ", enableHeaderKeyGenerator="
+ enableHeaderKeyGenerator + ", enableUriKeyGenerator=" + enableUriKeyGenerator + '}';
}

public static class RequestOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
import java.util.List;
import java.util.stream.Stream;

import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;

/**
* @author Marta Medio
* @author Ignacio Lozano
* @author Simone Gerevini
* @author Dong Hyeon Lee
*/
public class CacheKeyGenerator {

Expand All @@ -41,11 +41,11 @@ public class CacheKeyGenerator {

private final ThreadLocal<MessageDigest> messageDigest;

/* for testing */ static final List<KeyValueGenerator> DEFAULT_KEY_VALUE_GENERATORS = List.of(
new UriKeyValueGenerator(), new HeaderKeyValueGenerator(HttpHeaders.AUTHORIZATION, KEY_SEPARATOR),
new CookiesKeyValueGenerator(KEY_SEPARATOR));
/* for testing */ static List<KeyValueGenerator> DEFAULT_KEY_VALUE_GENERATORS;

public CacheKeyGenerator(List<KeyValueGenerator> keyValueGeneratorList) {
DEFAULT_KEY_VALUE_GENERATORS = keyValueGeneratorList;

public CacheKeyGenerator() {
messageDigest = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("MD5");
Expand Down Expand Up @@ -73,17 +73,20 @@ public String generateKey(ServerHttpRequest request, List<String> varyHeaders) {

private Stream<KeyValueGenerator> getKeyValueGenerators(List<String> varyHeaders) {
return Stream.concat(DEFAULT_KEY_VALUE_GENERATORS.stream(),
varyHeaders.stream().sorted().map(header -> new HeaderKeyValueGenerator(header, ",")));
varyHeaders.stream().sorted().map(header -> new HeaderKeyValueGenerator(true, header, ",")));
}

private byte[] generateRawKey(ServerHttpRequest request, List<String> varyHeaders) {
Stream<KeyValueGenerator> keyValueGenerators = getKeyValueGenerators(varyHeaders);

final ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
keyValueGenerators.map(generator -> generator.apply(request)).map(String::getBytes).forEach(bytes -> {
byteOutputStream.writeBytes(bytes);
byteOutputStream.writeBytes(KEY_SEPARATOR_BYTES);
});
keyValueGenerators.filter(KeyValueGenerator::isEnabled)
.map(generator -> generator.apply(request))
.map(String::getBytes)
.forEach(bytes -> {
byteOutputStream.writeBytes(bytes);
byteOutputStream.writeBytes(KEY_SEPARATOR_BYTES);
});

return byteOutputStream.toByteArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@
/**
* @author Marta Medio
* @author Ignacio Lozano
* @author Dong Hyeon Lee
*/
class CookiesKeyValueGenerator implements KeyValueGenerator {
public class CookiesKeyValueGenerator implements KeyValueGenerator {

private final String valueSeparator;

CookiesKeyValueGenerator(String valueSeparator) {
private boolean enabled = true;

public CookiesKeyValueGenerator(boolean enabled, String valueSeparator) {
this.enabled = enabled;
this.valueSeparator = Objects.requireNonNull(valueSeparator);
}

Expand All @@ -51,4 +55,13 @@ public String getKeyValue(ServerHttpRequest request) {
return cookiesData;
}

@Override
public boolean isEnabled() {
return this.enabled;
}

@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@
/**
* @author Marta Medio
* @author Ignacio Lozano
* @author Dong Hyeon Lee
*/
class HeaderKeyValueGenerator implements KeyValueGenerator {
public class HeaderKeyValueGenerator implements KeyValueGenerator {

private final String header;

private final String valueSeparator;

HeaderKeyValueGenerator(String header, String valueSeparator) {
private boolean enabled = true;

public HeaderKeyValueGenerator(boolean enabled, String header, String valueSeparator) {
this.enabled = enabled;
this.valueSeparator = valueSeparator;
if (!StringUtils.hasText(header)) {
throw new IllegalArgumentException("The parameter cannot be empty or null");
Expand All @@ -60,4 +64,13 @@ private Stream<String> getHeaderValues(HttpHeaders headers) {
return value == null ? Stream.empty() : value.stream();
}

@Override
public boolean isEnabled() {
return this.enabled;
}

@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
*
* @author Marta Medio
* @author Ignacio Lozano
* @author Dong Hyeon Lee
*/
interface KeyValueGenerator {
public interface KeyValueGenerator {

/*
* Calls getKeyValue() and guards against null.
Expand All @@ -39,4 +40,8 @@ default String apply(ServerHttpRequest request) {

String getKeyValue(ServerHttpRequest request);

boolean isEnabled();

void setEnabled(boolean enabled);

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,28 @@
*
* @author Marta Medio
* @author Ignacio Lozano
* @author Dong Hyeon Lee
*/
public class UriKeyValueGenerator implements KeyValueGenerator {

private boolean enabled = true;

public UriKeyValueGenerator(boolean enabled) {
this.enabled = enabled;
}

@Override
public String getKeyValue(ServerHttpRequest request) {
return request.getURI().toString();
}

@Override
public boolean isEnabled() {
return this.enabled;
}

@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
import org.junit.jupiter.api.Test;

import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CookiesKeyValueGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.HeaderKeyValueGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.KeyValueGenerator;
import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.UriKeyValueGenerator;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
Expand All @@ -38,7 +42,10 @@
*/
class CacheKeyGeneratorTest {

final CacheKeyGenerator cacheKeyGenerator = new CacheKeyGenerator();
List<KeyValueGenerator> defaultGenerators = List.of(new UriKeyValueGenerator(true),
new HeaderKeyValueGenerator(true, "Authorization", ";"), new CookiesKeyValueGenerator(true, ";"));

final CacheKeyGenerator cacheKeyGenerator = new CacheKeyGenerator(defaultGenerators);

@Test
public void shouldGenerateSameKeyForSameUri() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.stream.Collectors;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.http.HttpCookie;
Expand All @@ -29,9 +30,15 @@

/**
* @author Ignacio Lozano
* @author Dong Hyeon Lee
*/
class DefaultKeyValueGeneratorTests {

@BeforeEach
void enableAllGenerators() {
CacheKeyGenerator.DEFAULT_KEY_VALUE_GENERATORS.forEach(generator -> generator.setEnabled(true));
}

@Test
void uriAuthorizationAndCookiesArePresent() {
String uri = "http://myuri";
Expand Down Expand Up @@ -72,8 +79,33 @@ void onlyUriPresent() {
assertThat(result).isEqualTo(uri + ";" + "" + ";" + "");
}

@Test
void isEnabledFalse() {
String uri = "http://myuri";
HttpHeaders headers = new HttpHeaders();
String authorization = "my-auth";
headers.set("Authorization", authorization);
String cookieName = "my-cookie";
String cookieValue = "cookie-value";
HttpCookie cookie = new HttpCookie(cookieName, cookieValue);
MockServerHttpRequest request = MockServerHttpRequest.get(uri).cookie(cookie).headers(headers).build();

String result = applyDisabled(request);

assertThat(result).isEqualTo("");
}

public String apply(ServerHttpRequest request) {
return CacheKeyGenerator.DEFAULT_KEY_VALUE_GENERATORS.stream()
.filter(KeyValueGenerator::isEnabled)
.map(generator -> generator.apply(request))
.collect(Collectors.joining(CacheKeyGenerator.KEY_SEPARATOR));
}

public String applyDisabled(ServerHttpRequest request) {
return CacheKeyGenerator.DEFAULT_KEY_VALUE_GENERATORS.stream()
.peek(generator -> generator.setEnabled(false))
.filter(KeyValueGenerator::isEnabled)
.map(generator -> generator.apply(request))
.collect(Collectors.joining(CacheKeyGenerator.KEY_SEPARATOR));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class HeaderKeyValueGeneratorTest {
@Test
void exceptionIsThrown_whenConstructorHeaderIsNull() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new HeaderKeyValueGenerator(null, SEPARATOR));
.isThrownBy(() -> new HeaderKeyValueGenerator(true, null, SEPARATOR));
}

@Test
Expand All @@ -53,7 +53,7 @@ void keyValuePatternIsGenerated_whenOneSingleValueHeaderIsFound() {
headers.set(HEADER_NAME, SINGLE_HEADER_VALUE);
MockServerHttpRequest request = MockServerHttpRequest.get("http://this").headers(headers).build();

String result = new HeaderKeyValueGenerator(HEADER_NAME, SEPARATOR).apply(request);
String result = new HeaderKeyValueGenerator(true, HEADER_NAME, SEPARATOR).apply(request);

assertThat(result).isEqualTo(HEADER_NAME + "=" + SINGLE_HEADER_VALUE);
}
Expand All @@ -64,7 +64,7 @@ void keyValuePatternIsGenerated_whenOneMultipleValueHeaderIsFound() {
headers.put(HEADER_NAME, List.of(VALUE1, VALUE2));
MockServerHttpRequest request = MockServerHttpRequest.get("http://this").headers(headers).build();

String result = new HeaderKeyValueGenerator(HEADER_NAME, SEPARATOR).apply(request);
String result = new HeaderKeyValueGenerator(true, HEADER_NAME, SEPARATOR).apply(request);

assertThat(result).isEqualTo(HEADER_NAME + "=" + VALUE1 + SEPARATOR + VALUE2);
}
Expand All @@ -75,7 +75,7 @@ void sotedKeyValuePatternIsGenerated_whenOneMultipleUnsortedValueHeaderIsFound()
headers.put(HEADER_NAME, List.of(VALUE2, VALUE1));
MockServerHttpRequest request = MockServerHttpRequest.get("http://this").headers(headers).build();

String result = new HeaderKeyValueGenerator(HEADER_NAME, SEPARATOR).apply(request);
String result = new HeaderKeyValueGenerator(true, HEADER_NAME, SEPARATOR).apply(request);

assertThat(result).isEqualTo(HEADER_NAME + "=" + VALUE1 + SEPARATOR + VALUE2);
}
Expand Down