Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9749144

Browse files
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
Lines changed: 2 additions & 2 deletions
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‎

Lines changed: 28 additions & 34 deletions
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>
Lines changed: 13 additions & 0 deletions
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‎

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
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‎

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
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+
}
Lines changed: 7 additions & 5 deletions
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‎

Lines changed: 17 additions & 19 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /