spyder
spyder

Reputation: 126

Spring cloud ratelimit with bucket 4j and Hazelcast

I am using "com.giffing.bucket4j.spring.boot.starter" to implement ratelimit using hazelcast in my spring cloud project.

The bucket4j is not getting initialized on the application startup. Due to this the rate limiting is not working.

My project has limitation to use 3.1.2 spring boot version and as per the bucket4j giffing documentation I am using 0.10.3 version of bucket4j starter. But the rate limit is not working.

Please help.

Complete code base:

GatewaySampleApplication.java

package com.oardic.springbootbucket4j;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableCaching
public class GatewaySampleApplication {


    public static void main(String[] args) {
        SpringApplication.run(GatewaySampleApplication.class, args);
    }
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        //@formatter:off
        return builder.routes()
                 .route(p -> p
                            .path("/get")
                            .filters(f -> f.addRequestHeader("Hello", "World"))
                            .uri("http://httpbin.org:80"))
                .build();
        //@formatter:on
    }

}

MyController.java

package com.oardic.springbootbucket4j;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;

@RestController
@RequestMapping
public class MyController {

    @GetMapping("/hello")
    public Mono<String> hello(
        @RequestParam(defaultValue = "World") String name) {
        return Mono.just("Hello")
            .flatMap(s -> Mono
                .just(s + ", " + name + "!\n")
            );
    }
    
    @GetMapping("/world")
    public Mono<String> world(
        @RequestParam(defaultValue = "World") String name) {
        return Mono.just("Hello")
            .flatMap(s -> Mono
                .just(s + ", " + name + "!\n")
            );
    }
    
}

application.yml

spring:
  cache:
    type: jcache
    jcache:
      provider: com.hazelcast.cache.impl.HazelcastServerCachingProvider
      config: classpath:hazelcast.xml
management:
  endpoints:
    web:
      exposure:
        include: "*"
bucket4j:
    cache-to-use: hazelcast
    enabled: true
    filters:
    -   cache-name: buckets
        rate-limits:
        -   bandwidths:
            -   capacity: 1
                time: 5
                unit: seconds
            cache-key: '@securityService.getClientIP(#this)'
        url: /myservice/endpoint/*
debug: true
logging:
  file:
    name: temp.log
    path: D://temp.log

hazelcast.xml

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.hazelcast.com/schema/config
           http://www.hazelcast.com/schema/config/hazelcast-config-4.1.xsd">

    <map name="buckets">
        <time-to-live-seconds>120</time-to-live-seconds>
        <in-memory-format>BINARY</in-memory-format>
        <metadata-policy>CREATE_ON_UPDATE</metadata-policy>
        <statistics-enabled>true</statistics-enabled>
    </map>

    <cache name="buckets">
    </cache>

</hazelcast>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath />
    </parent>
    
    <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
    <artifactId>bucket4j-spring-boot-starter-example-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2022.0.3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
            <artifactId>bucket4j-spring-boot-starter</artifactId>
            <version>0.10.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.vladimir-bukhtoyarov</groupId>
            <artifactId>bucket4j-hazelcast</artifactId>
            <version>7.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.hazelcast</groupId>
            <artifactId>hazelcast</artifactId>
        </dependency>
        <dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <scope>provided</scope>
</dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Upvotes: 0

Views: 179

Answers (1)

spyder
spyder

Reputation: 126

Firstly giffing 0.10.3 doesnot have the required Async and Sync cache resolvers for hazelcast. Hence upgrading the griffing jar is required. For more info, you can check the PRs for this griffing repo.

Issue resolved with adding the updated spring boot starter bucket4j which is having async resolvers and also added buket4j related jars. bucket4j-hazelcast:com.bucket4j:8.10.1 and griffing 0.12.8 jars also use cache-to-use property to hazelcast-spring or hazelcast-reactive based on requirement.

Make sure the bucket4j dependecies are present in both hazelcast client and server.

Also when combined with this gateway, need to mandatorily include filter-method to WEBFLUX to register the filter with spring.

also use below props.

bucket4j.filters[1].filter-method=WEBFLUX
bucket4j.filters[1].cache-name=buckets
bucket4j.filters[1].url=^(/gateway/service).*
bucket4j.filters[1].rate-limits[0].bandwidths[0].capacity=10
bucket4j.filters[1].rate-limits[0].bandwidths[0].time=60
bucket4j.filters[1].rate-limits[0].bandwidths[0].unit=seconds
bucket4j.filters[1].rate-limits[0].bandwidths[0].refill-speed=interval

spring.cache.type=jcache
spring.cache.jcache.provider=com.hazelcast.client.cache.HazelcastClientCachingProvider

Upvotes: 0

Related Questions