diff --git a/bucket4j-spring-boot-starter/pom.xml b/bucket4j-spring-boot-starter/pom.xml index c05deb23..1060a462 100644 --- a/bucket4j-spring-boot-starter/pom.xml +++ b/bucket4j-spring-boot-starter/pom.xml @@ -17,7 +17,7 @@ UTF-8 UTF-8 17 - 3.23.5 + 3.24.3 2.15.0 diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/Bucket4JBaseConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/Bucket4JBaseConfiguration.java index 8282be33..357fe535 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/Bucket4JBaseConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/Bucket4JBaseConfiguration.java @@ -29,7 +29,6 @@ import io.github.bucket4j.Bandwidth; import io.github.bucket4j.BucketConfiguration; import io.github.bucket4j.ConfigurationBuilder; -import io.github.bucket4j.Refill; import lombok.extern.slf4j.Slf4j; /** @@ -138,14 +137,14 @@ private ConfigurationBuilder prepareBucket4jConfigurationBuilder(RateLimit rl) { long refillCapacity = bandWidth.getRefillCapacity() != null ? bandWidth.getRefillCapacity() : bandWidth.getCapacity(); var refillPeriod = Duration.of(bandWidth.getTime(), bandWidth.getUnit()); var bucket4jBandWidth = switch(bandWidth.getRefillSpeed()) { - case GREEDY -> Bandwidth.classic(capacity, Refill.greedy(refillCapacity, refillPeriod)).withId(bandWidth.getId()); - case INTERVAL -> Bandwidth.classic(capacity, Refill.intervally(refillCapacity, refillPeriod)).withId(bandWidth.getId()); - default -> throw new IllegalStateException("Unsupported Refill type: " + bandWidth.getRefillSpeed()); - }; + case GREEDY -> Bandwidth.builder().capacity(capacity).refillGreedy(refillCapacity, refillPeriod).id(bandWidth.getId()); + case INTERVAL -> Bandwidth.builder().capacity(capacity).refillIntervally(refillCapacity, refillPeriod).id(bandWidth.getId()); + }; + if(bandWidth.getInitialCapacity() != null) { - bucket4jBandWidth = bucket4jBandWidth.withInitialTokens(bandWidth.getInitialCapacity()); + bucket4jBandWidth = bucket4jBandWidth.initialTokens(bandWidth.getInitialCapacity()); } - configBuilder = configBuilder.addLimit(bucket4jBandWidth); + configBuilder = configBuilder.addLimit(bucket4jBandWidth.build()); } return configBuilder; } diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/AbstractReactiveFilter.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/AbstractReactiveFilter.java index 336560c1..a85e2d0d 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/AbstractReactiveFilter.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/AbstractReactiveFilter.java @@ -2,9 +2,11 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import com.giffing.bucket4j.spring.boot.starter.context.ConsumptionProbeHolder; +import com.giffing.bucket4j.spring.boot.starter.context.RateLimitCheck; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -22,9 +24,9 @@ @Data @Slf4j public class AbstractReactiveFilter { - + private FilterConfiguration filterConfig; - + public AbstractReactiveFilter(FilterConfiguration filterConfig) { this.filterConfig = filterConfig; } @@ -36,37 +38,28 @@ public void setFilterConfig(FilterConfiguration filterConfig) protected boolean urlMatches(ServerHttpRequest request) { return request.getURI().getPath().matches(filterConfig.getUrl()); } - + protected Mono chainWithRateLimitCheck(ServerWebExchange exchange, ReactiveFilterChain chain) { log.debug("reate-limit-check;method:{};uri:{}", exchange.getRequest().getMethod(), exchange.getRequest().getURI()); ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); - List> asyncConsumptionProbes = filterConfig.getRateLimitChecks() - .stream() - .map(rl -> rl.rateLimit(request)) - .filter(cph -> cph != null && cph.getConsumptionProbeCompletableFuture() != null) - .map(cph -> Mono.fromFuture(cph.getConsumptionProbeCompletableFuture())) - .toList(); + List> asyncConsumptionProbes = new ArrayList<>(); + for (RateLimitCheck rlc : filterConfig.getRateLimitChecks()) { + ConsumptionProbeHolder cph = rlc.rateLimit(request); + if(cph != null && cph.getConsumptionProbeCompletableFuture() != null){ + asyncConsumptionProbes.add(Mono.fromFuture(cph.getConsumptionProbeCompletableFuture())); + if(filterConfig.getStrategy() == RateLimitConditionMatchingStrategy.FIRST){ + break; + } + } + } if(asyncConsumptionProbes.isEmpty()) { return chain.apply(exchange); } - AtomicInteger consumptionProbeCounter = new AtomicInteger(0); return Flux - .concat(asyncConsumptionProbes) - //.takeWhile(Objects::nonNull) - .doOnNext(cp -> consumptionProbeCounter.incrementAndGet()) - .takeWhile(cp -> shouldTakeMoreConsumptionProbe(consumptionProbeCounter)) - .reduce(this::reduceConsumptionProbe) - .flatMap(consumptionProbe -> handleConsumptionProbe(exchange, chain, response, consumptionProbe)); - - } - - protected boolean shouldTakeMoreConsumptionProbe(AtomicInteger consumptionProbeCounter) { - boolean shouldTakeMore = filterConfig.getStrategy().equals(RateLimitConditionMatchingStrategy.ALL) - || - (filterConfig.getStrategy().equals(RateLimitConditionMatchingStrategy.FIRST) && consumptionProbeCounter.get() == 1); - log.debug("take-more-probes:{};probe-index:{};matching-strategy:{}", shouldTakeMore, consumptionProbeCounter.get(), filterConfig.getStrategy()); - return shouldTakeMore; + .concat(asyncConsumptionProbes) + .reduce(this::reduceConsumptionProbe) + .flatMap(consumptionProbe -> handleConsumptionProbe(exchange, chain, response, consumptionProbe)); } protected ConsumptionProbe reduceConsumptionProbe(ConsumptionProbe x, ConsumptionProbe y) { @@ -76,7 +69,7 @@ protected ConsumptionProbe reduceConsumptionProbe(ConsumptionProbe x, Consumptio } else if(!y.isConsumed()) { result = y; } else { - result = x.getRemainingTokens() < y.getRemainingTokens() ? x : y; + result = x.getRemainingTokens() < y.getRemainingTokens() ? x : y; } log.debug("reduce-probes;result-isConsumed:{};result-getremainingTokens:{};x-isConsumed:{};x-getremainingTokens{};y-isConsumed:{};y-getremainingTokens{}", result.isConsumed(), result.getRemainingTokens(), @@ -84,15 +77,15 @@ protected ConsumptionProbe reduceConsumptionProbe(ConsumptionProbe x, Consumptio y.isConsumed(), y.getRemainingTokens()); return result; } - + protected Mono handleConsumptionProbe(ServerWebExchange exchange, ReactiveFilterChain chain, ServerHttpResponse response, ConsumptionProbe cp) { - log.debug("probe-results;isConsumed:{};remainingTokens:{};nanosToWaitForRefill:{};nanosToWaitForReset:{}", - cp.isConsumed(), - cp.getRemainingTokens(), - cp.getNanosToWaitForRefill(), + log.debug("probe-results;isConsumed:{};remainingTokens:{};nanosToWaitForRefill:{};nanosToWaitForReset:{}", + cp.isConsumed(), + cp.getRemainingTokens(), + cp.getNanosToWaitForRefill(), cp.getNanosToWaitForReset()); - + if(!cp.isConsumed()) { if(Boolean.FALSE.equals(filterConfig.getHideHttpResponseHeaders())) { filterConfig.getHttpResponseHeaders().forEach(response.getHeaders()::addIfAbsent); @@ -101,7 +94,7 @@ protected Mono handleConsumptionProbe(ServerWebExchange exchange, Reactive response.setStatusCode(filterConfig.getHttpStatusCode()); response.getHeaders().set("Content-Type", filterConfig.getHttpContentType()); DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(filterConfig.getHttpResponseBody().getBytes(UTF_8)); - return response.writeWith(Flux.just(buffer)); + return response.writeWith(Flux.just(buffer)); } else { return Mono.error(new ReactiveRateLimitException("HTTP ResponseBody is null")); } @@ -112,6 +105,4 @@ protected Mono handleConsumptionProbe(ServerWebExchange exchange, Reactive } return chain.apply(exchange); } - - } diff --git a/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/gateway/SpringCloudGatewayRateLimitFilterTest.java b/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/gateway/SpringCloudGatewayRateLimitFilterTest.java index 8b0f31f7..2f9fbebb 100644 --- a/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/gateway/SpringCloudGatewayRateLimitFilterTest.java +++ b/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/gateway/SpringCloudGatewayRateLimitFilterTest.java @@ -39,52 +39,52 @@ class SpringCloudGatewayRateLimitFilterTest { private GlobalFilter filter; - private FilterConfiguration configuration; - private RateLimitCheck rateLimitCheck1; - private RateLimitCheck rateLimitCheck2; - private RateLimitCheck rateLimitCheck3; + private FilterConfiguration configuration; + private RateLimitCheck rateLimitCheck1; + private RateLimitCheck rateLimitCheck2; + private RateLimitCheck rateLimitCheck3; private ServerWebExchange exchange; private GatewayFilterChain chain; - - + + private ServerHttpResponse serverHttpResponse; - + @BeforeEach - public void setup() throws URISyntaxException { - rateLimitCheck1 = mock(RateLimitCheck.class); - rateLimitCheck2 = mock(RateLimitCheck.class); - rateLimitCheck3 = mock(RateLimitCheck.class); - - exchange = Mockito.mock(ServerWebExchange.class); - - ServerHttpRequest serverHttpRequest = Mockito.mock(ServerHttpRequest.class); - URI uri = new URI("url"); - when(serverHttpRequest.getURI()).thenReturn(uri); + public void setup() throws URISyntaxException { + rateLimitCheck1 = mock(RateLimitCheck.class); + rateLimitCheck2 = mock(RateLimitCheck.class); + rateLimitCheck3 = mock(RateLimitCheck.class); + + exchange = Mockito.mock(ServerWebExchange.class); + + ServerHttpRequest serverHttpRequest = Mockito.mock(ServerHttpRequest.class); + URI uri = new URI("url"); + when(serverHttpRequest.getURI()).thenReturn(uri); when(exchange.getRequest()).thenReturn(serverHttpRequest); - + serverHttpResponse = Mockito.mock(ServerHttpResponse.class); - when(exchange.getResponse()).thenReturn(serverHttpResponse); - + when(exchange.getResponse()).thenReturn(serverHttpResponse); + chain = Mockito.mock(GatewayFilterChain.class); when(chain.filter(exchange)).thenReturn(Mono.empty()); - - configuration = new FilterConfiguration(); - configuration.setRateLimitChecks(Arrays.asList(rateLimitCheck1, rateLimitCheck2, rateLimitCheck3)); - configuration.setUrl(".*"); - filter = new SpringCloudGatewayRateLimitFilter(configuration); - } - - @Test + + configuration = new FilterConfiguration<>(); + configuration.setRateLimitChecks(Arrays.asList(rateLimitCheck1, rateLimitCheck2, rateLimitCheck3)); + configuration.setUrl(".*"); + filter = new SpringCloudGatewayRateLimitFilter(configuration); + } + + @Test void should_throw_rate_limit_exception_with_no_remaining_tokens() { - + configuration.setStrategy(RateLimitConditionMatchingStrategy.FIRST); - rateLimitConfig(0L, rateLimitCheck1); - HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); - when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); - - AtomicBoolean hasRateLimitError = new AtomicBoolean(false); + rateLimitConfig(0L, rateLimitCheck1); + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); + + AtomicBoolean hasRateLimitError = new AtomicBoolean(false); Mono result = filter.filter(exchange, chain) .onErrorResume(ReactiveRateLimitException.class, (e) -> { hasRateLimitError.set(true); @@ -93,63 +93,62 @@ void should_throw_rate_limit_exception_with_no_remaining_tokens() { result.subscribe(); Assertions.assertTrue(hasRateLimitError.get()); } - + @Test void should_execute_all_checks_when_using_RateLimitConditionMatchingStrategy_All() throws URISyntaxException { - - configuration.setStrategy(RateLimitConditionMatchingStrategy.ALL); - - rateLimitConfig(30L, rateLimitCheck1); - rateLimitConfig(0L, rateLimitCheck2); - rateLimitConfig(0L, rateLimitCheck3); - - HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); - when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); - final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - - Mono result = filter.filter(exchange, chain); - assertThrows(ReactiveRateLimitException.class, () -> { - result.block(); - }); - + + configuration.setStrategy(RateLimitConditionMatchingStrategy.ALL); + + rateLimitConfig(30L, rateLimitCheck1); + rateLimitConfig(0L, rateLimitCheck2); + rateLimitConfig(0L, rateLimitCheck3); + + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + + Mono result = filter.filter(exchange, chain); + assertThrows(ReactiveRateLimitException.class, () -> { + result.block(); + }); + verify(rateLimitCheck1, times(1)).rateLimit(any()); - verify(rateLimitCheck2, times(1)).rateLimit(any()); - verify(rateLimitCheck3, times(1)).rateLimit(any()); + verify(rateLimitCheck2, times(1)).rateLimit(any()); + verify(rateLimitCheck3, times(1)).rateLimit(any()); } @Test void should_execute_only_one_check_when_using_RateLimitConditionMatchingStrategy_FIRST() { - configuration.setStrategy(RateLimitConditionMatchingStrategy.FIRST); - - rateLimitConfig(30L, rateLimitCheck1); - rateLimitConfig(0L, rateLimitCheck2); - rateLimitConfig(10L, rateLimitCheck3); - - HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); - when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); - final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - + configuration.setStrategy(RateLimitConditionMatchingStrategy.FIRST); + + rateLimitConfig(30L, rateLimitCheck1); + rateLimitConfig(0L, rateLimitCheck2); + rateLimitConfig(10L, rateLimitCheck3); + + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + Mono result = filter.filter(exchange, chain); result.block(); - - verify(httpHeaders, times(1)).set(any(), captor.capture()); - - List values = captor.getAllValues(); - Assertions.assertEquals("30", values.stream().findFirst().get()); - - verify(rateLimitCheck1, times(1)).rateLimit(any()); - verify(rateLimitCheck2, times(1)).rateLimit(any()); - verify(rateLimitCheck3, times(1)).rateLimit(any()); + + verify(httpHeaders, times(1)).set(any(), captor.capture()); + + List values = captor.getAllValues(); + Assertions.assertEquals("30", values.stream().findFirst().get()); + + verify(rateLimitCheck1, times(1)).rateLimit(any()); + verify(rateLimitCheck2, times(0)).rateLimit(any()); + verify(rateLimitCheck3, times(0)).rateLimit(any()); } - private void rateLimitConfig(Long remainingTokens, RateLimitCheck rateLimitCheck) { + private void rateLimitConfig(Long remainingTokens, RateLimitCheck rateLimitCheck) { ConsumptionProbeHolder consumptionHolder = Mockito.mock(ConsumptionProbeHolder.class); - ConsumptionProbe probe = Mockito.mock(ConsumptionProbe.class); - when(probe.isConsumed()).thenReturn(remainingTokens > 0 ? true : false); + ConsumptionProbe probe = Mockito.mock(ConsumptionProbe.class); + when(probe.isConsumed()).thenReturn(remainingTokens > 0); when(probe.getRemainingTokens()).thenReturn(remainingTokens); when(consumptionHolder.getConsumptionProbeCompletableFuture()) - .thenReturn(CompletableFuture.completedFuture(probe)); - when(rateLimitCheck.rateLimit(any())).thenReturn(consumptionHolder); + .thenReturn(CompletableFuture.completedFuture(probe)); + when(rateLimitCheck.rateLimit(any())).thenReturn(consumptionHolder); } - } diff --git a/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/webflux/WebfluxRateLimitFilterTest.java b/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/webflux/WebfluxRateLimitFilterTest.java index 24213f5e..aa2b9350 100644 --- a/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/webflux/WebfluxRateLimitFilterTest.java +++ b/bucket4j-spring-boot-starter/src/test/java/com/giffing/bucket4j/spring/boot/starter/webflux/WebfluxRateLimitFilterTest.java @@ -38,50 +38,50 @@ class WebfluxRateLimitFilterTest { private WebfluxWebFilter filter; private FilterConfiguration configuration; - private RateLimitCheck rateLimitCheck1; - private RateLimitCheck rateLimitCheck2; - private RateLimitCheck rateLimitCheck3; + private RateLimitCheck rateLimitCheck1; + private RateLimitCheck rateLimitCheck2; + private RateLimitCheck rateLimitCheck3; private ServerWebExchange exchange; private WebFilterChain chain; - - + + private ServerHttpResponse serverHttpResponse; - + @BeforeEach - public void setup() throws URISyntaxException { - rateLimitCheck1 = mock(RateLimitCheck.class); - rateLimitCheck2 = mock(RateLimitCheck.class); - rateLimitCheck3 = mock(RateLimitCheck.class); - - exchange = Mockito.mock(ServerWebExchange.class); - - ServerHttpRequest serverHttpRequest = Mockito.mock(ServerHttpRequest.class); - URI uri = new URI("url"); - when(serverHttpRequest.getURI()).thenReturn(uri); + public void setup() throws URISyntaxException { + rateLimitCheck1 = mock(RateLimitCheck.class); + rateLimitCheck2 = mock(RateLimitCheck.class); + rateLimitCheck3 = mock(RateLimitCheck.class); + + exchange = Mockito.mock(ServerWebExchange.class); + + ServerHttpRequest serverHttpRequest = Mockito.mock(ServerHttpRequest.class); + URI uri = new URI("url"); + when(serverHttpRequest.getURI()).thenReturn(uri); when(exchange.getRequest()).thenReturn(serverHttpRequest); - + serverHttpResponse = Mockito.mock(ServerHttpResponse.class); - when(exchange.getResponse()).thenReturn(serverHttpResponse); - + when(exchange.getResponse()).thenReturn(serverHttpResponse); + chain = Mockito.mock(WebFilterChain.class); when(chain.filter(exchange)).thenReturn(Mono.empty()); - - configuration = new FilterConfiguration<>(); - configuration.setRateLimitChecks(Arrays.asList(rateLimitCheck1, rateLimitCheck2, rateLimitCheck3)); - configuration.setUrl(".*"); - filter = new WebfluxWebFilter(configuration); - } + + configuration = new FilterConfiguration<>(); + configuration.setRateLimitChecks(Arrays.asList(rateLimitCheck1, rateLimitCheck2, rateLimitCheck3)); + configuration.setUrl(".*"); + filter = new WebfluxWebFilter(configuration); + } @Test void should_throw_rate_limit_exception_with_no_remaining_tokens() { - + configuration.setStrategy(RateLimitConditionMatchingStrategy.FIRST); - rateLimitConfig(0L, rateLimitCheck1); - HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); - when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); - - AtomicBoolean hasRateLimitError = new AtomicBoolean(false); + rateLimitConfig(0L, rateLimitCheck1); + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); + + AtomicBoolean hasRateLimitError = new AtomicBoolean(false); Mono result = filter.filter(exchange, chain) .onErrorResume(ReactiveRateLimitException.class, (e) -> { hasRateLimitError.set(true); @@ -90,63 +90,63 @@ public void setup() throws URISyntaxException { result.subscribe(); Assertions.assertTrue(hasRateLimitError.get()); } - + @Test void should_execute_all_checks_when_using_RateLimitConditionMatchingStrategy_All() throws URISyntaxException { - - configuration.setStrategy(RateLimitConditionMatchingStrategy.ALL); - - rateLimitConfig(30L, rateLimitCheck1); - rateLimitConfig(0L, rateLimitCheck2); - rateLimitConfig(0L, rateLimitCheck3); - - HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); - when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); - - Assertions.assertThrows(ReactiveRateLimitException.class, () -> { - Mono result = filter.filter(exchange, chain); - result.block(); - }); - + + configuration.setStrategy(RateLimitConditionMatchingStrategy.ALL); + + rateLimitConfig(30L, rateLimitCheck1); + rateLimitConfig(0L, rateLimitCheck2); + rateLimitConfig(0L, rateLimitCheck3); + + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); + + Assertions.assertThrows(ReactiveRateLimitException.class, () -> { + Mono result = filter.filter(exchange, chain); + result.block(); + }); + verify(rateLimitCheck1).rateLimit(any()); - verify(rateLimitCheck2).rateLimit(any()); - verify(rateLimitCheck3).rateLimit(any()); + verify(rateLimitCheck2).rateLimit(any()); + verify(rateLimitCheck3).rateLimit(any()); } @Test void should_execute_only_one_check_when_using_RateLimitConditionMatchingStrategy_FIRST() { - - configuration.setStrategy(RateLimitConditionMatchingStrategy.FIRST); - - rateLimitConfig(30L, rateLimitCheck1); - rateLimitConfig(0L, rateLimitCheck2); - rateLimitConfig(10L, rateLimitCheck3); - - HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); - when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); - - Mono result = filter.filter(exchange, chain); + + configuration.setStrategy(RateLimitConditionMatchingStrategy.FIRST); + + rateLimitConfig(30L, rateLimitCheck1); + rateLimitConfig(0L, rateLimitCheck2); + rateLimitConfig(10L, rateLimitCheck3); + + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(serverHttpResponse.getHeaders()).thenReturn(httpHeaders); + + Mono result = filter.filter(exchange, chain); result.block(); - + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(httpHeaders, times(1)).set(any(), captor.capture()); - - List values = captor.getAllValues(); - Assertions.assertEquals("30", values.stream().findFirst().get()); - - verify(rateLimitCheck1).rateLimit(any()); - verify(rateLimitCheck2).rateLimit(any()); - verify(rateLimitCheck3).rateLimit(any()); + verify(httpHeaders, times(1)).set(any(), captor.capture()); + + List values = captor.getAllValues(); + Assertions.assertEquals("30", values.stream().findFirst().get()); + + verify(rateLimitCheck1, times(1)).rateLimit(any()); + verify(rateLimitCheck2, times(0)).rateLimit(any()); + verify(rateLimitCheck3, times(0)).rateLimit(any()); } - private void rateLimitConfig(Long remainingTokens, RateLimitCheck rateLimitCheck) { + private void rateLimitConfig(Long remainingTokens, RateLimitCheck rateLimitCheck) { ConsumptionProbeHolder consumptionHolder = Mockito.mock(ConsumptionProbeHolder.class); - ConsumptionProbe probe = Mockito.mock(ConsumptionProbe.class); - when(probe.isConsumed()).thenReturn(remainingTokens > 0 ? true : false); + ConsumptionProbe probe = Mockito.mock(ConsumptionProbe.class); + when(probe.isConsumed()).thenReturn(remainingTokens > 0); when(probe.getRemainingTokens()).thenReturn(remainingTokens); when(consumptionHolder.getConsumptionProbeCompletableFuture()) - .thenReturn(CompletableFuture.completedFuture(probe)); - when(rateLimitCheck.rateLimit(any())).thenReturn(consumptionHolder); + .thenReturn(CompletableFuture.completedFuture(probe)); + when(rateLimitCheck.rateLimit(any())).thenReturn(consumptionHolder); } - + } diff --git a/examples/ehcache/pom.xml b/examples/ehcache/pom.xml index b73429a4..f876405e 100644 --- a/examples/ehcache/pom.xml +++ b/examples/ehcache/pom.xml @@ -12,6 +12,7 @@ true + 4.0.4 @@ -58,12 +59,12 @@ com.sun.xml.bind jaxb-core - 4.0.3 + ${jaxb-core.version} com.sun.xml.bind jaxb-impl - 4.0.3 + ${jaxb-core.version} org.ehcache diff --git a/examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/config/security/SecurityConfig.java b/examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/config/security/SecurityConfig.java index 7737da55..5f5fc65d 100644 --- a/examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/config/security/SecurityConfig.java +++ b/examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/config/security/SecurityConfig.java @@ -3,6 +3,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -14,16 +15,20 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests().requestMatchers("/unsecure").permitAll(); - http.authorizeHttpRequests().requestMatchers("/login").permitAll(); - http.authorizeHttpRequests().requestMatchers("/secure").hasAnyRole("ADMIN", "USER"); - http.authorizeHttpRequests().requestMatchers("/hello").permitAll(); - http.authorizeHttpRequests().requestMatchers("/filters/**").permitAll(); - return http.csrf().disable().build(); + http.csrf(AbstractHttpConfigurer::disable); + http.authorizeHttpRequests(auth -> { + auth.requestMatchers("/unsecure").permitAll(); + auth.requestMatchers("/actuator/*").permitAll(); + auth.requestMatchers("/login").permitAll(); + auth.requestMatchers("/filters/**").permitAll(); + auth.requestMatchers("/hello").permitAll(); + auth.requestMatchers("/secure").hasAnyRole("ADMIN", "USER"); + }); + return http.build(); } @Bean - public UserDetailsService inMemoryUser() throws Exception { + public UserDetailsService inMemoryUser() { UserDetails user = User.builder() .username("admin") .password("123") diff --git a/examples/redis-lettuce/pom.xml b/examples/redis-lettuce/pom.xml index 63863e2c..6429103c 100644 --- a/examples/redis-lettuce/pom.xml +++ b/examples/redis-lettuce/pom.xml @@ -17,7 +17,7 @@ 17 UTF-8 1.6.4 - 6.2.6.RELEASE + 6.3.0.RELEASE diff --git a/examples/webflux-infinispan/pom.xml b/examples/webflux-infinispan/pom.xml index 3831cd7c..73a6a364 100644 --- a/examples/webflux-infinispan/pom.xml +++ b/examples/webflux-infinispan/pom.xml @@ -45,13 +45,7 @@ org.infinispan - infinispan-spring-boot-starter-embedded - - - org.infinispan - infinispan-core - - + infinispan-spring-boot3-starter-embedded org.infinispan diff --git a/examples/webflux-infinispan/src/main/resources/infinispan.xml b/examples/webflux-infinispan/src/main/resources/infinispan.xml index ad8fc0b0..0e2861d0 100644 --- a/examples/webflux-infinispan/src/main/resources/infinispan.xml +++ b/examples/webflux-infinispan/src/main/resources/infinispan.xml @@ -6,5 +6,7 @@ + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index ea3588e2..678d4524 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.4 + 3.1.6 @@ -25,11 +25,10 @@ examples/gateway examples/redis-jedis examples/redis-lettuce + examples/webflux-infinispan - --> https://github.com/MarcGiffing/bucket4j-spring-boot-starter @@ -45,12 +44,12 @@ bucket4j-spring-boot-starter-parent-0.3.4 - 0.10.0-SNAPSHOT + 0.10.1 UTF-8 UTF-8 17 - 8.4.0 - 4.0.7 + 8.7.0 + 4.0.8