diff --git a/README.adoc b/README.adoc index 6318ec29..ccc75540 100644 --- a/README.adoc +++ b/README.adoc @@ -17,6 +17,8 @@ Project version overview: == Contents * <> +** +** * <> ** <> *** <> @@ -41,8 +43,6 @@ Project version overview: This project is a Spring Boot Starter for Bucket4j, allowing you to set access limits on your API effortlessly. Its key advantage lies in the configuration via properties or yaml files, eliminating the need for manual code authoring. - - Here are some example use cases: * Preventing DoS Attacks @@ -60,7 +60,9 @@ The project offers several features, some utilizing Spring's Expression Language You have two options for rate limit configuration: adding a filter for incoming web requests or applying fine-grained control at the method level. -=== Filter + +[[introduction_filter]] +=== Use Filter for rate limiting Filters are customizable components designed to intercept incoming web requests, capable of rejecting requests to halt further processing. You can incorporate multiple filters for various URLs or opt to bypass rate limits entirely for authenticated users. When the limit is exceeded, the web request is aborted, and the client receives an HTTP Status 429 Too Many Requests error. @@ -80,7 +82,8 @@ bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds bucket4j.filters[0].rate-limits[0].bandwidths[0].refill-speed=intervall ---- -=== Method + +=== Use Annotations on methods for rate limiting Utilizing the '@RateLimiting' annotation, AOP intercepts your method. This grants you comprehensive access to method parameters, empowering you to define the rate limit key or conditionally skip rate limiting with ease. @@ -99,23 +102,29 @@ bucket4j.methods[0].rate-limits[0].bandwidths[0].refill-speed=intervall @RateLimiting( // reference to the property file name = "not_an_admin", + // the rate limit is per user + cacheKey= "#username", // only when the parameter is not admin - executeCondition = "#myParamName != 'admin'", + executeCondition = "#username != 'admin'", + // skip when parameter equals admin + skipCondition = "#username eq 'admin", // the method name is added to the cache key to prevent conflicts with other methods ratePerMethod = true, // if the limit is exceeded the fallback method is called. If not provided an exception is thrown fallbackMethodName = "myFallbackMethod") - public String execute(String myParamName) { - log.info("Method with Param {} executed", myParamName); + public String execute(String username) { + log.info("Method with Param {} executed", username); return myParamName; } - public String myFallbackMethod(String myParamName) { - log.info("Fallback-Method with Param {} executed", myParamName); + // the fallback method must have the same signature + public String myFallbackMethod(String username) { + log.info("Fallback-Method with Param {} executed", username); return myParamName; } ---- +You can find some Configuration examples in the test project: {url-examples}/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method[Examples] [[project_configuration]] == Project Configuration diff --git a/examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuiteTest.java b/examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuiteTest.java index 9dce3395..0d4e6828 100644 --- a/examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuiteTest.java +++ b/examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuiteTest.java @@ -1,6 +1,6 @@ package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; -import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.method.MethodTestSuite; +import com.giffing.bucket4j.spring.boot.starter.general.tests.method.method.MethodTestSuite; import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodRateLimitTest.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodRateLimitTest.java similarity index 84% rename from examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodRateLimitTest.java rename to examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodRateLimitTest.java index f1a4b80e..21e33600 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodRateLimitTest.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodRateLimitTest.java @@ -1,4 +1,4 @@ -package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.method; +package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; import com.giffing.bucket4j.spring.boot.starter.context.RateLimitException; import lombok.RequiredArgsConstructor; @@ -85,4 +85,16 @@ public void assert_rate_limit_with_cache_key() { assertThrows(RateLimitException.class, () -> testService.withCacheKey("key2")); } + @Test + public void assert_rate_limit_with_rate_per_method() { + for(int i = 0; i < 5; i++) { + // rate limit by parameter value + testService.withRatePerMethod1("key1"); + testService.withRatePerMethod2("key2"); + // all tokens consumed + } + assertThrows(RateLimitException.class, () -> testService.withRatePerMethod1("key1")); + assertThrows(RateLimitException.class, () -> testService.withRatePerMethod2("key2")); + } + } diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodTestApplication.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestApplication.java similarity index 85% rename from examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodTestApplication.java rename to examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestApplication.java index 39ba101e..4bc7f6b5 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodTestApplication.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestApplication.java @@ -1,4 +1,4 @@ -package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.method; +package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodTestSuite.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestSuite.java similarity index 70% rename from examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodTestSuite.java rename to examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestSuite.java index a0e9764e..3a2c4ede 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/MethodTestSuite.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestSuite.java @@ -1,4 +1,4 @@ -package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.method; +package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/TestService.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/TestService.java similarity index 70% rename from examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/TestService.java rename to examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/TestService.java index 5ec5c18e..05be69e3 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/method/TestService.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/TestService.java @@ -1,4 +1,4 @@ -package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.method; +package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; import lombok.extern.slf4j.Slf4j; @@ -33,6 +33,23 @@ public String withCacheKey(String cacheKey) { return cacheKey; } + @RateLimiting( + name = "default", + ratePerMethod = true) + public String withRatePerMethod1(String cacheKey) { + log.info("Method withRatePerMethod1 with Param {} executed", cacheKey); + return cacheKey; + } + + @RateLimiting( + name = "default", + ratePerMethod = true) + public String withRatePerMethod2(String cacheKey) { + log.info("Method withRatePerMethod1 with Param {} executed", cacheKey); + return cacheKey; + } + + @RateLimiting(name = "default", cacheKey = "'normal'", fallbackMethodName = "fallbackMethod") public String withFallbackMethod(String myParamName) { return "normal-method-executed;param:" + myParamName;