From dcebc2fa28e9a9c0e953905ce4f4fe8039be63ff Mon Sep 17 00:00:00 2001 From: Marc Giffing Date: Fri, 29 Sep 2023 14:53:18 +0200 Subject: [PATCH] #184 #160 Spring Data Redis --- bucket4j-spring-boot-starter/pom.xml | 6 +- .../cache/Bucket4jCacheConfiguration.java | 2 - .../RedissonBucket4jConfiguration.java | 6 +- .../redis/redisson/RedissonCacheResolver.java | 15 ++- .../RedisSpringDataBucket4jConfiguration.java | 25 ----- .../RedisSpringDataCacheResolver.java | 40 -------- .../ServletHeaderExecutePredicate.java | 8 +- .../predicate/ServletMethodPredicate.java | 4 - .../ServletPathExecutePredicate.java | 4 - .../ServletQueryExecutePredicate.java | 4 - ...tRequestExecutePredicateConfiguration.java | 29 +++++- examples/redis-jedis/.gitignore | 38 ++++++++ examples/redis-jedis/pom.xml | 65 +++++++++++++ .../boot/starter/DebugMetricHandler.java | 27 ++++++ .../boot/starter/JedisConfiguraiton.java | 34 +++++++ .../spring/boot/starter/RedisApplication.java | 13 +++ .../spring/boot/starter/TestController.java | 20 ++++ .../src/main/resources/application.yml | 34 +++++++ .../starter/examples/redis/RedisTest.java | 89 +++++++++++++++++ examples/redis-lettuce/.gitignore | 38 ++++++++ examples/redis-lettuce/pom.xml | 68 +++++++++++++ .../boot/starter/DebugMetricHandler.java | 27 ++++++ .../boot/starter/LettuceConfiguraiton.java | 16 ++++ .../spring/boot/starter/RedisApplication.java | 13 +++ .../spring/boot/starter/TestController.java | 20 ++++ .../src/main/resources/application.yml | 36 +++++++ .../starter/examples/redis/RedisTest.java | 96 +++++++++++++++++++ examples/redis-redisson/.gitignore | 38 ++++++++ examples/redis-redisson/pom.xml | 69 +++++++++++++ .../boot/starter/DebugMetricHandler.java | 27 ++++++ .../spring/boot/starter/RedisApplication.java | 13 +++ .../boot/starter/RedissonConfiguraiton.java | 43 +++++++++ .../spring/boot/starter/TestController.java | 20 ++++ .../src/main/resources/application.yml | 35 +++++++ .../starter/examples/redis/RedisTest.java | 96 +++++++++++++++++++ pom.xml | 13 ++- 36 files changed, 1025 insertions(+), 106 deletions(-) delete mode 100644 bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataBucket4jConfiguration.java delete mode 100644 bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataCacheResolver.java create mode 100644 examples/redis-jedis/.gitignore create mode 100644 examples/redis-jedis/pom.xml create mode 100644 examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java create mode 100644 examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/JedisConfiguraiton.java create mode 100644 examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java create mode 100644 examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java create mode 100644 examples/redis-jedis/src/main/resources/application.yml create mode 100644 examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java create mode 100644 examples/redis-lettuce/.gitignore create mode 100644 examples/redis-lettuce/pom.xml create mode 100644 examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java create mode 100644 examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/LettuceConfiguraiton.java create mode 100644 examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java create mode 100644 examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java create mode 100644 examples/redis-lettuce/src/main/resources/application.yml create mode 100644 examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java create mode 100644 examples/redis-redisson/.gitignore create mode 100644 examples/redis-redisson/pom.xml create mode 100644 examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java create mode 100644 examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java create mode 100644 examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedissonConfiguraiton.java create mode 100644 examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java create mode 100644 examples/redis-redisson/src/main/resources/application.yml create mode 100644 examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java diff --git a/bucket4j-spring-boot-starter/pom.xml b/bucket4j-spring-boot-starter/pom.xml index 1b404a86..c05deb23 100644 --- a/bucket4j-spring-boot-starter/pom.xml +++ b/bucket4j-spring-boot-starter/pom.xml @@ -17,6 +17,8 @@ UTF-8 UTF-8 17 + 3.23.5 + 2.15.0 @@ -93,7 +95,7 @@ org.redisson redisson - 3.20.0 + ${redisson.version} provided @@ -114,7 +116,7 @@ org.apache.ignite ignite-core - 2.15.0 + ${ignite-core.version} provided diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java index 8780bf11..51853c32 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java @@ -13,7 +13,6 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.jedis.JedisBucket4jConfiguration; import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.lettuce.LettuceBucket4jConfiguration; import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.redisson.RedissonBucket4jConfiguration; -import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.springdata.RedisSpringDataBucket4jConfiguration; @Configuration @AutoConfigureAfter(CacheAutoConfiguration.class) @@ -26,7 +25,6 @@ JedisBucket4jConfiguration.class, LettuceBucket4jConfiguration.class, RedissonBucket4jConfiguration.class, - RedisSpringDataBucket4jConfiguration.class }) public class Bucket4jCacheConfiguration { } diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java index 271b956f..75a0d346 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java @@ -3,7 +3,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import io.github.bucket4j.redis.redisson.cas.RedissonBasedProxyManager; -import org.redisson.command.CommandExecutor; +import org.redisson.command.CommandAsyncExecutor; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -14,12 +14,12 @@ @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnClass(RedissonBasedProxyManager.class) -@ConditionalOnBean(CommandExecutor.class) +@ConditionalOnBean(CommandAsyncExecutor.class) @ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "cache-to-use", havingValue = "redis-redisson", matchIfMissing = true) public class RedissonBucket4jConfiguration { @Bean - public AsyncCacheResolver bucket4RedisResolver(CommandExecutor commandExecutor) { + public AsyncCacheResolver bucket4RedisResolver(CommandAsyncExecutor commandExecutor) { return new RedissonCacheResolver(commandExecutor); } } diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheResolver.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheResolver.java index 78260c7b..0969678f 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheResolver.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheResolver.java @@ -1,18 +1,15 @@ package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.redisson; -import java.time.Duration; - -import org.redisson.command.CommandExecutor; - import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.ProxyManagerWrapper; import com.giffing.bucket4j.spring.boot.starter.context.ConsumptionProbeHolder; - import io.github.bucket4j.distributed.AsyncBucketProxy; import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy; -import io.github.bucket4j.distributed.proxy.ProxyManager; import io.github.bucket4j.redis.redisson.cas.RedissonBasedProxyManager; +import org.redisson.command.CommandAsyncExecutor; + +import java.time.Duration; /** * This class is the Redis implementation of the {@link CacheResolver}. @@ -20,15 +17,15 @@ */ public class RedissonCacheResolver implements AsyncCacheResolver { - private final CommandExecutor commandExecutor; + private final CommandAsyncExecutor commandExecutor; - public RedissonCacheResolver(CommandExecutor commandExecutor) { + public RedissonCacheResolver(CommandAsyncExecutor commandExecutor) { this.commandExecutor = commandExecutor; } @Override public ProxyManagerWrapper resolve(String cacheName) { - final ProxyManager proxyManager = RedissonBasedProxyManager.builderFor(commandExecutor) + var proxyManager = RedissonBasedProxyManager.builderFor(commandExecutor) .withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10))) .build(); diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataBucket4jConfiguration.java deleted file mode 100644 index 386270c3..00000000 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataBucket4jConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.springdata; - -import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; -import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; -import io.github.bucket4j.redis.spring.cas.SpringDataRedisBasedProxyManager; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisCommands; - -@Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) -@ConditionalOnClass(SpringDataRedisBasedProxyManager.class) -@ConditionalOnBean(RedisCommands.class) -@ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "cache-to-use", havingValue = "redis-springdata", matchIfMissing = true) -public class RedisSpringDataBucket4jConfiguration { - - @Bean - public SyncCacheResolver bucket4RedisResolver(RedisCommands redisCommands) { - return new RedisSpringDataCacheResolver(redisCommands); - } -} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataCacheResolver.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataCacheResolver.java deleted file mode 100644 index 450dddd7..00000000 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/springdata/RedisSpringDataCacheResolver.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.springdata; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; -import com.giffing.bucket4j.spring.boot.starter.config.cache.ProxyManagerWrapper; -import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; -import com.giffing.bucket4j.spring.boot.starter.context.ConsumptionProbeHolder; -import io.github.bucket4j.Bucket; -import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy; -import io.github.bucket4j.distributed.proxy.ProxyManager; -import io.github.bucket4j.redis.spring.cas.SpringDataRedisBasedProxyManager; -import java.time.Duration; -import org.springframework.data.redis.connection.RedisCommands; - -/** - * This class is the Redis implementation of the {@link CacheResolver}. - * - */ -public class RedisSpringDataCacheResolver implements SyncCacheResolver { - - private final RedisCommands redisCommands; - - public RedisSpringDataCacheResolver(RedisCommands redisCommands) { - this.redisCommands = redisCommands; - } - - @Override - public ProxyManagerWrapper resolve(String cacheName) { - final ProxyManager proxyManager = SpringDataRedisBasedProxyManager.builderFor(redisCommands) - .withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10))) - .build(); - - return (key, numTokens, bucketConfiguration, metricsListener) -> { - Bucket bucket = proxyManager.builder().build(key.getBytes(UTF_8), bucketConfiguration).toListenable(metricsListener); - return new ConsumptionProbeHolder(bucket.tryConsumeAndReturnRemaining(numTokens)); - }; - - } -} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletHeaderExecutePredicate.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletHeaderExecutePredicate.java index 7df66d65..be521798 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletHeaderExecutePredicate.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletHeaderExecutePredicate.java @@ -1,14 +1,10 @@ package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; -import java.util.Collections; - -import org.springframework.stereotype.Component; - import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.HeaderExecutePredicate; - import jakarta.servlet.http.HttpServletRequest; -@Component +import java.util.Collections; + public class ServletHeaderExecutePredicate extends HeaderExecutePredicate{ @Override diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletMethodPredicate.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletMethodPredicate.java index 94ae8bd1..b59c15de 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletMethodPredicate.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletMethodPredicate.java @@ -1,12 +1,8 @@ package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; -import org.springframework.stereotype.Component; - import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.MethodExecutePredicate; - import jakarta.servlet.http.HttpServletRequest; -@Component public class ServletMethodPredicate extends MethodExecutePredicate { @Override diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletPathExecutePredicate.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletPathExecutePredicate.java index 540bca03..1b853533 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletPathExecutePredicate.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletPathExecutePredicate.java @@ -1,12 +1,8 @@ package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; -import org.springframework.stereotype.Component; - import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.PathExecutePredicate; - import jakarta.servlet.http.HttpServletRequest; -@Component public class ServletPathExecutePredicate extends PathExecutePredicate{ @Override diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletQueryExecutePredicate.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletQueryExecutePredicate.java index 03a83590..092175fc 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletQueryExecutePredicate.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletQueryExecutePredicate.java @@ -1,12 +1,8 @@ package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; -import org.springframework.stereotype.Component; - import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.QueryExecutePredicate; - import jakarta.servlet.http.HttpServletRequest; -@Component public class ServletQueryExecutePredicate extends QueryExecutePredicate { @Override diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletRequestExecutePredicateConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletRequestExecutePredicateConfiguration.java index f2106a9f..6c1a0ff2 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletRequestExecutePredicateConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletRequestExecutePredicateConfiguration.java @@ -1,9 +1,34 @@ package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; -import org.springframework.context.annotation.ComponentScan; +import com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.Bucket4JAutoConfigurationServletFilter; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration -@ComponentScan +@AutoConfigureAfter(Bucket4JAutoConfigurationServletFilter.class) +@ConditionalOnBean(Bucket4JAutoConfigurationServletFilter.class) public class ServletRequestExecutePredicateConfiguration { + + @Bean + ServletHeaderExecutePredicate servletHeaderExecutePredicate() { + return new ServletHeaderExecutePredicate(); + } + + @Bean + ServletMethodPredicate servletMethodPredicate() { + return new ServletMethodPredicate(); + } + + @Bean + ServletPathExecutePredicate servletPathExecutePredicate() { + return new ServletPathExecutePredicate(); + } + + @Bean + ServletQueryExecutePredicate servletQueryExecutePredicate() { + return new ServletQueryExecutePredicate(); + } + } diff --git a/examples/redis-jedis/.gitignore b/examples/redis-jedis/.gitignore new file mode 100644 index 00000000..5ff6309b --- /dev/null +++ b/examples/redis-jedis/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/examples/redis-jedis/pom.xml b/examples/redis-jedis/pom.xml new file mode 100644 index 00000000..09bd86d8 --- /dev/null +++ b/examples/redis-jedis/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter-parent + ${revision} + ../../pom.xml + + + bucket4j-spring-boot-starter-example-redis-jedis + + + 17 + 17 + UTF-8 + 1.6.4 + 5.0.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter + + + com.bucket4j + bucket4j-redis + + + redis.clients + jedis + ${jedis.version} + + + + com.redis.testcontainers + testcontainers-redis-junit + ${testcontainers-redis-junit.version} + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + \ No newline at end of file diff --git a/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java new file mode 100644 index 00000000..8f8ddcdd --- /dev/null +++ b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java @@ -0,0 +1,27 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class DebugMetricHandler implements MetricHandler { + + @Override + public void handle(MetricType type, String name, long tokens, List tags) { + System.out.println(String.format("type: %s; name: %s; tags: %s; tokens: %s", + type, + name, + tags + .stream() + .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) + .collect(Collectors.joining(",")), + tokens)); + + } + +} diff --git a/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/JedisConfiguraiton.java b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/JedisConfiguraiton.java new file mode 100644 index 00000000..38cd1ac4 --- /dev/null +++ b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/JedisConfiguraiton.java @@ -0,0 +1,34 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import java.time.Duration; + +@Configuration +public class JedisConfiguraiton { + + @Bean + public JedisPool jedisPool(@Value("${spring.redis.port}") String port) { + final JedisPoolConfig poolConfig = buildPoolConfig(); + return new JedisPool(poolConfig, "localhost", Integer.valueOf(port)); + } + + private JedisPoolConfig buildPoolConfig() { + final JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(128); + poolConfig.setMaxIdle(128); + poolConfig.setMinIdle(16); + poolConfig.setTestOnBorrow(true); + poolConfig.setTestOnReturn(true); + poolConfig.setTestWhileIdle(true); + poolConfig.setMinEvictableIdleTimeMillis(Duration.ofSeconds(60).toMillis()); + poolConfig.setTimeBetweenEvictionRunsMillis(Duration.ofSeconds(30).toMillis()); + poolConfig.setNumTestsPerEvictionRun(3); + poolConfig.setBlockWhenExhausted(true); + return poolConfig; + } +} diff --git a/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java new file mode 100644 index 00000000..5e4078f8 --- /dev/null +++ b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java @@ -0,0 +1,13 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RedisApplication { + + public static void main(String[] args) { + SpringApplication.run(RedisApplication.class, args); + } + +} \ No newline at end of file diff --git a/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java new file mode 100644 index 00000000..ee3b5b31 --- /dev/null +++ b/examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java @@ -0,0 +1,20 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @GetMapping("hello") + public ResponseEntity hello() { + return ResponseEntity.ok("Hello World"); + } + + @GetMapping("world") + public ResponseEntity world() { + return ResponseEntity.ok("Hello World"); + } + +} diff --git a/examples/redis-jedis/src/main/resources/application.yml b/examples/redis-jedis/src/main/resources/application.yml new file mode 100644 index 00000000..e3464482 --- /dev/null +++ b/examples/redis-jedis/src/main/resources/application.yml @@ -0,0 +1,34 @@ +debug: true +logging: + level: + com.giffing.bucket4j: debug +management: + endpoints: + web: + exposure: + include: "*" + security: + enabled: false + +bucket4j: + enabled: true + cache-to-use: redis-jedis + filters: + - cache-name: buckets_test + url: ^(/hello).* + rate-limits: + - bandwidths: + - capacity: 5 + time: 10 + unit: seconds + - cache-name: buckets_test + url: ^(/world).* + rate-limits: + - bandwidths: + - capacity: 10 + time: 10 + unit: seconds + +spring: + main: + allow-bean-definition-overriding: true diff --git a/examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java b/examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java new file mode 100644 index 00000000..85e8303d --- /dev/null +++ b/examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java @@ -0,0 +1,89 @@ +package com.giffing.bucket4j.spring.boot.starter.examples.redis; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.Collections; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.fail; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@Testcontainers +class RedisTest { + + @Container + static GenericContainer redis = + new GenericContainer(DockerImageName.parse("redis:7")) + .withExposedPorts(6379); + + @DynamicPropertySource + static void redisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.redis.host", () -> redis.getHost()); + registry.add("spring.redis.port", () -> redis.getFirstMappedPort()); + } + + @Autowired + private MockMvc mockMvc; + + @Test + void helloTest() throws Exception { + String url = "/hello"; + IntStream.rangeClosed(1, 5) + .boxed() + .sorted(Collections.reverseOrder()) + .forEach(counter -> { + successfulWebRequest(url, counter - 1); + }); + + blockedWebRequestDueToRateLimit(url); + } + + + @Test + void worldTest() throws Exception { + String url = "/world"; + IntStream.rangeClosed(1, 10) + .boxed() + .sorted(Collections.reverseOrder()) + .forEach(counter -> { + successfulWebRequest(url, counter - 1); + }); + + blockedWebRequestDueToRateLimit(url); + } + + private void successfulWebRequest(String url, Integer remainingTries) { + try { + this.mockMvc + .perform(get(url)) + .andExpect(status().isOk()) + .andExpect(header().longValue("X-Rate-Limit-Remaining", remainingTries)) + .andExpect(content().string(containsString("Hello World"))); + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + private void blockedWebRequestDueToRateLimit(String url) throws Exception { + this.mockMvc + .perform(get(url)) + .andExpect(status().is(HttpStatus.TOO_MANY_REQUESTS.value())) + .andExpect(content().string(containsString("{ \"message\": \"Too many requests!\" }"))); + } +} diff --git a/examples/redis-lettuce/.gitignore b/examples/redis-lettuce/.gitignore new file mode 100644 index 00000000..5ff6309b --- /dev/null +++ b/examples/redis-lettuce/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/examples/redis-lettuce/pom.xml b/examples/redis-lettuce/pom.xml new file mode 100644 index 00000000..63863e2c --- /dev/null +++ b/examples/redis-lettuce/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter-parent + ${revision} + ../../pom.xml + + + bucket4j-spring-boot-starter-example-redis-lettuce + + + 17 + 17 + UTF-8 + 1.6.4 + 6.2.6.RELEASE + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter + + + com.bucket4j + bucket4j-redis + + + io.lettuce + lettuce-core + ${lettuce-core.version} + + + org.apache.commons + commons-pool2 + + + com.redis.testcontainers + testcontainers-redis-junit + ${testcontainers-redis-junit.version} + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + \ No newline at end of file diff --git a/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java new file mode 100644 index 00000000..8f8ddcdd --- /dev/null +++ b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java @@ -0,0 +1,27 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class DebugMetricHandler implements MetricHandler { + + @Override + public void handle(MetricType type, String name, long tokens, List tags) { + System.out.println(String.format("type: %s; name: %s; tags: %s; tokens: %s", + type, + name, + tags + .stream() + .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) + .collect(Collectors.joining(",")), + tokens)); + + } + +} diff --git a/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/LettuceConfiguraiton.java b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/LettuceConfiguraiton.java new file mode 100644 index 00000000..9b10b55f --- /dev/null +++ b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/LettuceConfiguraiton.java @@ -0,0 +1,16 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import io.lettuce.core.RedisClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class LettuceConfiguraiton { + + @Bean + public RedisClient redisClient(@Value("${spring.redis.port}") String port) { + return RedisClient.create("redis://password@localhost:%s/".formatted(port)); + } + +} diff --git a/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java new file mode 100644 index 00000000..5e4078f8 --- /dev/null +++ b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java @@ -0,0 +1,13 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RedisApplication { + + public static void main(String[] args) { + SpringApplication.run(RedisApplication.class, args); + } + +} \ No newline at end of file diff --git a/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java new file mode 100644 index 00000000..ee3b5b31 --- /dev/null +++ b/examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java @@ -0,0 +1,20 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @GetMapping("hello") + public ResponseEntity hello() { + return ResponseEntity.ok("Hello World"); + } + + @GetMapping("world") + public ResponseEntity world() { + return ResponseEntity.ok("Hello World"); + } + +} diff --git a/examples/redis-lettuce/src/main/resources/application.yml b/examples/redis-lettuce/src/main/resources/application.yml new file mode 100644 index 00000000..dffbf4c5 --- /dev/null +++ b/examples/redis-lettuce/src/main/resources/application.yml @@ -0,0 +1,36 @@ +debug: true +logging: + level: + com.giffing.bucket4j: debug +management: + endpoints: + web: + exposure: + include: "*" + security: + enabled: false + +bucket4j: + enabled: true + cache-to-use: redis-lettuce + filters: + - cache-name: buckets_test + filter-method: webflux + url: ^(/hello).* + rate-limits: + - bandwidths: + - capacity: 5 + time: 10 + unit: seconds + - cache-name: buckets_test + filter-method: webflux + url: ^(/world).* + rate-limits: + - bandwidths: + - capacity: 10 + time: 10 + unit: seconds + +spring: + main: + allow-bean-definition-overriding: true diff --git a/examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java b/examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java new file mode 100644 index 00000000..00854130 --- /dev/null +++ b/examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java @@ -0,0 +1,96 @@ +package com.giffing.bucket4j.spring.boot.starter.examples.redis; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.Collections; +import java.util.stream.IntStream; + +@SpringBootTest +@AutoConfigureMockMvc +@Testcontainers +class RedisTest { + + @Container + static GenericContainer redis = + new GenericContainer(DockerImageName.parse("redis:7")) + .withExposedPorts(6379); + + @DynamicPropertySource + static void redisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.redis.host", () -> redis.getHost()); + registry.add("spring.redis.port", () -> redis.getFirstMappedPort()); + } + + @Autowired + ApplicationContext context; + + WebTestClient rest; + + @BeforeEach + public void setup() { + this.rest = WebTestClient + .bindToApplicationContext(this.context) + .configureClient() + .build(); + } + + @Test + void helloTest() throws Exception { + String url = "/hello"; + IntStream.rangeClosed(1, 5) + .boxed() + .sorted(Collections.reverseOrder()) + .forEach(counter -> { + successfulWebRequest(url, counter - 1); + }); + + blockedWebRequestDueToRateLimit(url); + } + + + @Test + void worldTest() throws Exception { + String url = "/world"; + IntStream.rangeClosed(1, 10) + .boxed() + .sorted(Collections.reverseOrder()) + .forEach(counter -> { + System.out.println(counter); + successfulWebRequest(url, counter - 1); + }); + + blockedWebRequestDueToRateLimit(url); + } + + private void successfulWebRequest(String url, Integer remainingTries) { + rest + .get() + .uri(url) + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals("X-Rate-Limit-Remaining", String.valueOf(remainingTries)); + } + + private void blockedWebRequestDueToRateLimit(String url) throws Exception { + rest + .get() + .uri(url) + .exchange() + .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) + .expectBody().jsonPath("error", "Too many requests!"); + } + +} diff --git a/examples/redis-redisson/.gitignore b/examples/redis-redisson/.gitignore new file mode 100644 index 00000000..5ff6309b --- /dev/null +++ b/examples/redis-redisson/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/examples/redis-redisson/pom.xml b/examples/redis-redisson/pom.xml new file mode 100644 index 00000000..e270666a --- /dev/null +++ b/examples/redis-redisson/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter-parent + ${revision} + ../../pom.xml + + + bucket4j-spring-boot-starter-example-redis-redisson + + + 17 + 17 + UTF-8 + 1.6.4 + 3.23.5 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + com.giffing.bucket4j.spring.boot.starter + bucket4j-spring-boot-starter + + + com.bucket4j + bucket4j-redis + + + org.redisson + redisson + ${redisson.version} + + + + org.apache.commons + commons-pool2 + + + com.redis.testcontainers + testcontainers-redis-junit + ${testcontainers-redis-junit.version} + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + \ No newline at end of file diff --git a/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java new file mode 100644 index 00000000..8f8ddcdd --- /dev/null +++ b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java @@ -0,0 +1,27 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; +import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class DebugMetricHandler implements MetricHandler { + + @Override + public void handle(MetricType type, String name, long tokens, List tags) { + System.out.println(String.format("type: %s; name: %s; tags: %s; tokens: %s", + type, + name, + tags + .stream() + .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) + .collect(Collectors.joining(",")), + tokens)); + + } + +} diff --git a/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java new file mode 100644 index 00000000..5e4078f8 --- /dev/null +++ b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisApplication.java @@ -0,0 +1,13 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RedisApplication { + + public static void main(String[] args) { + SpringApplication.run(RedisApplication.class, args); + } + +} \ No newline at end of file diff --git a/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedissonConfiguraiton.java b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedissonConfiguraiton.java new file mode 100644 index 00000000..2938f9a8 --- /dev/null +++ b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedissonConfiguraiton.java @@ -0,0 +1,43 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.SingleServerConfig; +import org.redisson.connection.ServiceManager; +import org.redisson.connection.SingleConnectionManager; +import org.redisson.liveobject.core.RedissonObjectBuilder; +import org.redisson.rx.CommandRxService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RedissonConfiguraiton { + + @Bean + public CommandRxService commandReactiveService( + RedissionConfig redissionConfig) { + return new CommandRxService( + new SingleConnectionManager( + redissionConfig.ssc(), + new ServiceManager(redissionConfig.client().getConfig())), + new RedissonObjectBuilder(redissionConfig.client())); + + } + + private record RedissionConfig (RedissonClient client, SingleServerConfig ssc) {} + + @Bean + RedissionConfig redissonConfig(@Value("${spring.redis.host}") String host, + @Value("${spring.redis.port}") String port) { + var address = "redis://%s:%s".formatted(host, port); + + var config = new Config(); + var singleServerConfig = config.useSingleServer() + .setAddress(address) + .setRetryAttempts(5); + return new RedissionConfig(Redisson.create(config), singleServerConfig); + } + +} diff --git a/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java new file mode 100644 index 00000000..ee3b5b31 --- /dev/null +++ b/examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/TestController.java @@ -0,0 +1,20 @@ +package com.giffing.bucket4j.spring.boot.starter; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @GetMapping("hello") + public ResponseEntity hello() { + return ResponseEntity.ok("Hello World"); + } + + @GetMapping("world") + public ResponseEntity world() { + return ResponseEntity.ok("Hello World"); + } + +} diff --git a/examples/redis-redisson/src/main/resources/application.yml b/examples/redis-redisson/src/main/resources/application.yml new file mode 100644 index 00000000..8a9c7d57 --- /dev/null +++ b/examples/redis-redisson/src/main/resources/application.yml @@ -0,0 +1,35 @@ +logging: + level: + com.giffing.bucket4j: debug +management: + endpoints: + web: + exposure: + include: "*" + security: + enabled: false + +bucket4j: + enabled: true + cache-to-use: redis-redisson + filters: + - cache-name: buckets_test + filter-method: webflux + url: ^(/hello).* + rate-limits: + - bandwidths: + - capacity: 5 + time: 10 + unit: seconds + - cache-name: buckets_test + filter-method: webflux + url: ^(/world).* + rate-limits: + - bandwidths: + - capacity: 10 + time: 10 + unit: seconds + +spring: + main: + allow-bean-definition-overriding: true diff --git a/examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java b/examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java new file mode 100644 index 00000000..00854130 --- /dev/null +++ b/examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/redis/RedisTest.java @@ -0,0 +1,96 @@ +package com.giffing.bucket4j.spring.boot.starter.examples.redis; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.Collections; +import java.util.stream.IntStream; + +@SpringBootTest +@AutoConfigureMockMvc +@Testcontainers +class RedisTest { + + @Container + static GenericContainer redis = + new GenericContainer(DockerImageName.parse("redis:7")) + .withExposedPorts(6379); + + @DynamicPropertySource + static void redisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.redis.host", () -> redis.getHost()); + registry.add("spring.redis.port", () -> redis.getFirstMappedPort()); + } + + @Autowired + ApplicationContext context; + + WebTestClient rest; + + @BeforeEach + public void setup() { + this.rest = WebTestClient + .bindToApplicationContext(this.context) + .configureClient() + .build(); + } + + @Test + void helloTest() throws Exception { + String url = "/hello"; + IntStream.rangeClosed(1, 5) + .boxed() + .sorted(Collections.reverseOrder()) + .forEach(counter -> { + successfulWebRequest(url, counter - 1); + }); + + blockedWebRequestDueToRateLimit(url); + } + + + @Test + void worldTest() throws Exception { + String url = "/world"; + IntStream.rangeClosed(1, 10) + .boxed() + .sorted(Collections.reverseOrder()) + .forEach(counter -> { + System.out.println(counter); + successfulWebRequest(url, counter - 1); + }); + + blockedWebRequestDueToRateLimit(url); + } + + private void successfulWebRequest(String url, Integer remainingTries) { + rest + .get() + .uri(url) + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals("X-Rate-Limit-Remaining", String.valueOf(remainingTries)); + } + + private void blockedWebRequestDueToRateLimit(String url) throws Exception { + rest + .get() + .uri(url) + .exchange() + .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) + .expectBody().jsonPath("error", "Too many requests!"); + } + +} diff --git a/pom.xml b/pom.xml index 70cca452..6da2249b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,10 +23,13 @@ examples/hazelcast examples/webflux examples/gateway - + examples/redis-jedis + examples/redis-lettuce + examples/redis-redisson + https://github.com/MarcGiffing/bucket4j-spring-boot-starter @@ -46,7 +49,7 @@ UTF-8 UTF-8 17 - 8.1.0 + 8.4.0 4.0.7