Skip to content

Commit

Permalink
- Feature Adding limit rate to endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
FarukBraimo committed Jun 18, 2024
1 parent 07eeec6 commit 498ba01
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 88 deletions.
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ services:
POSTGRES_PASSWORD: 123
POSTGRES_DB: falcon
ports:
- "5432:5432"
- "5432:5432"
160 changes: 84 additions & 76 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,83 +1,91 @@
<?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.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vodacom</groupId>
<artifactId>falcon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>falcon-api</name>
<description>Falcon: Travel Assistant API</description>
<properties>
<java.version>17</java.version>
<failsafe.version>3.3.2</failsafe.version>
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.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vodacom</groupId>
<artifactId>falcon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>falcon-api</name>
<description>Falcon: Travel Assistant API</description>
<properties>
<java.version>17</java.version>
<failsafe.version>3.3.2</failsafe.version>

</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>dev.failsafe</groupId>
<artifactId>failsafe</artifactId>
<version>${failsafe.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>dev.failsafe</groupId>
<artifactId>failsafe</artifactId>
<version>${failsafe.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!-- https://mvnrepository.com/artifact/com.bucket4j/bucket4j-core -->
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.7.0</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>
28 changes: 18 additions & 10 deletions src/main/java/com/vodacom/falcon/client/APICaller.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.vodacom.falcon.client;

import dev.failsafe.Failsafe;
import dev.failsafe.RateLimiter;
import dev.failsafe.RetryPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -9,14 +10,13 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

public class APICaller {
private static final Logger LOGGER = LoggerFactory.getLogger(APICaller.class);

public static HttpResponse<String> getData(String url) {
LOGGER.info("Getting data from {}... url ", url);

try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
Expand All @@ -32,14 +32,18 @@ public static HttpResponse<String> getData(String url) {
}

private static HttpResponse<String> getDataWithFaultTolerance(HttpRequest request) {
return Failsafe.with(buildRetryPolicy()).get(() -> {
HttpResponse<String> response = buildHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new Exception(String.format("Error requesting resource: %s with status code: %s , body: %s", request.uri(), response.statusCode(), response.body()));
}
return response;
});
if (getRateLimit().tryAcquirePermit()) {
return Failsafe.with(buildRetryPolicy()).get(() -> {
HttpResponse<String> response = buildHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new Exception(String.format("Error requesting resource: %s with status code: %s , body: %s", request.uri(), response.statusCode(), response.body()));
}
return response;
});
}

return null;
}

private static RetryPolicy<HttpResponse<String>> buildRetryPolicy() {
Expand All @@ -52,6 +56,10 @@ private static RetryPolicy<HttpResponse<String>> buildRetryPolicy() {
.build();
}

private static RateLimiter<Object> getRateLimit() {
return RateLimiter.burstyBuilder(3, Duration.ofSeconds(10)).build();
}

private static HttpClient buildHttpClient() {
return HttpClient
.newBuilder()
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/com/vodacom/falcon/config/RateLimitInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.vodacom.falcon.config;

import io.github.bucket4j.Bucket;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.time.Duration;

@Component
public class RateLimitInterceptor implements HandlerInterceptor {

private Bucket createNewBucket() {
return Bucket.builder()
.addLimit(limit -> limit.capacity(10)
.refillIntervally(10, Duration.ofMinutes(1)))
.build();
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession(true);

String ip = request.getRemoteAddr();
Bucket bucket = (Bucket) session.getAttribute("throttler-" + ip);

if (bucket == null) {
bucket = createNewBucket();
session.setAttribute("throttler-" + ip, bucket);
}
if (bucket.tryConsume(1)) {
return true;
}

response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "You have exhausted your API Request Quota");
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.vodacom.falcon.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class RequestInterceptorConfig implements WebMvcConfigurer {
@Autowired
private RateLimitInterceptor interceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor)
.addPathPatterns("/falcon/**");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public ResponseEntity<InsightResponse> getInsight(@RequestParam("city") String c
}

@GetMapping("/historical")
public ResponseEntity<HistoricalEconomyInsightResponse> getHistoricalInsights(@RequestParam("city") String city) {
public ResponseEntity<HistoricalEconomyInsightResponse> getHistoricalInsights(@RequestParam("city") String city) throws InterruptedException {
HistoricalEconomyInsightResponse response = falconInsightService.getHistoricalInsights(city);
return new ResponseEntity<>(response, HttpStatus.OK);
}
Expand Down

0 comments on commit 498ba01

Please sign in to comment.