Skip to content

Commit

Permalink
Merge pull request #11 from marcosbarbero/feature/consul-repository
Browse files Browse the repository at this point in the history
Feature/consul repository
  • Loading branch information
marcosbarbero authored Aug 30, 2017
2 parents 777ce52 + fa5f18c commit a9acf92
Show file tree
Hide file tree
Showing 14 changed files with 375 additions and 51 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ target/
### JetBrains template

*.iml
.idea/
.idea/
*.DS_Store
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ There are five built-in rate limit approaches:
>Note: It is possible to combine Authenticated User, Request Origin and URL just adding
multiple values to the list

Default implementations
---
There are two implementations provided:
* `RedisRateLimiter` for production
* `InMemoryRateLimiter` only for dev environment


Usage
---
>This project is available on maven central
Expand All @@ -31,24 +24,34 @@ Add the dependency on pom.xml
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>1.1.0.RELEASE</version>
<version>1.2.0.RELEASE</version>
</dependency>
```

In case you are using Redis there will be needed to add the following dependency as well to pom.xml
In case you are using Redis there will be needed to add the following dependency
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```

In case you are using Consul there will be needed to add the following dependency
```
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul</artifactId>
</dependency>
```

Sample configuration
```
zuul:
ratelimit:
enabled: true #default false
behind-proxy: true #default false
key-prefix: your-prefix
enabled: true
repository: REDIS
behind-proxy: true
policies:
myServiceId:
limit: 10
Expand All @@ -59,6 +62,25 @@ zuul:
- url
```

Available implementations
---
There are three implementations provided:
* `InMemoryRateLimiter` - uses ConcurrentHashMap as data storage
* `ConsulRateLimiter` - uses [Consul](https://www.consul.io/) as data storage
* `RedisRateLimiter` - uses [Redis](https://redis.io/) as data storage

Common application properties
---
Property namespace: __zuul.ratelimit__

|Property name| Values |Default Value|
|-------------|:-------|:-------------:|
|enabled|true/false|false|
|behind-proxy|true/false|false|
|key-prefix|String|${spring.application.name:rate-limit-application}|
|repository|CONSUL, REDIS, IN_MEMORY|IN_MEMORY|
|policies|List of [Policy](spring-cloud-zuul-ratelimit-core/src/main/java/com/marcosbarbero/cloud/autoconfigure/zuul/ratelimit/config/properties/Policy.java)| - |

Contributing
---
Spring Cloud Zuul Rate Limit is released under the non-restrictive Apache 2.0 license, and follows a very
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR2</version>
<version>Dalston.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.consul.ConditionalOnConsulEnabled;
Expand Down Expand Up @@ -58,7 +57,7 @@ public RateLimitFilter rateLimiterFilter(final RateLimiter rateLimiter,

@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnMissingBean(RateLimiter.class)
@ConditionalOnProperty(prefix = PREFIX, name = "strategy", havingValue = "REDIS")
@ConditionalOnProperty(prefix = PREFIX, name = "repository", havingValue = "REDIS")
public static class RedisConfiguration {

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

@ConditionalOnConsulEnabled
@ConditionalOnMissingBean(RateLimiter.class)
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
@ConditionalOnProperty(prefix = PREFIX , name = "strategy", havingValue = "CONSUL")
@ConditionalOnProperty(prefix = PREFIX, name = "repository", havingValue = "CONSUL")
public static class ConsulConfiguration {

@Bean
Expand All @@ -86,8 +84,7 @@ public RateLimiter consultRateLimiter(final ConsulClient consulClient, final Obj
}

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

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.LinkedHashMap;
Expand All @@ -39,9 +40,12 @@ public class RateLimitProperties {
private boolean behindProxy;
private boolean enabled;

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

public enum Strategy {
private Repository repository;

public enum Repository {
REDIS, CONSUL
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.repository;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.Rate;
Expand Down Expand Up @@ -46,10 +47,10 @@ public class ConsulRateLimiter extends AbstractRateLimiter implements RateLimite
@Override
Rate getRate(String key) {
Rate rate = null;
String value = this.consulClient.getKVValue(key).getValue().getValue();
if (StringUtils.hasText(value)) {
GetValue value = this.consulClient.getKVValue(key).getValue();
if (value != null) {
try {
rate = this.objectMapper.readValue(value, Rate.class);
rate = this.objectMapper.readValue(value.getDecodedValue(), Rate.class);
} catch (IOException e) {
log.error("Failed to deserialize Rate", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ private Optional<Policy> policy() {
private String key(final HttpServletRequest request, final List<Type> types) {
final Route route = route();
final StringJoiner joiner = new StringJoiner(":");
joiner.add(this.properties.getKeyPrefix());
joiner.add(route.getId());
if (!types.isEmpty()) {
if (types.contains(URL)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
import org.junit.Test;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand All @@ -26,8 +29,8 @@ public class ConsulRateLimitFilterTest extends BaseRateLimitFilterTest {
private ConsulClient consulClient = mock(ConsulClient.class);
private ObjectMapper objectMapper = new ObjectMapper();

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

@Before
Expand All @@ -45,36 +48,37 @@ public void testRateLimitExceedCapacity() throws Exception {
GetValue getValue = mock(GetValue.class);
when(this.consulClient.getKVValue(anyString())).thenReturn(response);
when(response.getValue()).thenReturn(getValue);
when(getValue.getValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate()));
when(getValue.getDecodedValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate(-1)));
super.testRateLimitExceedCapacity();
}

@Test
@Override
@SuppressWarnings("unchecked")
public void testRateLimit() throws Exception {
// BoundValueOperations ops = mock(BoundValueOperations.class);
// when(this.redisTemplate.boundValueOps(anyString())).thenReturn(ops);
// when(ops.increment(anyLong())).thenReturn(2L);
//
//
// this.request.setRequestURI("/serviceA");
// this.request.setRemoteAddr("10.0.0.100");
//
// assertTrue(this.filter.shouldFilter());
//
// for (int i = 0; i < 2; i++) {
// this.filter.run();
// }
//
// String remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
// assertEquals("0", remaining);
//
// TimeUnit.SECONDS.sleep(2);
//
// when(ops.increment(anyLong())).thenReturn(1L);
// this.filter.run();
// remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
// assertEquals(remaining, "1");
Response<GetValue> response = mock(Response.class);
GetValue getValue = mock(GetValue.class);
when(this.consulClient.getKVValue(anyString())).thenReturn(response);
when(response.getValue()).thenReturn(getValue);
when(getValue.getDecodedValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate(1)));

this.request.setRequestURI("/serviceA");
this.request.setRemoteAddr("10.0.0.100");

assertTrue(this.filter.shouldFilter());

for (int i = 0; i < 2; i++) {
this.filter.run();
}

String remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
assertEquals("0", remaining);

TimeUnit.SECONDS.sleep(2);

when(getValue.getDecodedValue()).thenReturn(this.objectMapper.writeValueAsString(this.rate(2)));
this.filter.run();
remaining = this.response.getHeader(RateLimitFilter.Headers.REMAINING);
assertEquals("1", remaining);
}
}
49 changes: 49 additions & 0 deletions spring-cloud-zuul-ratelimit-tests/consul/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,55 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>consul</artifactId>
<name>Tests - Consul RateLimit</name>


<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
</dependency>

<dependency>
<groupId>com.pszymczyk.consul</groupId>
<artifactId>embedded-consul</artifactId>
<version>0.3.4</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.19.1</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<skipTests>${skip.tests}</skipTests>
<argLine>${argLine} -Duser.timezone=UTC -Xms256m -Xmx256m</argLine>
<includes>
<includes>**/*Test*</includes>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Loading

0 comments on commit a9acf92

Please sign in to comment.