Skip to content

Commit 9749144

Browse files
authored
Feature/rate limit using bucket4j (#3)
* Rate Limit Spring Boot REST API using Bucket4j * Rate Limit Spring Boot REST API using Bucket4j * Added Spring security examples
1 parent 530b55d commit 9749144

File tree

108 files changed

+2135
-346
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+2135
-346
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
spring.data.mongodb.uri=mongodb://localhost:27017/movies?locale=en
2-
spring.data.mongodb.username=
3-
spring.data.mongodb.password=
2+
spring.data.mongodb.username=root
3+
spring.data.mongodb.password=Passw0rd
44

55
#open API path
66
springdoc.api-docs.path=/api-docs

ratelimit-api-using-bucket4j/pom.xml

+28-34
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,65 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
34
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<artifactId>ratelimit-api-using-bucket4j</artifactId>
6+
<description>Demo project for Spring Boot</description>
7+
<groupId>com.stacktips</groupId>
48
<modelVersion>4.0.0</modelVersion>
9+
<name>movies-api</name>
10+
<version>0.0.1-SNAPSHOT</version>
511
<parent>
6-
<groupId>org.springframework.boot</groupId>
712
<artifactId>spring-boot-starter-parent</artifactId>
8-
<version>3.3.2</version>
9-
<relativePath/> <!-- lookup parent from repository -->
13+
<groupId>org.springframework.boot</groupId>
14+
<relativePath/>
15+
<version>3.3.2</version> <!-- lookup parent from repository -->
1016
</parent>
11-
<groupId>com.stacktips</groupId>
12-
<artifactId>springboot-rest-api-monogdb</artifactId>
13-
<version>0.0.1-SNAPSHOT</version>
14-
<name>movies-api</name>
15-
<description>Demo project for Spring Boot</description>
1617
<properties>
1718
<java.version>17</java.version>
1819
</properties>
20+
<build>
21+
<plugins>
22+
<plugin>
23+
<artifactId>spring-boot-maven-plugin</artifactId>
24+
<groupId>org.springframework.boot</groupId>
25+
</plugin>
26+
</plugins>
27+
</build>
1928
<dependencies>
20-
<!-- For java 17+ -->
2129
<dependency>
22-
<groupId>com.bucket4j</groupId>
2330
<artifactId>bucket4j_jdk17-core</artifactId>
31+
<groupId>com.bucket4j</groupId>
2432
<version>8.13.1</version>
2533
</dependency>
34+
2635
<dependency>
27-
<groupId>org.springframework.boot</groupId>
28-
<artifactId>spring-boot-starter-data-mongodb</artifactId>
29-
</dependency>
30-
<dependency>
31-
<groupId>org.springframework.boot</groupId>
3236
<artifactId>spring-boot-starter-web</artifactId>
37+
<groupId>org.springframework.boot</groupId>
3338
</dependency>
3439

3540
<dependency>
36-
<groupId>org.springframework.boot</groupId>
3741
<artifactId>spring-boot-starter-test</artifactId>
42+
<groupId>org.springframework.boot</groupId>
3843
<scope>test</scope>
3944
</dependency>
4045

4146
<dependency>
42-
<groupId>io.rest-assured</groupId>
4347
<artifactId>rest-assured</artifactId>
44-
<version>5.3.1</version>
48+
<groupId>io.rest-assured</groupId>
4549
<scope>test</scope>
50+
<version>5.3.1</version>
4651
</dependency>
47-
4852
<dependency>
49-
<groupId>io.rest-assured</groupId>
5053
<artifactId>json-path</artifactId>
51-
<version>5.3.1</version>
54+
<groupId>io.rest-assured</groupId>
5255
<scope>test</scope>
56+
<version>5.3.1</version>
5357
</dependency>
58+
5459
<dependency>
55-
<groupId>org.projectlombok</groupId>
5660
<artifactId>lombok</artifactId>
61+
<groupId>org.projectlombok</groupId>
5762
<scope>annotationProcessor</scope>
5863
</dependency>
59-
6064
</dependencies>
61-
62-
<build>
63-
<plugins>
64-
<plugin>
65-
<groupId>org.springframework.boot</groupId>
66-
<artifactId>spring-boot-maven-plugin</artifactId>
67-
</plugin>
68-
</plugins>
69-
</build>
70-
7165
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.stacktips.movies;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class MyApiApplication {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(MyApiApplication.class, args);
11+
}
12+
13+
}

ratelimit-api-using-bucket4j/src/main/java/com/stacktips/movies/api/GlobalExceptionHandler.java

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.stacktips.movies.api;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.http.MediaType;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
import java.util.Collections;
10+
import java.util.Map;
11+
12+
@RestController
13+
@RequiredArgsConstructor
14+
@RequestMapping(value = "/hello",
15+
produces = {MediaType.APPLICATION_JSON_VALUE})
16+
public class HelloController {
17+
18+
@GetMapping
19+
public Map<String, String> hello() {
20+
return Collections.singletonMap("hello", "world!");
21+
}
22+
}

ratelimit-api-using-bucket4j/src/main/java/com/stacktips/movies/api/MoviesController.java

-45
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.stacktips.movies.config;
2+
3+
import lombok.Data;
4+
import org.springframework.boot.context.properties.ConfigurationProperties;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
import java.time.Duration;
8+
import java.util.Map;
9+
10+
@Data
11+
@Configuration
12+
@ConfigurationProperties(prefix = "rate-limiting")
13+
public class BucketConfig {
14+
15+
private Map<String, ClientBucketConfig> clients;
16+
17+
@Data
18+
public static class ClientBucketConfig {
19+
20+
private int capacity;
21+
private int refillTokens;
22+
private Duration refillDuration;
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.stacktips.movies.config;
22

3+
import org.springframework.beans.factory.annotation.Autowired;
34
import org.springframework.boot.web.servlet.FilterRegistrationBean;
45
import org.springframework.context.annotation.Bean;
56
import org.springframework.context.annotation.Configuration;
@@ -8,18 +9,19 @@
89
public class FilterConfig {
910

1011
// @Bean
11-
// public FilterRegistrationBean<RateLimitingFilter> RateLimitingFilter() {
12+
// public FilterRegistrationBean<RateLimitingFilter> rateLimitingFilter() {
1213
// FilterRegistrationBean<RateLimitingFilter> registrationBean = new FilterRegistrationBean<>();
1314
// registrationBean.setFilter(new RateLimitingFilter());
14-
// registrationBean.addUrlPatterns("/api/1.0/movies/*");
15+
// registrationBean.addUrlPatterns("/hello/*");
1516
// return registrationBean;
1617
// }
1718

1819
@Bean
19-
public FilterRegistrationBean<RateLimitingClientFilter> rateLimitingClientFilter() {
20+
public FilterRegistrationBean<RateLimitingClientFilter> rateLimitingClientFilter(
21+
@Autowired BucketConfig bucketConfig) {
2022
FilterRegistrationBean<RateLimitingClientFilter> registrationBean = new FilterRegistrationBean<>();
21-
registrationBean.setFilter(new RateLimitingClientFilter());
22-
registrationBean.addUrlPatterns("/api/1.0/movies/*");
23+
registrationBean.setFilter(new RateLimitingClientFilter(bucketConfig));
24+
registrationBean.addUrlPatterns("/hello/*");
2325
return registrationBean;
2426
}
2527
}

ratelimit-api-using-bucket4j/src/main/java/com/stacktips/movies/config/RateLimitingClientFilter.java

+17-19
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,35 @@
22

33
import io.github.bucket4j.Bucket;
44
import io.github.bucket4j.ConsumptionProbe;
5-
import jakarta.servlet.FilterConfig;
65
import jakarta.servlet.*;
76
import jakarta.servlet.http.HttpServletRequest;
87
import jakarta.servlet.http.HttpServletResponse;
8+
import lombok.RequiredArgsConstructor;
99
import org.springframework.http.HttpStatus;
1010
import org.springframework.http.MediaType;
1111

1212
import java.io.IOException;
13-
import java.time.Duration;
1413
import java.util.concurrent.ConcurrentHashMap;
1514
import java.util.concurrent.TimeUnit;
1615

16+
@RequiredArgsConstructor
1717
public class RateLimitingClientFilter implements Filter {
1818

19+
private final BucketConfig bucketConfig;
20+
1921
private final ConcurrentHashMap<String, Bucket> buckets = new ConcurrentHashMap<>();
2022

2123
@Override
2224
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
2325
throws IOException, ServletException {
2426
HttpServletResponse httpResponse = (HttpServletResponse) response;
2527
HttpServletRequest httpRequest = (HttpServletRequest) request;
26-
27-
String apiKey = httpRequest.getHeader("X-API-Key");
28+
String apiKey = httpRequest.getHeader("X-Client-ID");
2829
if (apiKey == null) {
30+
2931
httpResponse.setStatus(HttpStatus.BAD_REQUEST.value());
3032
httpResponse.setContentType(MediaType.TEXT_PLAIN_VALUE);
31-
httpResponse.getWriter().write("Missing X-API-Key header");
33+
httpResponse.getWriter().write("Missing X-Client-ID header");
3234
return;
3335
}
3436

@@ -46,20 +48,16 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
4648
}
4749
}
4850

49-
private Bucket createNewBucket(String apiKey) {
50-
return Bucket.builder()
51-
.addLimit(limit -> limit.capacity(10)
52-
.refillIntervally(1, Duration.ofMinutes(1)))
53-
.build();
54-
}
55-
56-
@Override
57-
public void init(FilterConfig filterConfig) {
58-
59-
}
60-
61-
@Override
62-
public void destroy() {
51+
private Bucket createNewBucket(String clientId) {
52+
BucketConfig.ClientBucketConfig config = bucketConfig.getClients().get(clientId);
53+
if (config == null) {
54+
throw new IllegalArgumentException("Unknown client: " + clientId);
55+
}
6356

57+
return Bucket.builder()
58+
.addLimit(limit ->
59+
limit.capacity(config.getCapacity())
60+
.refillIntervally(config.getRefillTokens(), config.getRefillDuration())
61+
).build();
6462
}
6563
}

0 commit comments

Comments
 (0)