Skip to content

Commit a9acf92

Browse files
Merge pull request #11 from marcosbarbero/feature/consul-repository
Feature/consul repository
2 parents 777ce52 + fa5f18c commit a9acf92

14 files changed

Lines changed: 375 additions & 51 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,5 @@ target/
5757
### JetBrains template
5858

5959
*.iml
60-
.idea/
60+
.idea/
61+
*.DS_Store

README.md

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ There are five built-in rate limit approaches:
1515
>Note: It is possible to combine Authenticated User, Request Origin and URL just adding
1616
multiple values to the list
1717

18-
Default implementations
19-
---
20-
There are two implementations provided:
21-
* `RedisRateLimiter` for production
22-
* `InMemoryRateLimiter` only for dev environment
23-
24-
2518
Usage
2619
---
2720
>This project is available on maven central
@@ -31,24 +24,34 @@ Add the dependency on pom.xml
3124
<dependency>
3225
<groupId>com.marcosbarbero.cloud</groupId>
3326
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
34-
<version>1.1.0.RELEASE</version>
27+
<version>1.2.0.RELEASE</version>
3528
</dependency>
3629
```
3730

38-
In case you are using Redis there will be needed to add the following dependency as well to pom.xml
31+
In case you are using Redis there will be needed to add the following dependency
3932
```
4033
<dependency>
4134
<groupId>org.springframework.boot</groupId>
4235
<artifactId>spring-boot-starter-data-redis</artifactId>
4336
</dependency>
4437
```
4538

39+
In case you are using Consul there will be needed to add the following dependency
40+
```
41+
<dependency>
42+
<groupId>org.springframework.cloud</groupId>
43+
<artifactId>spring-cloud-starter-consul</artifactId>
44+
</dependency>
45+
```
46+
4647
Sample configuration
4748
```
4849
zuul:
4950
ratelimit:
50-
enabled: true #default false
51-
behind-proxy: true #default false
51+
key-prefix: your-prefix
52+
enabled: true
53+
repository: REDIS
54+
behind-proxy: true
5255
policies:
5356
myServiceId:
5457
limit: 10
@@ -59,6 +62,25 @@ zuul:
5962
- url
6063
```
6164

65+
Available implementations
66+
---
67+
There are three implementations provided:
68+
* `InMemoryRateLimiter` - uses ConcurrentHashMap as data storage
69+
* `ConsulRateLimiter` - uses [Consul](https://www.consul.io/) as data storage
70+
* `RedisRateLimiter` - uses [Redis](https://redis.io/) as data storage
71+
72+
Common application properties
73+
---
74+
Property namespace: __zuul.ratelimit__
75+
76+
|Property name| Values |Default Value|
77+
|-------------|:-------|:-------------:|
78+
|enabled|true/false|false|
79+
|behind-proxy|true/false|false|
80+
|key-prefix|String|${spring.application.name:rate-limit-application}|
81+
|repository|CONSUL, REDIS, IN_MEMORY|IN_MEMORY|
82+
|policies|List of [Policy](spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/properties/Policy.java)| - |
83+
6284
Contributing
6385
---
6486
Spring Cloud Zuul Rate Limit is released under the non-restrictive Apache 2.0 license, and follows a very

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
<dependency>
7676
<groupId>org.springframework.cloud</groupId>
7777
<artifactId>spring-cloud-dependencies</artifactId>
78-
<version>Dalston.SR2</version>
78+
<version>Dalston.SR3</version>
7979
<type>pom</type>
8080
<scope>import</scope>
8181
</dependency>

spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/RateLimitAutoConfiguration.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.springframework.beans.factory.annotation.Qualifier;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
3231
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3332
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3433
import org.springframework.cloud.consul.ConditionalOnConsulEnabled;
@@ -58,7 +57,7 @@ public RateLimitFilter rateLimiterFilter(final RateLimiter rateLimiter,
5857

5958
@ConditionalOnClass(RedisTemplate.class)
6059
@ConditionalOnMissingBean(RateLimiter.class)
61-
@ConditionalOnProperty(prefix = PREFIX, name = "strategy", havingValue = "REDIS")
60+
@ConditionalOnProperty(prefix = PREFIX, name = "repository", havingValue = "REDIS")
6261
public static class RedisConfiguration {
6362

6463
@Bean("rateLimiterRedisTemplate")
@@ -74,8 +73,7 @@ public RateLimiter redisRateLimiter(@Qualifier("rateLimiterRedisTemplate") final
7473

7574
@ConditionalOnConsulEnabled
7675
@ConditionalOnMissingBean(RateLimiter.class)
77-
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
78-
@ConditionalOnProperty(prefix = PREFIX , name = "strategy", havingValue = "CONSUL")
76+
@ConditionalOnProperty(prefix = PREFIX, name = "repository", havingValue = "CONSUL")
7977
public static class ConsulConfiguration {
8078

8179
@Bean
@@ -86,8 +84,7 @@ public RateLimiter consultRateLimiter(final ConsulClient consulClient, final Obj
8684
}
8785

8886
@ConditionalOnMissingBean(RateLimiter.class)
89-
@ConditionalOnMissingClass({"org.springframework.data.redis.core.RedisTemplate",
90-
"com.ecwid.consul.v1.ConsulClient"})
87+
@ConditionalOnProperty(prefix = PREFIX, name = "repository", havingValue = "IN_MEMORY", matchIfMissing = true)
9188
public static class InMemoryConfiguration {
9289

9390
@Bean

spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/properties/RateLimitProperties.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties;
1818

19+
import org.springframework.beans.factory.annotation.Value;
1920
import org.springframework.boot.context.properties.ConfigurationProperties;
2021

2122
import java.util.LinkedHashMap;
@@ -39,9 +40,12 @@ public class RateLimitProperties {
3940
private boolean behindProxy;
4041
private boolean enabled;
4142

42-
private Strategy strategy;
43+
@Value("${spring.application.name:rate-limit-application}")
44+
private String keyPrefix;
4345

44-
public enum Strategy {
46+
private Repository repository;
47+
48+
public enum Repository {
4549
REDIS, CONSUL
4650
}
4751

spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/repository/ConsulRateLimiter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository;
1818

1919
import com.ecwid.consul.v1.ConsulClient;
20+
import com.ecwid.consul.v1.kv.model.GetValue;
2021
import com.fasterxml.jackson.core.JsonProcessingException;
2122
import com.fasterxml.jackson.databind.ObjectMapper;
2223
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.Rate;
@@ -46,10 +47,10 @@ public class ConsulRateLimiter extends AbstractRateLimiter implements RateLimite
4647
@Override
4748
Rate getRate(String key) {
4849
Rate rate = null;
49-
String value = this.consulClient.getKVValue(key).getValue().getValue();
50-
if (StringUtils.hasText(value)) {
50+
GetValue value = this.consulClient.getKVValue(key).getValue();
51+
if (value != null) {
5152
try {
52-
rate = this.objectMapper.readValue(value, Rate.class);
53+
rate = this.objectMapper.readValue(value.getDecodedValue(), Rate.class);
5354
} catch (IOException e) {
5455
log.error("Failed to deserialize Rate", e);
5556
}

spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/RateLimitFilter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ private Optional<Policy> policy() {
108108
private String key(final HttpServletRequest request, final List<Type> types) {
109109
final Route route = route();
110110
final StringJoiner joiner = new StringJoiner(":");
111+
joiner.add(this.properties.getKeyPrefix());
111112
joiner.add(route.getId());
112113
if (!types.isEmpty()) {
113114
if (types.contains(URL)) {

spring-cloud-zuul-ratelimit-core/src/test/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/filters/ConsulRateLimitFilterTest.java

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
import org.junit.Test;
1212

1313
import java.util.Date;
14+
import java.util.concurrent.TimeUnit;
1415

1516
import static java.util.concurrent.TimeUnit.SECONDS;
17+
import static org.junit.Assert.assertEquals;
18+
import static org.junit.Assert.assertTrue;
1619
import static org.mockito.Matchers.anyString;
1720
import static org.mockito.Mockito.mock;
1821
import static org.mockito.Mockito.when;
@@ -26,8 +29,8 @@ public class ConsulRateLimitFilterTest extends BaseRateLimitFilterTest {
2629
private ConsulClient consulClient = mock(ConsulClient.class);
2730
private ObjectMapper objectMapper = new ObjectMapper();
2831

29-
private Rate rate() {
30-
return new Rate(30L, -1L, 100L, new Date(System.currentTimeMillis() + SECONDS.toMillis(10)));
32+
private Rate rate(long remaining) {
33+
return new Rate(30L, remaining, 100L, new Date(System.currentTimeMillis() + SECONDS.toMillis(2)));
3134
}
3235

3336
@Before
@@ -45,36 +48,37 @@ public void testRateLimitExceedCapacity() throws Exception {
4548
GetValue getValue = mock(GetValue.class);
4649
when(this.consulClient.getKVValue(anyString())).thenReturn(response);
4750
when(response.getValue()).thenReturn(getValue);
48-
when(getValue.getValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate()));
51+
when(getValue.getDecodedValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate(-1)));
4952
super.testRateLimitExceedCapacity();
5053
}
5154

5255
@Test
5356
@Override
5457
@SuppressWarnings("unchecked")
5558
public void testRateLimit() throws Exception {
56-
// BoundValueOperations ops = mock(BoundValueOperations.class);
57-
// when(this.redisTemplate.boundValueOps(anyString())).thenReturn(ops);
58-
// when(ops.increment(anyLong())).thenReturn(2L);
59-
//
60-
//
61-
// this.request.setRequestURI("/serviceA");
62-
// this.request.setRemoteAddr("10.0.0.100");
63-
//
64-
// assertTrue(this.filter.shouldFilter());
65-
//
66-
// for (int i = 0; i < 2; i++) {
67-
// this.filter.run();
68-
// }
69-
//
70-
// String remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
71-
// assertEquals("0", remaining);
72-
//
73-
// TimeUnit.SECONDS.sleep(2);
74-
//
75-
// when(ops.increment(anyLong())).thenReturn(1L);
76-
// this.filter.run();
77-
// remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
78-
// assertEquals(remaining, "1");
59+
Response<GetValue> response = mock(Response.class);
60+
GetValue getValue = mock(GetValue.class);
61+
when(this.consulClient.getKVValue(anyString())).thenReturn(response);
62+
when(response.getValue()).thenReturn(getValue);
63+
when(getValue.getDecodedValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate(1)));
64+
65+
this.request.setRequestURI("/serviceA");
66+
this.request.setRemoteAddr("10.0.0.100");
67+
68+
assertTrue(this.filter.shouldFilter());
69+
70+
for (int i = 0; i < 2; i++) {
71+
this.filter.run();
72+
}
73+
74+
String remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
75+
assertEquals("0", remaining);
76+
77+
TimeUnit.SECONDS.sleep(2);
78+
79+
when(getValue.getDecodedValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate(2)));
80+
this.filter.run();
81+
remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
82+
assertEquals("1", remaining);
7983
}
8084
}

spring-cloud-zuul-ratelimit-tests/consul/pom.xml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,55 @@
1111
<modelVersion>4.0.0</modelVersion>
1212

1313
<artifactId>consul</artifactId>
14+
<name>Tests - Consul RateLimit</name>
1415

1516

17+
<dependencies>
18+
<dependency>
19+
<groupId>org.springframework.cloud</groupId>
20+
<artifactId>spring-cloud-starter-consul</artifactId>
21+
</dependency>
22+
23+
<dependency>
24+
<groupId>org.springframework.cloud</groupId>
25+
<artifactId>spring-cloud-starter-zuul</artifactId>
26+
</dependency>
27+
28+
<dependency>
29+
<groupId>com.marcosbarbero.cloud</groupId>
30+
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
31+
</dependency>
32+
33+
<dependency>
34+
<groupId>com.pszymczyk.consul</groupId>
35+
<artifactId>embedded-consul</artifactId>
36+
<version>0.3.4</version>
37+
</dependency>
38+
</dependencies>
39+
40+
<build>
41+
<plugins>
42+
<plugin>
43+
<groupId>org.apache.maven.plugins</groupId>
44+
<artifactId>maven-failsafe-plugin</artifactId>
45+
<version>2.19.1</version>
46+
<executions>
47+
<execution>
48+
<goals>
49+
<goal>integration-test</goal>
50+
<goal>verify</goal>
51+
</goals>
52+
<configuration>
53+
<skipTests>${skip.tests}</skipTests>
54+
<argLine>${argLine} -Duser.timezone=UTC -Xms256m -Xmx256m</argLine>
55+
<includes>
56+
<includes>**/*Test*</includes>
57+
</includes>
58+
</configuration>
59+
</execution>
60+
</executions>
61+
</plugin>
62+
</plugins>
63+
</build>
64+
1665
</project>

0 commit comments

Comments
 (0)