@@ -5,6 +5,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
* https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Ronny Bräunlich
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter")
|
||||
public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Config> implements ApplicationContextAware {
|
||||
@@ -64,7 +66,7 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
|
||||
private String burstCapacityHeader = BURST_CAPACITY_HEADER;
|
||||
|
||||
public RedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
|
||||
RedisScript<List<Long>> script, Validator validator) {
|
||||
RedisScript<List<Long>> script, Validator validator) {
|
||||
super(Config.class, CONFIGURATION_PROPERTY_NAME, validator);
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.script = script;
|
||||
@@ -159,12 +161,12 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
|
||||
Instant.now().getEpochSecond() + "", "1");
|
||||
// allowed, tokens_left = redis.eval(SCRIPT, keys, args)
|
||||
Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
|
||||
// .log("redisratelimiter", Level.FINER);
|
||||
// .log("redisratelimiter", Level.FINER);
|
||||
return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
|
||||
.reduce(new ArrayList<Long>(), (longs, l) -> {
|
||||
longs.addAll(l);
|
||||
return longs;
|
||||
}) .map(results -> {
|
||||
}).map(results -> {
|
||||
boolean allowed = results.get(0) == 1L;
|
||||
Long tokensLeft = results.get(1);
|
||||
|
||||
@@ -188,11 +190,13 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HashMap<String, String> getHeaders(Config config, Long tokensLeft) {
|
||||
HashMap<String, String> headers = new HashMap<>();
|
||||
headers.put(this.remainingHeader, tokensLeft.toString());
|
||||
headers.put(this.replenishRateHeader, String.valueOf(config.getReplenishRate()));
|
||||
headers.put(this.burstCapacityHeader, String.valueOf(config.getBurstCapacity()));
|
||||
public Map<String, String> getHeaders(Config config, Long tokensLeft) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
if (isIncludeHeaders()) {
|
||||
headers.put(this.remainingHeader, tokensLeft.toString());
|
||||
headers.put(this.replenishRateHeader, String.valueOf(config.getReplenishRate()));
|
||||
headers.put(this.burstCapacityHeader, String.valueOf(config.getBurstCapacity()));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.UUID;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -25,6 +26,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
/**
|
||||
* see https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L36-L62
|
||||
* @author Spencer Gibb
|
||||
* @author Ronny Bräunlich
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@@ -71,7 +73,7 @@ public class RedisRateLimiterTests extends BaseWebClientTests {
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
// # After the burst is done, check the steady state
|
||||
// # After the burst is done, check the steady state
|
||||
for (int i = 0; i < replenishRate; i++) {
|
||||
response = rateLimiter.isAllowed(routeId, id).block();
|
||||
assertThat(response.isAllowed()).as("steady state # %s is allowed", i).isTrue();
|
||||
@@ -80,13 +82,33 @@ public class RedisRateLimiterTests extends BaseWebClientTests {
|
||||
response = rateLimiter.isAllowed(routeId, id).block();
|
||||
assertThat(response.isAllowed()).as("steady state # %s is allowed", replenishRate).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void keysUseRedisKeyHashTags() {
|
||||
assertThat(RedisRateLimiter.getKeys("1"))
|
||||
.containsExactly("request_rate_limiter.{1}.tokens", "request_rate_limiter.{1}.timestamp");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void redisRateLimiterDoesNotSendHeadersIfDeactivated() throws Exception {
|
||||
assumeThat("Ignore on Circle",
|
||||
System.getenv("CIRCLECI"), is(nullValue()));
|
||||
|
||||
String id = UUID.randomUUID().toString();
|
||||
String routeId = "myroute";
|
||||
|
||||
rateLimiter.setIncludeHeaders(false);
|
||||
|
||||
Response response = rateLimiter.isAllowed(routeId, id).block();
|
||||
assertThat(response.isAllowed()).isTrue();
|
||||
assertThat(response.getHeaders())
|
||||
.doesNotContainKey(RedisRateLimiter.REMAINING_HEADER);
|
||||
assertThat(response.getHeaders()).
|
||||
doesNotContainKey(RedisRateLimiter.REPLENISH_RATE_HEADER);
|
||||
assertThat(response.getHeaders()).
|
||||
doesNotContainKey(RedisRateLimiter.BURST_CAPACITY_HEADER);
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@Import(BaseWebClientTests.DefaultTestConfig.class)
|
||||
|
||||
Reference in New Issue
Block a user